mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-11-03 19:17:13 -05:00 
			
		
		
		
	Accounted for .sender in a few places
This commit is contained in:
		
							parent
							
								
									ba7878b9aa
								
							
						
					
					
						commit
						5d4587ef8b
					
				@ -45,9 +45,9 @@ class DocumentAdmin(admin.ModelAdmin):
 | 
			
		||||
            "all": ("paperless.css",)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    search_fields = ("sender__name", "title", "content")
 | 
			
		||||
    list_display = ("created_", "sender", "title", "tags_", "document")
 | 
			
		||||
    list_filter = ("tags", "sender", MonthListFilter)
 | 
			
		||||
    search_fields = ("correspondent__name", "title", "content")
 | 
			
		||||
    list_display = ("created_", "correspondent", "title", "tags_", "document")
 | 
			
		||||
    list_filter = ("tags", "correspondent", MonthListFilter)
 | 
			
		||||
    list_per_page = 25
 | 
			
		||||
 | 
			
		||||
    def created_(self, obj):
 | 
			
		||||
 | 
			
		||||
@ -57,11 +57,11 @@ class Consumer(object):
 | 
			
		||||
        r"^.*/(.*)\.(pdf|jpe?g|png|gif|tiff)$",
 | 
			
		||||
        flags=re.IGNORECASE
 | 
			
		||||
    )
 | 
			
		||||
    REGEX_SENDER_TITLE = re.compile(
 | 
			
		||||
    REGEX_CORRESPONDENT_TITLE = re.compile(
 | 
			
		||||
        r"^.*/(.+) - (.*)\.(pdf|jpe?g|png|gif|tiff)$",
 | 
			
		||||
        flags=re.IGNORECASE
 | 
			
		||||
    )
 | 
			
		||||
    REGEX_SENDER_TITLE_TAGS = re.compile(
 | 
			
		||||
    REGEX_CORRESPONDENT_TITLE_TAGS = re.compile(
 | 
			
		||||
        r"^.*/(.*) - (.*) - ([a-z0-9\-,]*)\.(pdf|jpe?g|png|gif|tiff)$",
 | 
			
		||||
        flags=re.IGNORECASE
 | 
			
		||||
    )
 | 
			
		||||
@ -238,16 +238,18 @@ class Consumer(object):
 | 
			
		||||
 | 
			
		||||
    def _guess_attributes_from_name(self, parseable):
 | 
			
		||||
        """
 | 
			
		||||
        We use a crude naming convention to make handling the sender, title,
 | 
			
		||||
        and tags easier:
 | 
			
		||||
          "<sender> - <title> - <tags>.<suffix>"
 | 
			
		||||
          "<sender> - <title>.<suffix>"
 | 
			
		||||
        We use a crude naming convention to make handling the correspondent,
 | 
			
		||||
        title, and tags easier:
 | 
			
		||||
          "<correspondent> - <title> - <tags>.<suffix>"
 | 
			
		||||
          "<correspondent> - <title>.<suffix>"
 | 
			
		||||
          "<title>.<suffix>"
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        def get_sender(sender_name):
 | 
			
		||||
        def get_correspondent(correspondent_name):
 | 
			
		||||
            return Correspondent.objects.get_or_create(
 | 
			
		||||
                name=sender_name, defaults={"slug": slugify(sender_name)})[0]
 | 
			
		||||
                name=correspondent_name,
 | 
			
		||||
                defaults={"slug": slugify(correspondent_name)}
 | 
			
		||||
            )[0]
 | 
			
		||||
 | 
			
		||||
        def get_tags(tags):
 | 
			
		||||
            r = []
 | 
			
		||||
@ -262,27 +264,27 @@ class Consumer(object):
 | 
			
		||||
                return "jpg"
 | 
			
		||||
            return suffix
 | 
			
		||||
 | 
			
		||||
        # First attempt: "<sender> - <title> - <tags>.<suffix>"
 | 
			
		||||
        m = re.match(self.REGEX_SENDER_TITLE_TAGS, parseable)
 | 
			
		||||
        # First attempt: "<correspondent> - <title> - <tags>.<suffix>"
 | 
			
		||||
        m = re.match(self.REGEX_CORRESPONDENT_TITLE_TAGS, parseable)
 | 
			
		||||
        if m:
 | 
			
		||||
            return (
 | 
			
		||||
                get_sender(m.group(1)),
 | 
			
		||||
                get_correspondent(m.group(1)),
 | 
			
		||||
                m.group(2),
 | 
			
		||||
                get_tags(m.group(3)),
 | 
			
		||||
                get_suffix(m.group(4))
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        # Second attempt: "<sender> - <title>.<suffix>"
 | 
			
		||||
        m = re.match(self.REGEX_SENDER_TITLE, parseable)
 | 
			
		||||
        # Second attempt: "<correspondent> - <title>.<suffix>"
 | 
			
		||||
        m = re.match(self.REGEX_CORRESPONDENT_TITLE, parseable)
 | 
			
		||||
        if m:
 | 
			
		||||
            return (
 | 
			
		||||
                get_sender(m.group(1)),
 | 
			
		||||
                get_correspondent(m.group(1)),
 | 
			
		||||
                m.group(2),
 | 
			
		||||
                (),
 | 
			
		||||
                get_suffix(m.group(3))
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        # That didn't work, so we assume sender and tags are None
 | 
			
		||||
        # That didn't work, so we assume correspondent and tags are None
 | 
			
		||||
        m = re.match(self.REGEX_TITLE, parseable)
 | 
			
		||||
        return None, m.group(1), (), get_suffix(m.group(2))
 | 
			
		||||
 | 
			
		||||
@ -296,7 +298,7 @@ class Consumer(object):
 | 
			
		||||
        self.log("debug", "Saving record to database")
 | 
			
		||||
 | 
			
		||||
        document = Document.objects.create(
 | 
			
		||||
            sender=sender,
 | 
			
		||||
            correspondent=sender,
 | 
			
		||||
            title=title,
 | 
			
		||||
            content=text,
 | 
			
		||||
            file_type=file_type,
 | 
			
		||||
 | 
			
		||||
@ -23,7 +23,7 @@ class UploadForm(forms.Form):
 | 
			
		||||
        "image/tiff": Document.TYPE_TIF,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    sender = forms.CharField(
 | 
			
		||||
    correspondent = forms.CharField(
 | 
			
		||||
        max_length=Correspondent._meta.get_field("name").max_length,
 | 
			
		||||
        required=False
 | 
			
		||||
    )
 | 
			
		||||
@ -34,18 +34,19 @@ class UploadForm(forms.Form):
 | 
			
		||||
    document = forms.FileField()
 | 
			
		||||
    signature = forms.CharField(max_length=256)
 | 
			
		||||
 | 
			
		||||
    def clean_sender(self):
 | 
			
		||||
    def clean_correspondent(self):
 | 
			
		||||
        """
 | 
			
		||||
        I suppose it might look cleaner to use .get_or_create() here, but that
 | 
			
		||||
        would also allow someone to fill up the db with bogus senders before
 | 
			
		||||
        all validation was met.
 | 
			
		||||
        would also allow someone to fill up the db with bogus correspondents
 | 
			
		||||
        before all validation was met.
 | 
			
		||||
        """
 | 
			
		||||
        sender = self.cleaned_data.get("sender")
 | 
			
		||||
        if not sender:
 | 
			
		||||
        corresp = self.cleaned_data.get("correspondent")
 | 
			
		||||
        if not corresp:
 | 
			
		||||
            return None
 | 
			
		||||
        if not Correspondent.SAFE_REGEX.match(sender) or " - " in sender:
 | 
			
		||||
            raise forms.ValidationError("That sender name is suspicious.")
 | 
			
		||||
        return sender
 | 
			
		||||
        if not Correspondent.SAFE_REGEX.match(corresp) or " - " in corresp:
 | 
			
		||||
            raise forms.ValidationError(
 | 
			
		||||
                "That correspondent name is suspicious.")
 | 
			
		||||
        return corresp
 | 
			
		||||
 | 
			
		||||
    def clean_title(self):
 | 
			
		||||
        title = self.cleaned_data.get("title")
 | 
			
		||||
@ -63,10 +64,10 @@ class UploadForm(forms.Form):
 | 
			
		||||
        return document, self.TYPE_LOOKUP[file_type]
 | 
			
		||||
 | 
			
		||||
    def clean(self):
 | 
			
		||||
        sender = self.clened_data("sender")
 | 
			
		||||
        corresp = self.clened_data("correspondent")
 | 
			
		||||
        title = self.cleaned_data("title")
 | 
			
		||||
        signature = self.cleaned_data("signature")
 | 
			
		||||
        if sha256(sender + title + self.SECRET).hexdigest() == signature:
 | 
			
		||||
        if sha256(corresp + title + self.SECRET).hexdigest() == signature:
 | 
			
		||||
            return True
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
@ -77,13 +78,15 @@ class UploadForm(forms.Form):
 | 
			
		||||
        form do that as well.  Think of it as a poor-man's queue server.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        sender = self.clened_data("sender")
 | 
			
		||||
        correspondent = self.clened_data("correspondent")
 | 
			
		||||
        title = self.cleaned_data("title")
 | 
			
		||||
        document, file_type = self.cleaned_data.get("document")
 | 
			
		||||
 | 
			
		||||
        t = int(mktime(datetime.now()))
 | 
			
		||||
        file_name = os.path.join(
 | 
			
		||||
            Consumer.CONSUME, "{} - {}.{}".format(sender, title, file_type))
 | 
			
		||||
            Consumer.CONSUME,
 | 
			
		||||
            "{} - {}.{}".format(correspondent, title, file_type)
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        with open(file_name, "wb") as f:
 | 
			
		||||
            f.write(document)
 | 
			
		||||
 | 
			
		||||
@ -22,6 +22,13 @@ class Command(Renderable, BaseCommand):
 | 
			
		||||
 | 
			
		||||
    def add_arguments(self, parser):
 | 
			
		||||
        parser.add_argument("target")
 | 
			
		||||
        parser.add_argument(
 | 
			
		||||
            "--legacy",
 | 
			
		||||
            action="store_true",
 | 
			
		||||
            help="Don't try to export all of the document data, just dump the "
 | 
			
		||||
                 "original document files out in a format that makes "
 | 
			
		||||
                 "re-consuming them easy."
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        BaseCommand.__init__(self, *args, **kwargs)
 | 
			
		||||
@ -40,6 +47,13 @@ class Command(Renderable, BaseCommand):
 | 
			
		||||
        if not settings.PASSPHRASE:
 | 
			
		||||
            settings.PASSPHRASE = input("Please enter the passphrase: ")
 | 
			
		||||
 | 
			
		||||
        if options["legacy"]:
 | 
			
		||||
            self.dump_legacy()
 | 
			
		||||
        else:
 | 
			
		||||
            self.dump()
 | 
			
		||||
 | 
			
		||||
    def dump(self):
 | 
			
		||||
 | 
			
		||||
        documents = Document.objects.all()
 | 
			
		||||
        document_map = {d.pk: d for d in documents}
 | 
			
		||||
        manifest = json.loads(serializers.serialize("json", documents))
 | 
			
		||||
@ -65,3 +79,28 @@ class Command(Renderable, BaseCommand):
 | 
			
		||||
 | 
			
		||||
        with open(os.path.join(self.target, "manifest.json"), "w") as f:
 | 
			
		||||
            json.dump(manifest, f, indent=2)
 | 
			
		||||
 | 
			
		||||
    def dump_legacy(self):
 | 
			
		||||
 | 
			
		||||
        for document in Document.objects.all():
 | 
			
		||||
 | 
			
		||||
            target = os.path.join(
 | 
			
		||||
                self.target, self._get_legacy_file_name(document))
 | 
			
		||||
 | 
			
		||||
            print("Exporting: {}".format(target))
 | 
			
		||||
 | 
			
		||||
            with open(target, "wb") as f:
 | 
			
		||||
                f.write(GnuPG.decrypted(document.source_file))
 | 
			
		||||
                t = int(time.mktime(document.created.timetuple()))
 | 
			
		||||
                os.utime(target, times=(t, t))
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def _get_legacy_file_name(doc):
 | 
			
		||||
        if doc.correspondent and doc.title:
 | 
			
		||||
            tags = ",".join([t.slug for t in doc.tags.all()])
 | 
			
		||||
            if tags:
 | 
			
		||||
                return "{} - {} - {}.{}".format(
 | 
			
		||||
                    doc.correspondent, doc.title, tags, doc.file_type)
 | 
			
		||||
            return "{} - {}.{}".format(
 | 
			
		||||
                doc.correspondent, doc.title, doc.file_type)
 | 
			
		||||
        return os.path.basename(doc.source_path)
 | 
			
		||||
 | 
			
		||||
@ -16,4 +16,13 @@ class Migration(migrations.Migration):
 | 
			
		||||
            old_name='Sender',
 | 
			
		||||
            new_name='Correspondent',
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AlterModelOptions(
 | 
			
		||||
            name='document',
 | 
			
		||||
            options={'ordering': ('correspondent', 'title')},
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.RenameField(
 | 
			
		||||
            model_name='document',
 | 
			
		||||
            old_name='sender',
 | 
			
		||||
            new_name='correspondent',
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
@ -140,7 +140,7 @@ class Document(models.Model):
 | 
			
		||||
    TYPE_TIF = "tiff"
 | 
			
		||||
    TYPES = (TYPE_PDF, TYPE_PNG, TYPE_JPG, TYPE_GIF, TYPE_TIF,)
 | 
			
		||||
 | 
			
		||||
    sender = models.ForeignKey(
 | 
			
		||||
    correspondent = models.ForeignKey(
 | 
			
		||||
        Correspondent, blank=True, null=True, related_name="documents")
 | 
			
		||||
    title = models.CharField(max_length=128, blank=True, db_index=True)
 | 
			
		||||
    content = models.TextField(db_index=True)
 | 
			
		||||
@ -155,14 +155,15 @@ class Document(models.Model):
 | 
			
		||||
    modified = models.DateTimeField(auto_now=True, editable=False)
 | 
			
		||||
 | 
			
		||||
    class Meta(object):
 | 
			
		||||
        ordering = ("sender", "title")
 | 
			
		||||
        ordering = ("correspondent", "title")
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        created = self.created.strftime("%Y%m%d%H%M%S")
 | 
			
		||||
        if self.sender and self.title:
 | 
			
		||||
            return "{}: {} - {}".format(created, self.sender, self.title)
 | 
			
		||||
        if self.sender or self.title:
 | 
			
		||||
            return "{}: {}".format(created, self.sender or self.title)
 | 
			
		||||
        if self.correspondent and self.title:
 | 
			
		||||
            return "{}: {} - {}".format(
 | 
			
		||||
                created, self.correspondent, self.title)
 | 
			
		||||
        if self.correspondent or self.title:
 | 
			
		||||
            return "{}: {}".format(created, self.correspondent or self.title)
 | 
			
		||||
        return str(created)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
 | 
			
		||||
@ -20,8 +20,8 @@ class TagSerializer(serializers.HyperlinkedModelSerializer):
 | 
			
		||||
 | 
			
		||||
class DocumentSerializer(serializers.ModelSerializer):
 | 
			
		||||
 | 
			
		||||
    sender = serializers.HyperlinkedRelatedField(
 | 
			
		||||
        read_only=True, view_name="drf:sender-detail", allow_null=True)
 | 
			
		||||
    correspondent = serializers.HyperlinkedRelatedField(
 | 
			
		||||
        read_only=True, view_name="drf:correspondent-detail", allow_null=True)
 | 
			
		||||
    tags = serializers.HyperlinkedRelatedField(
 | 
			
		||||
        read_only=True, view_name="drf:tag-detail", many=True)
 | 
			
		||||
 | 
			
		||||
@ -29,7 +29,7 @@ class DocumentSerializer(serializers.ModelSerializer):
 | 
			
		||||
        model = Document
 | 
			
		||||
        fields = (
 | 
			
		||||
            "id",
 | 
			
		||||
            "sender",
 | 
			
		||||
            "correspondent",
 | 
			
		||||
            "title",
 | 
			
		||||
            "content",
 | 
			
		||||
            "file_type",
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user