diff --git a/src/documents/migrations/0001_initial.py b/src/documents/migrations/0001_initial.py index 89c9e29df..e1e0cf3ba 100644 --- a/src/documents/migrations/0001_initial.py +++ b/src/documents/migrations/0001_initial.py @@ -1,5 +1,13 @@ -# Generated by Django 1.9 on 2015-12-20 19:10 +# Generated by Django 5.2.9 on 2026-01-20 18:46 +import datetime + +import django.core.validators +import django.db.models.deletion +import django.db.models.functions.comparison +import django.db.models.functions.text +import django.utils.timezone +import multiselectfield.db.fields from django.conf import settings from django.db import migrations from django.db import models @@ -8,9 +16,239 @@ from django.db import models class Migration(migrations.Migration): initial = True - dependencies = [] + dependencies = [ + ("auth", "0012_alter_user_first_name_max_length"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] operations = [ + migrations.CreateModel( + name="WorkflowActionEmail", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "subject", + models.CharField( + help_text="The subject of the email, can include some placeholders, see documentation.", + max_length=256, + verbose_name="email subject", + ), + ), + ( + "body", + models.TextField( + help_text="The body (message) of the email, can include some placeholders, see documentation.", + verbose_name="email body", + ), + ), + ( + "to", + models.TextField( + help_text="The destination email addresses, comma separated.", + verbose_name="emails to", + ), + ), + ( + "include_document", + models.BooleanField( + default=False, + verbose_name="include document in email", + ), + ), + ], + ), + migrations.CreateModel( + name="WorkflowActionWebhook", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "url", + models.CharField( + help_text="The destination URL for the notification.", + max_length=256, + verbose_name="webhook url", + ), + ), + ( + "use_params", + models.BooleanField(default=True, verbose_name="use parameters"), + ), + ( + "as_json", + models.BooleanField(default=False, verbose_name="send as JSON"), + ), + ( + "params", + models.JSONField( + blank=True, + help_text="The parameters to send with the webhook URL if body not used.", + null=True, + verbose_name="webhook parameters", + ), + ), + ( + "body", + models.TextField( + blank=True, + help_text="The body to send with the webhook URL if parameters not used.", + null=True, + verbose_name="webhook body", + ), + ), + ( + "headers", + models.JSONField( + blank=True, + help_text="The headers to send with the webhook URL.", + null=True, + verbose_name="webhook headers", + ), + ), + ( + "include_document", + models.BooleanField( + default=False, + verbose_name="include document in webhook", + ), + ), + ], + ), + migrations.CreateModel( + name="Correspondent", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=128, verbose_name="name")), + ( + "match", + models.CharField(blank=True, max_length=256, verbose_name="match"), + ), + ( + "matching_algorithm", + models.PositiveIntegerField( + choices=[ + (0, "None"), + (1, "Any word"), + (2, "All words"), + (3, "Exact match"), + (4, "Regular expression"), + (5, "Fuzzy word"), + (6, "Automatic"), + ], + default=1, + verbose_name="matching algorithm", + ), + ), + ( + "is_insensitive", + models.BooleanField(default=True, verbose_name="is insensitive"), + ), + ( + "owner", + models.ForeignKey( + blank=True, + default=None, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to=settings.AUTH_USER_MODEL, + verbose_name="owner", + ), + ), + ], + options={ + "verbose_name": "correspondent", + "verbose_name_plural": "correspondents", + "ordering": ("name",), + "abstract": False, + }, + ), + migrations.CreateModel( + name="CustomField", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "created", + models.DateTimeField( + db_index=True, + default=django.utils.timezone.now, + editable=False, + verbose_name="created", + ), + ), + ("name", models.CharField(max_length=128)), + ( + "data_type", + models.CharField( + choices=[ + ("string", "String"), + ("url", "URL"), + ("date", "Date"), + ("boolean", "Boolean"), + ("integer", "Integer"), + ("float", "Float"), + ("monetary", "Monetary"), + ("documentlink", "Document Link"), + ("select", "Select"), + ("longtext", "Long Text"), + ], + editable=False, + max_length=50, + verbose_name="data type", + ), + ), + ( + "extra_data", + models.JSONField( + blank=True, + help_text="Extra data for the custom field, such as select options", + null=True, + verbose_name="extra data", + ), + ), + ], + options={ + "verbose_name": "custom field", + "verbose_name_plural": "custom fields", + "ordering": ("created",), + "constraints": [ + models.UniqueConstraint( + fields=("name",), + name="documents_customfield_unique_name", + ), + ], + }, + ), migrations.CreateModel( name="Document", fields=[ @@ -23,18 +261,1716 @@ class Migration(migrations.Migration): verbose_name="ID", ), ), - ("sender", models.CharField(blank=True, db_index=True, max_length=128)), - ("title", models.CharField(blank=True, db_index=True, max_length=128)), + ("deleted_at", models.DateTimeField(blank=True, null=True)), + ("restored_at", models.DateTimeField(blank=True, null=True)), + ("transaction_id", models.UUIDField(blank=True, null=True)), + ( + "title", + models.CharField( + blank=True, + db_index=True, + max_length=128, + verbose_name="title", + ), + ), ( "content", models.TextField( - db_index=( - "mysql" not in settings.DATABASES["default"]["ENGINE"] + blank=True, + help_text="The raw, text-only data of the document. This field is primarily used for searching.", + verbose_name="content", + ), + ), + ( + "mime_type", + models.CharField( + editable=False, + max_length=256, + verbose_name="mime type", + ), + ), + ( + "checksum", + models.CharField( + editable=False, + help_text="The checksum of the original document.", + max_length=32, + unique=True, + verbose_name="checksum", + ), + ), + ( + "archive_checksum", + models.CharField( + blank=True, + editable=False, + help_text="The checksum of the archived document.", + max_length=32, + null=True, + verbose_name="archive checksum", + ), + ), + ( + "page_count", + models.PositiveIntegerField( + help_text="The number of pages of the document.", + null=True, + validators=[django.core.validators.MinValueValidator(1)], + verbose_name="page count", + ), + ), + ( + "created", + models.DateField( + db_index=True, + default=datetime.date.today, + verbose_name="created", + ), + ), + ( + "modified", + models.DateTimeField( + auto_now=True, + db_index=True, + verbose_name="modified", + ), + ), + ( + "storage_type", + models.CharField( + choices=[ + ("unencrypted", "Unencrypted"), + ("gpg", "Encrypted with GNU Privacy Guard"), + ], + default="unencrypted", + editable=False, + max_length=11, + verbose_name="storage type", + ), + ), + ( + "added", + models.DateTimeField( + db_index=True, + default=django.utils.timezone.now, + editable=False, + verbose_name="added", + ), + ), + ( + "filename", + models.FilePathField( + default=None, + editable=False, + help_text="Current filename in storage", + max_length=1024, + null=True, + unique=True, + verbose_name="filename", + ), + ), + ( + "archive_filename", + models.FilePathField( + default=None, + editable=False, + help_text="Current archive filename in storage", + max_length=1024, + null=True, + unique=True, + verbose_name="archive filename", + ), + ), + ( + "original_filename", + models.CharField( + default=None, + editable=False, + help_text="The original name of the file when it was uploaded", + max_length=1024, + null=True, + verbose_name="original filename", + ), + ), + ( + "archive_serial_number", + models.PositiveIntegerField( + blank=True, + db_index=True, + help_text="The position of this document in your physical document archive.", + null=True, + unique=True, + validators=[ + django.core.validators.MaxValueValidator(4294967295), + django.core.validators.MinValueValidator(0), + ], + verbose_name="archive serial number", + ), + ), + ( + "correspondent", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="documents", + to="documents.correspondent", + verbose_name="correspondent", + ), + ), + ( + "owner", + models.ForeignKey( + blank=True, + default=None, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to=settings.AUTH_USER_MODEL, + verbose_name="owner", + ), + ), + ], + options={ + "verbose_name": "document", + "verbose_name_plural": "documents", + "ordering": ("-created",), + }, + ), + migrations.CreateModel( + name="CustomFieldInstance", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("deleted_at", models.DateTimeField(blank=True, null=True)), + ("restored_at", models.DateTimeField(blank=True, null=True)), + ("transaction_id", models.UUIDField(blank=True, null=True)), + ( + "created", + models.DateTimeField( + db_index=True, + default=django.utils.timezone.now, + editable=False, + verbose_name="created", + ), + ), + ("value_text", models.CharField(max_length=128, null=True)), + ("value_bool", models.BooleanField(null=True)), + ("value_url", models.URLField(null=True)), + ("value_date", models.DateField(null=True)), + ("value_int", models.IntegerField(null=True)), + ("value_float", models.FloatField(null=True)), + ("value_monetary", models.CharField(max_length=128, null=True)), + ( + "value_monetary_amount", + models.GeneratedField( + db_persist=True, + expression=models.Case( + models.When( + then=django.db.models.functions.comparison.Cast( + django.db.models.functions.text.Substr( + "value_monetary", + 1, + ), + output_field=models.DecimalField( + decimal_places=2, + max_digits=65, + ), + ), + value_monetary__regex="^\\d+", + ), + default=django.db.models.functions.comparison.Cast( + django.db.models.functions.text.Substr( + "value_monetary", + 4, + ), + output_field=models.DecimalField( + decimal_places=2, + max_digits=65, + ), + ), + output_field=models.DecimalField( + decimal_places=2, + max_digits=65, + ), + ), + output_field=models.DecimalField( + decimal_places=2, + max_digits=65, ), ), ), - ("created", models.DateTimeField(auto_now_add=True)), - ("modified", models.DateTimeField(auto_now=True)), + ("value_document_ids", models.JSONField(null=True)), + ("value_select", models.CharField(max_length=16, null=True)), + ("value_long_text", models.TextField(null=True)), + ( + "field", + models.ForeignKey( + editable=False, + on_delete=django.db.models.deletion.CASCADE, + related_name="fields", + to="documents.customfield", + ), + ), + ( + "document", + models.ForeignKey( + editable=False, + on_delete=django.db.models.deletion.CASCADE, + related_name="custom_fields", + to="documents.document", + ), + ), + ], + options={ + "verbose_name": "custom field instance", + "verbose_name_plural": "custom field instances", + "ordering": ("created",), + }, + ), + migrations.CreateModel( + name="DocumentType", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=128, verbose_name="name")), + ( + "match", + models.CharField(blank=True, max_length=256, verbose_name="match"), + ), + ( + "matching_algorithm", + models.PositiveIntegerField( + choices=[ + (0, "None"), + (1, "Any word"), + (2, "All words"), + (3, "Exact match"), + (4, "Regular expression"), + (5, "Fuzzy word"), + (6, "Automatic"), + ], + default=1, + verbose_name="matching algorithm", + ), + ), + ( + "is_insensitive", + models.BooleanField(default=True, verbose_name="is insensitive"), + ), + ( + "owner", + models.ForeignKey( + blank=True, + default=None, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to=settings.AUTH_USER_MODEL, + verbose_name="owner", + ), + ), + ], + options={ + "verbose_name": "document type", + "verbose_name_plural": "document types", + "ordering": ("name",), + "abstract": False, + }, + ), + migrations.AddField( + model_name="document", + name="document_type", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="documents", + to="documents.documenttype", + verbose_name="document type", + ), + ), + migrations.CreateModel( + name="Note", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("deleted_at", models.DateTimeField(blank=True, null=True)), + ("restored_at", models.DateTimeField(blank=True, null=True)), + ("transaction_id", models.UUIDField(blank=True, null=True)), + ( + "note", + models.TextField( + blank=True, + help_text="Note for the document", + verbose_name="content", + ), + ), + ( + "created", + models.DateTimeField( + db_index=True, + default=django.utils.timezone.now, + verbose_name="created", + ), + ), + ( + "document", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="notes", + to="documents.document", + verbose_name="document", + ), + ), + ( + "user", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="notes", + to=settings.AUTH_USER_MODEL, + verbose_name="user", + ), + ), + ], + options={ + "verbose_name": "note", + "verbose_name_plural": "notes", + "ordering": ("created",), + }, + ), + migrations.CreateModel( + name="PaperlessTask", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "task_id", + models.CharField( + help_text="Celery ID for the Task that was run", + max_length=255, + unique=True, + verbose_name="Task ID", + ), + ), + ( + "acknowledged", + models.BooleanField( + default=False, + help_text="If the task is acknowledged via the frontend or API", + verbose_name="Acknowledged", + ), + ), + ( + "task_file_name", + models.CharField( + help_text="Name of the file which the Task was run for", + max_length=255, + null=True, + verbose_name="Task Filename", + ), + ), + ( + "task_name", + models.CharField( + choices=[ + ("consume_file", "Consume File"), + ("train_classifier", "Train Classifier"), + ("check_sanity", "Check Sanity"), + ("index_optimize", "Index Optimize"), + ("llmindex_update", "LLM Index Update"), + ], + help_text="Name of the task that was run", + max_length=255, + null=True, + verbose_name="Task Name", + ), + ), + ( + "status", + models.CharField( + choices=[ + ("FAILURE", "FAILURE"), + ("PENDING", "PENDING"), + ("RECEIVED", "RECEIVED"), + ("RETRY", "RETRY"), + ("REVOKED", "REVOKED"), + ("STARTED", "STARTED"), + ("SUCCESS", "SUCCESS"), + ], + default="PENDING", + help_text="Current state of the task being run", + max_length=30, + verbose_name="Task State", + ), + ), + ( + "date_created", + models.DateTimeField( + default=django.utils.timezone.now, + help_text="Datetime field when the task result was created in UTC", + null=True, + verbose_name="Created DateTime", + ), + ), + ( + "date_started", + models.DateTimeField( + default=None, + help_text="Datetime field when the task was started in UTC", + null=True, + verbose_name="Started DateTime", + ), + ), + ( + "date_done", + models.DateTimeField( + default=None, + help_text="Datetime field when the task was completed in UTC", + null=True, + verbose_name="Completed DateTime", + ), + ), + ( + "result", + models.TextField( + default=None, + help_text="The data returned by the task", + null=True, + verbose_name="Result Data", + ), + ), + ( + "type", + models.CharField( + choices=[ + ("auto_task", "Auto Task"), + ("scheduled_task", "Scheduled Task"), + ("manual_task", "Manual Task"), + ], + default="auto_task", + help_text="The type of task that was run", + max_length=30, + verbose_name="Task Type", + ), + ), + ( + "owner", + models.ForeignKey( + blank=True, + default=None, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to=settings.AUTH_USER_MODEL, + verbose_name="owner", + ), + ), + ], + options={ + "abstract": False, + }, + ), + migrations.CreateModel( + name="SavedView", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=128, verbose_name="name")), + ( + "show_on_dashboard", + models.BooleanField(verbose_name="show on dashboard"), + ), + ( + "show_in_sidebar", + models.BooleanField(verbose_name="show in sidebar"), + ), + ( + "sort_field", + models.CharField( + blank=True, + max_length=128, + null=True, + verbose_name="sort field", + ), + ), + ( + "sort_reverse", + models.BooleanField(default=False, verbose_name="sort reverse"), + ), + ( + "page_size", + models.PositiveIntegerField( + blank=True, + null=True, + validators=[django.core.validators.MinValueValidator(1)], + verbose_name="View page size", + ), + ), + ( + "display_mode", + models.CharField( + blank=True, + choices=[ + ("table", "Table"), + ("smallCards", "Small Cards"), + ("largeCards", "Large Cards"), + ], + max_length=128, + null=True, + verbose_name="View display mode", + ), + ), + ( + "display_fields", + models.JSONField( + blank=True, + null=True, + verbose_name="Document display fields", + ), + ), + ( + "owner", + models.ForeignKey( + blank=True, + default=None, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to=settings.AUTH_USER_MODEL, + verbose_name="owner", + ), + ), + ], + options={ + "verbose_name": "saved view", + "verbose_name_plural": "saved views", + "ordering": ("name",), + }, + ), + migrations.CreateModel( + name="SavedViewFilterRule", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "rule_type", + models.PositiveIntegerField( + choices=[ + (0, "title contains"), + (1, "content contains"), + (2, "ASN is"), + (3, "correspondent is"), + (4, "document type is"), + (5, "is in inbox"), + (6, "has tag"), + (7, "has any tag"), + (8, "created before"), + (9, "created after"), + (10, "created year is"), + (11, "created month is"), + (12, "created day is"), + (13, "added before"), + (14, "added after"), + (15, "modified before"), + (16, "modified after"), + (17, "does not have tag"), + (18, "does not have ASN"), + (19, "title or content contains"), + (20, "fulltext query"), + (21, "more like this"), + (22, "has tags in"), + (23, "ASN greater than"), + (24, "ASN less than"), + (25, "storage path is"), + (26, "has correspondent in"), + (27, "does not have correspondent in"), + (28, "has document type in"), + (29, "does not have document type in"), + (30, "has storage path in"), + (31, "does not have storage path in"), + (32, "owner is"), + (33, "has owner in"), + (34, "does not have owner"), + (35, "does not have owner in"), + (36, "has custom field value"), + (37, "is shared by me"), + (38, "has custom fields"), + (39, "has custom field in"), + (40, "does not have custom field in"), + (41, "does not have custom field"), + (42, "custom fields query"), + (43, "created to"), + (44, "created from"), + (45, "added to"), + (46, "added from"), + (47, "mime type is"), + ], + verbose_name="rule type", + ), + ), + ( + "value", + models.CharField( + blank=True, + max_length=255, + null=True, + verbose_name="value", + ), + ), + ( + "saved_view", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="filter_rules", + to="documents.savedview", + verbose_name="saved view", + ), + ), + ], + options={ + "verbose_name": "filter rule", + "verbose_name_plural": "filter rules", + }, + ), + migrations.CreateModel( + name="ShareLink", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("deleted_at", models.DateTimeField(blank=True, null=True)), + ("restored_at", models.DateTimeField(blank=True, null=True)), + ("transaction_id", models.UUIDField(blank=True, null=True)), + ( + "created", + models.DateTimeField( + blank=True, + db_index=True, + default=django.utils.timezone.now, + editable=False, + verbose_name="created", + ), + ), + ( + "expiration", + models.DateTimeField( + blank=True, + db_index=True, + null=True, + verbose_name="expiration", + ), + ), + ( + "slug", + models.SlugField( + blank=True, + editable=False, + unique=True, + verbose_name="slug", + ), + ), + ( + "file_version", + models.CharField( + choices=[("archive", "Archive"), ("original", "Original")], + default="archive", + max_length=50, + ), + ), + ( + "document", + models.ForeignKey( + blank=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="share_links", + to="documents.document", + verbose_name="document", + ), + ), + ( + "owner", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="share_links", + to=settings.AUTH_USER_MODEL, + verbose_name="owner", + ), + ), + ], + options={ + "verbose_name": "share link", + "verbose_name_plural": "share links", + "ordering": ("created",), + }, + ), + migrations.CreateModel( + name="StoragePath", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=128, verbose_name="name")), + ( + "match", + models.CharField(blank=True, max_length=256, verbose_name="match"), + ), + ( + "matching_algorithm", + models.PositiveIntegerField( + choices=[ + (0, "None"), + (1, "Any word"), + (2, "All words"), + (3, "Exact match"), + (4, "Regular expression"), + (5, "Fuzzy word"), + (6, "Automatic"), + ], + default=1, + verbose_name="matching algorithm", + ), + ), + ( + "is_insensitive", + models.BooleanField(default=True, verbose_name="is insensitive"), + ), + ("path", models.TextField(verbose_name="path")), + ( + "owner", + models.ForeignKey( + blank=True, + default=None, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to=settings.AUTH_USER_MODEL, + verbose_name="owner", + ), + ), + ], + options={ + "verbose_name": "storage path", + "verbose_name_plural": "storage paths", + "ordering": ("name",), + "abstract": False, + }, + ), + migrations.AddField( + model_name="document", + name="storage_path", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="documents", + to="documents.storagepath", + verbose_name="storage path", + ), + ), + migrations.CreateModel( + name="Tag", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "tn_ancestors_pks", + models.TextField( + blank=True, + default="", + editable=False, + verbose_name="Ancestors pks", + ), + ), + ( + "tn_ancestors_count", + models.PositiveIntegerField( + default=0, + editable=False, + verbose_name="Ancestors count", + ), + ), + ( + "tn_children_pks", + models.TextField( + blank=True, + default="", + editable=False, + verbose_name="Children pks", + ), + ), + ( + "tn_children_count", + models.PositiveIntegerField( + default=0, + editable=False, + verbose_name="Children count", + ), + ), + ( + "tn_depth", + models.PositiveIntegerField( + default=0, + editable=False, + validators=[ + django.core.validators.MinValueValidator(0), + django.core.validators.MaxValueValidator(10), + ], + verbose_name="Depth", + ), + ), + ( + "tn_descendants_pks", + models.TextField( + blank=True, + default="", + editable=False, + verbose_name="Descendants pks", + ), + ), + ( + "tn_descendants_count", + models.PositiveIntegerField( + default=0, + editable=False, + verbose_name="Descendants count", + ), + ), + ( + "tn_index", + models.PositiveIntegerField( + default=0, + editable=False, + verbose_name="Index", + ), + ), + ( + "tn_level", + models.PositiveIntegerField( + default=1, + editable=False, + validators=[ + django.core.validators.MinValueValidator(1), + django.core.validators.MaxValueValidator(10), + ], + verbose_name="Level", + ), + ), + ( + "tn_priority", + models.PositiveIntegerField( + default=0, + validators=[ + django.core.validators.MinValueValidator(0), + django.core.validators.MaxValueValidator(9999999999), + ], + verbose_name="Priority", + ), + ), + ( + "tn_order", + models.PositiveIntegerField( + default=0, + editable=False, + verbose_name="Order", + ), + ), + ( + "tn_siblings_pks", + models.TextField( + blank=True, + default="", + editable=False, + verbose_name="Siblings pks", + ), + ), + ( + "tn_siblings_count", + models.PositiveIntegerField( + default=0, + editable=False, + verbose_name="Siblings count", + ), + ), + ("name", models.CharField(max_length=128, verbose_name="name")), + ( + "match", + models.CharField(blank=True, max_length=256, verbose_name="match"), + ), + ( + "matching_algorithm", + models.PositiveIntegerField( + choices=[ + (0, "None"), + (1, "Any word"), + (2, "All words"), + (3, "Exact match"), + (4, "Regular expression"), + (5, "Fuzzy word"), + (6, "Automatic"), + ], + default=1, + verbose_name="matching algorithm", + ), + ), + ( + "is_insensitive", + models.BooleanField(default=True, verbose_name="is insensitive"), + ), + ( + "color", + models.CharField( + default="#a6cee3", + max_length=7, + verbose_name="color", + ), + ), + ( + "is_inbox_tag", + models.BooleanField( + default=False, + help_text="Marks this tag as an inbox tag: All newly consumed documents will be tagged with inbox tags.", + verbose_name="is inbox tag", + ), + ), + ( + "owner", + models.ForeignKey( + blank=True, + default=None, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to=settings.AUTH_USER_MODEL, + verbose_name="owner", + ), + ), + ( + "tn_parent", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="tn_children", + to="documents.tag", + verbose_name="Parent", + ), + ), + ], + options={ + "verbose_name": "tag", + "verbose_name_plural": "tags", + "ordering": ("name",), + "abstract": False, + }, + ), + migrations.AddField( + model_name="document", + name="tags", + field=models.ManyToManyField( + blank=True, + related_name="documents", + to="documents.tag", + verbose_name="tags", + ), + ), + migrations.CreateModel( + name="UiSettings", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("settings", models.JSONField(null=True)), + ( + "user", + models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, + related_name="ui_settings", + to=settings.AUTH_USER_MODEL, + ), + ), ], ), + migrations.CreateModel( + name="WorkflowAction", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "type", + models.PositiveIntegerField( + choices=[ + (1, "Assignment"), + (2, "Removal"), + (3, "Email"), + (4, "Webhook"), + ], + default=1, + verbose_name="Workflow Action Type", + ), + ), + ("order", models.PositiveIntegerField(default=0, verbose_name="order")), + ( + "assign_title", + models.TextField( + blank=True, + help_text="Assign a document title, must be a Jinja2 template, see documentation.", + null=True, + verbose_name="assign title", + ), + ), + ( + "assign_custom_fields_values", + models.JSONField( + blank=True, + default=dict, + help_text="Optional values to assign to the custom fields.", + null=True, + verbose_name="custom field values", + ), + ), + ( + "remove_all_tags", + models.BooleanField(default=False, verbose_name="remove all tags"), + ), + ( + "remove_all_document_types", + models.BooleanField( + default=False, + verbose_name="remove all document types", + ), + ), + ( + "remove_all_correspondents", + models.BooleanField( + default=False, + verbose_name="remove all correspondents", + ), + ), + ( + "remove_all_storage_paths", + models.BooleanField( + default=False, + verbose_name="remove all storage paths", + ), + ), + ( + "remove_all_owners", + models.BooleanField( + default=False, + verbose_name="remove all owners", + ), + ), + ( + "remove_all_permissions", + models.BooleanField( + default=False, + verbose_name="remove all permissions", + ), + ), + ( + "remove_all_custom_fields", + models.BooleanField( + default=False, + verbose_name="remove all custom fields", + ), + ), + ( + "assign_change_groups", + models.ManyToManyField( + blank=True, + related_name="+", + to="auth.group", + verbose_name="grant change permissions to these groups", + ), + ), + ( + "assign_change_users", + models.ManyToManyField( + blank=True, + related_name="+", + to=settings.AUTH_USER_MODEL, + verbose_name="grant change permissions to these users", + ), + ), + ( + "assign_correspondent", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="+", + to="documents.correspondent", + verbose_name="assign this correspondent", + ), + ), + ( + "assign_custom_fields", + models.ManyToManyField( + blank=True, + related_name="+", + to="documents.customfield", + verbose_name="assign these custom fields", + ), + ), + ( + "assign_document_type", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="+", + to="documents.documenttype", + verbose_name="assign this document type", + ), + ), + ( + "assign_owner", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="+", + to=settings.AUTH_USER_MODEL, + verbose_name="assign this owner", + ), + ), + ( + "assign_storage_path", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="+", + to="documents.storagepath", + verbose_name="assign this storage path", + ), + ), + ( + "assign_tags", + models.ManyToManyField( + blank=True, + related_name="+", + to="documents.tag", + verbose_name="assign this tag", + ), + ), + ( + "assign_view_groups", + models.ManyToManyField( + blank=True, + related_name="+", + to="auth.group", + verbose_name="grant view permissions to these groups", + ), + ), + ( + "assign_view_users", + models.ManyToManyField( + blank=True, + related_name="+", + to=settings.AUTH_USER_MODEL, + verbose_name="grant view permissions to these users", + ), + ), + ( + "remove_change_groups", + models.ManyToManyField( + blank=True, + related_name="+", + to="auth.group", + verbose_name="remove change permissions for these groups", + ), + ), + ( + "remove_change_users", + models.ManyToManyField( + blank=True, + related_name="+", + to=settings.AUTH_USER_MODEL, + verbose_name="remove change permissions for these users", + ), + ), + ( + "remove_correspondents", + models.ManyToManyField( + blank=True, + related_name="+", + to="documents.correspondent", + verbose_name="remove these correspondent(s)", + ), + ), + ( + "remove_custom_fields", + models.ManyToManyField( + blank=True, + related_name="+", + to="documents.customfield", + verbose_name="remove these custom fields", + ), + ), + ( + "remove_document_types", + models.ManyToManyField( + blank=True, + related_name="+", + to="documents.documenttype", + verbose_name="remove these document type(s)", + ), + ), + ( + "remove_owners", + models.ManyToManyField( + blank=True, + related_name="+", + to=settings.AUTH_USER_MODEL, + verbose_name="remove these owner(s)", + ), + ), + ( + "remove_storage_paths", + models.ManyToManyField( + blank=True, + related_name="+", + to="documents.storagepath", + verbose_name="remove these storage path(s)", + ), + ), + ( + "remove_tags", + models.ManyToManyField( + blank=True, + related_name="+", + to="documents.tag", + verbose_name="remove these tag(s)", + ), + ), + ( + "remove_view_groups", + models.ManyToManyField( + blank=True, + related_name="+", + to="auth.group", + verbose_name="remove view permissions for these groups", + ), + ), + ( + "remove_view_users", + models.ManyToManyField( + blank=True, + related_name="+", + to=settings.AUTH_USER_MODEL, + verbose_name="remove view permissions for these users", + ), + ), + ( + "email", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="action", + to="documents.workflowactionemail", + verbose_name="email", + ), + ), + ( + "webhook", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="action", + to="documents.workflowactionwebhook", + verbose_name="webhook", + ), + ), + ], + options={ + "verbose_name": "workflow action", + "verbose_name_plural": "workflow actions", + }, + ), + migrations.CreateModel( + name="Workflow", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "name", + models.CharField(max_length=256, unique=True, verbose_name="name"), + ), + ("order", models.IntegerField(default=0, verbose_name="order")), + ("enabled", models.BooleanField(default=True, verbose_name="enabled")), + ( + "actions", + models.ManyToManyField( + related_name="workflows", + to="documents.workflowaction", + verbose_name="actions", + ), + ), + ], + ), + migrations.CreateModel( + name="WorkflowRun", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("deleted_at", models.DateTimeField(blank=True, null=True)), + ("restored_at", models.DateTimeField(blank=True, null=True)), + ("transaction_id", models.UUIDField(blank=True, null=True)), + ( + "type", + models.PositiveIntegerField( + choices=[ + (1, "Consumption Started"), + (2, "Document Added"), + (3, "Document Updated"), + (4, "Scheduled"), + ], + null=True, + verbose_name="workflow trigger type", + ), + ), + ( + "run_at", + models.DateTimeField( + db_index=True, + default=django.utils.timezone.now, + verbose_name="date run", + ), + ), + ( + "document", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="workflow_runs", + to="documents.document", + verbose_name="document", + ), + ), + ( + "workflow", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="runs", + to="documents.workflow", + verbose_name="workflow", + ), + ), + ], + options={ + "verbose_name": "workflow run", + "verbose_name_plural": "workflow runs", + }, + ), + migrations.CreateModel( + name="WorkflowTrigger", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "type", + models.PositiveIntegerField( + choices=[ + (1, "Consumption Started"), + (2, "Document Added"), + (3, "Document Updated"), + (4, "Scheduled"), + ], + default=1, + verbose_name="Workflow Trigger Type", + ), + ), + ( + "sources", + multiselectfield.db.fields.MultiSelectField( + choices=[ + (1, "Consume Folder"), + (2, "Api Upload"), + (3, "Mail Fetch"), + (4, "Web UI"), + ], + default="1,2,3,4", + max_length=7, + ), + ), + ( + "filter_path", + models.CharField( + blank=True, + help_text="Only consume documents with a path that matches this if specified. Wildcards specified as * are allowed. Case insensitive.", + max_length=256, + null=True, + verbose_name="filter path", + ), + ), + ( + "filter_filename", + models.CharField( + blank=True, + help_text="Only consume documents which entirely match this filename if specified. Wildcards such as *.pdf or *invoice* are allowed. Case insensitive.", + max_length=256, + null=True, + verbose_name="filter filename", + ), + ), + ( + "match", + models.CharField(blank=True, max_length=256, verbose_name="match"), + ), + ( + "matching_algorithm", + models.PositiveIntegerField( + choices=[ + (0, "None"), + (1, "Any word"), + (2, "All words"), + (3, "Exact match"), + (4, "Regular expression"), + (5, "Fuzzy word"), + ], + default=0, + verbose_name="matching algorithm", + ), + ), + ( + "is_insensitive", + models.BooleanField(default=True, verbose_name="is insensitive"), + ), + ( + "filter_custom_field_query", + models.TextField( + blank=True, + help_text="JSON-encoded custom field query expression.", + null=True, + verbose_name="filter custom field query", + ), + ), + ( + "schedule_offset_days", + models.IntegerField( + default=0, + help_text="The number of days to offset the schedule trigger by.", + verbose_name="schedule offset days", + ), + ), + ( + "schedule_is_recurring", + models.BooleanField( + default=False, + help_text="If the schedule should be recurring.", + verbose_name="schedule is recurring", + ), + ), + ( + "schedule_recurring_interval_days", + models.PositiveIntegerField( + default=1, + help_text="The number of days between recurring schedule triggers.", + validators=[django.core.validators.MinValueValidator(1)], + verbose_name="schedule recurring delay in days", + ), + ), + ( + "schedule_date_field", + models.CharField( + choices=[ + ("added", "Added"), + ("created", "Created"), + ("modified", "Modified"), + ("custom_field", "Custom Field"), + ], + default="added", + help_text="The field to check for a schedule trigger.", + max_length=20, + verbose_name="schedule date field", + ), + ), + ( + "filter_has_all_tags", + models.ManyToManyField( + blank=True, + related_name="workflowtriggers_has_all", + to="documents.tag", + verbose_name="has all of these tag(s)", + ), + ), + ( + "filter_has_correspondent", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="documents.correspondent", + verbose_name="has this correspondent", + ), + ), + ( + "filter_has_document_type", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="documents.documenttype", + verbose_name="has this document type", + ), + ), + ( + "filter_has_not_correspondents", + models.ManyToManyField( + blank=True, + related_name="workflowtriggers_has_not_correspondent", + to="documents.correspondent", + verbose_name="does not have these correspondent(s)", + ), + ), + ( + "filter_has_not_document_types", + models.ManyToManyField( + blank=True, + related_name="workflowtriggers_has_not_document_type", + to="documents.documenttype", + verbose_name="does not have these document type(s)", + ), + ), + ( + "filter_has_not_storage_paths", + models.ManyToManyField( + blank=True, + related_name="workflowtriggers_has_not_storage_path", + to="documents.storagepath", + verbose_name="does not have these storage path(s)", + ), + ), + ( + "filter_has_not_tags", + models.ManyToManyField( + blank=True, + related_name="workflowtriggers_has_not", + to="documents.tag", + verbose_name="does not have these tag(s)", + ), + ), + ( + "filter_has_storage_path", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="documents.storagepath", + verbose_name="has this storage path", + ), + ), + ( + "filter_has_tags", + models.ManyToManyField( + blank=True, + to="documents.tag", + verbose_name="has these tag(s)", + ), + ), + ], + options={ + "verbose_name": "workflow trigger", + "verbose_name_plural": "workflow triggers", + }, + ), ] diff --git a/src/documents/migrations/0002_auto_20151226_1316.py b/src/documents/migrations/0002_auto_20151226_1316.py deleted file mode 100644 index ffd240902..000000000 --- a/src/documents/migrations/0002_auto_20151226_1316.py +++ /dev/null @@ -1,26 +0,0 @@ -# Generated by Django 1.9 on 2015-12-26 13:16 - -import django.utils.timezone -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "0001_initial"), - ] - - operations = [ - migrations.AlterModelOptions( - name="document", - options={"ordering": ("sender", "title")}, - ), - migrations.AlterField( - model_name="document", - name="created", - field=models.DateTimeField( - default=django.utils.timezone.now, - editable=False, - ), - ), - ] diff --git a/src/documents/migrations/1033_alter_documenttype_options_alter_tag_options_and_more.py b/src/documents/migrations/0002_initial.py similarity index 61% rename from src/documents/migrations/1033_alter_documenttype_options_alter_tag_options_and_more.py rename to src/documents/migrations/0002_initial.py index 1368ac641..ba0e93da8 100644 --- a/src/documents/migrations/1033_alter_documenttype_options_alter_tag_options_and_more.py +++ b/src/documents/migrations/0002_initial.py @@ -1,50 +1,49 @@ -# Generated by Django 4.1.5 on 2023-03-04 22:33 +# Generated by Django 5.2.9 on 2026-01-20 18:46 +import django.db.models.deletion from django.db import migrations from django.db import models class Migration(migrations.Migration): + initial = True + dependencies = [ - ("documents", "1032_alter_correspondent_matching_algorithm_and_more"), + ("documents", "0001_initial"), + ("paperless_mail", "0001_initial"), ] operations = [ - migrations.AlterModelOptions( - name="documenttype", - options={ - "ordering": ("name",), - "verbose_name": "document type", - "verbose_name_plural": "document types", - }, + migrations.AddField( + model_name="workflowtrigger", + name="filter_mailrule", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="paperless_mail.mailrule", + verbose_name="filter documents from this mail rule", + ), ), - migrations.AlterModelOptions( - name="tag", - options={ - "ordering": ("name",), - "verbose_name": "tag", - "verbose_name_plural": "tags", - }, + migrations.AddField( + model_name="workflowtrigger", + name="schedule_date_custom_field", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="documents.customfield", + verbose_name="schedule date custom field", + ), ), - migrations.AlterField( - model_name="correspondent", - name="name", - field=models.CharField(max_length=128, verbose_name="name"), - ), - migrations.AlterField( - model_name="documenttype", - name="name", - field=models.CharField(max_length=128, verbose_name="name"), - ), - migrations.AlterField( - model_name="storagepath", - name="name", - field=models.CharField(max_length=128, verbose_name="name"), - ), - migrations.AlterField( - model_name="tag", - name="name", - field=models.CharField(max_length=128, verbose_name="name"), + migrations.AddField( + model_name="workflow", + name="triggers", + field=models.ManyToManyField( + related_name="workflows", + to="documents.workflowtrigger", + verbose_name="triggers", + ), ), migrations.AddConstraint( model_name="correspondent", @@ -61,6 +60,13 @@ class Migration(migrations.Migration): name="documents_correspondent_name_uniq", ), ), + migrations.AddConstraint( + model_name="customfieldinstance", + constraint=models.UniqueConstraint( + fields=("document", "field"), + name="documents_customfieldinstance_unique_document_field", + ), + ), migrations.AddConstraint( model_name="documenttype", constraint=models.UniqueConstraint( diff --git a/src/documents/migrations/0003_sender.py b/src/documents/migrations/0003_sender.py deleted file mode 100644 index dd194afdb..000000000 --- a/src/documents/migrations/0003_sender.py +++ /dev/null @@ -1,70 +0,0 @@ -# Generated by Django 1.9 on 2016-01-11 12:21 - -import django.db.models.deletion -from django.db import migrations -from django.db import models -from django.template.defaultfilters import slugify - -DOCUMENT_SENDER_MAP = {} - - -def move_sender_strings_to_sender_model(apps, schema_editor): - sender_model = apps.get_model("documents", "Sender") - document_model = apps.get_model("documents", "Document") - - # Create the sender and log the relationship with the document - for document in document_model.objects.all(): - if document.sender: - ( - DOCUMENT_SENDER_MAP[document.pk], - _, - ) = sender_model.objects.get_or_create( - name=document.sender, - defaults={"slug": slugify(document.sender)}, - ) - - -def realign_senders(apps, schema_editor): - document_model = apps.get_model("documents", "Document") - for pk, sender in DOCUMENT_SENDER_MAP.items(): - document_model.objects.filter(pk=pk).update(sender=sender) - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "0002_auto_20151226_1316"), - ] - - operations = [ - migrations.CreateModel( - name="Sender", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("name", models.CharField(max_length=128, unique=True)), - ("slug", models.SlugField()), - ], - ), - migrations.RunPython(move_sender_strings_to_sender_model), - migrations.RemoveField( - model_name="document", - name="sender", - ), - migrations.AddField( - model_name="document", - name="sender", - field=models.ForeignKey( - blank=True, - on_delete=django.db.models.deletion.CASCADE, - to="documents.Sender", - ), - ), - migrations.RunPython(realign_senders), - ] diff --git a/src/documents/migrations/0004_auto_20160114_1844.py b/src/documents/migrations/0004_auto_20160114_1844.py deleted file mode 100644 index 97bda420e..000000000 --- a/src/documents/migrations/0004_auto_20160114_1844.py +++ /dev/null @@ -1,25 +0,0 @@ -# Generated by Django 1.9 on 2016-01-14 18:44 - -import django.db.models.deletion -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "0003_sender"), - ] - - operations = [ - migrations.AlterField( - model_name="document", - name="sender", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.CASCADE, - related_name="documents", - to="documents.Sender", - ), - ), - ] diff --git a/src/documents/migrations/0004_auto_20160114_1844_squashed_0011_auto_20160303_1929.py b/src/documents/migrations/0004_auto_20160114_1844_squashed_0011_auto_20160303_1929.py deleted file mode 100644 index 8d86cbbc1..000000000 --- a/src/documents/migrations/0004_auto_20160114_1844_squashed_0011_auto_20160303_1929.py +++ /dev/null @@ -1,178 +0,0 @@ -# Generated by Django 4.2.13 on 2024-06-28 17:52 - -import django.db.models.deletion -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - replaces = [ - ("documents", "0004_auto_20160114_1844"), - ("documents", "0005_auto_20160123_0313"), - ("documents", "0006_auto_20160123_0430"), - ("documents", "0007_auto_20160126_2114"), - ("documents", "0008_document_file_type"), - ("documents", "0009_auto_20160214_0040"), - ("documents", "0010_log"), - ("documents", "0011_auto_20160303_1929"), - ] - - dependencies = [ - ("documents", "0003_sender"), - ] - - operations = [ - migrations.AlterField( - model_name="document", - name="sender", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.CASCADE, - related_name="documents", - to="documents.sender", - ), - ), - migrations.AlterModelOptions( - name="sender", - options={"ordering": ("name",)}, - ), - migrations.CreateModel( - name="Tag", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("name", models.CharField(max_length=128, unique=True)), - ("slug", models.SlugField(blank=True)), - ( - "colour", - models.PositiveIntegerField( - choices=[ - (1, "#a6cee3"), - (2, "#1f78b4"), - (3, "#b2df8a"), - (4, "#33a02c"), - (5, "#fb9a99"), - (6, "#e31a1c"), - (7, "#fdbf6f"), - (8, "#ff7f00"), - (9, "#cab2d6"), - (10, "#6a3d9a"), - (11, "#b15928"), - (12, "#000000"), - (13, "#cccccc"), - ], - default=1, - ), - ), - ("match", models.CharField(blank=True, max_length=256)), - ( - "matching_algorithm", - models.PositiveIntegerField( - choices=[ - (1, "Any"), - (2, "All"), - (3, "Literal"), - (4, "Regular Expression"), - ], - default=1, - help_text='Which algorithm you want to use when matching text to the OCR\'d PDF. Here, "any" looks for any occurrence of any word provided in the PDF, while "all" requires that every word provided appear in the PDF, albeit not in the order provided. A "literal" match means that the text you enter must appear in the PDF exactly as you\'ve entered it, and "regular expression" uses a regex to match the PDF. If you don\'t know what a regex is, you probably don\'t want this option.', - ), - ), - ], - options={ - "abstract": False, - }, - ), - migrations.AlterField( - model_name="sender", - name="slug", - field=models.SlugField(blank=True), - ), - migrations.AddField( - model_name="document", - name="file_type", - field=models.CharField( - choices=[ - ("pdf", "PDF"), - ("png", "PNG"), - ("jpg", "JPG"), - ("gif", "GIF"), - ("tiff", "TIFF"), - ], - default="pdf", - editable=False, - max_length=4, - ), - preserve_default=False, - ), - migrations.AddField( - model_name="document", - name="tags", - field=models.ManyToManyField( - blank=True, - related_name="documents", - to="documents.tag", - ), - ), - migrations.CreateModel( - name="Log", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("group", models.UUIDField(blank=True)), - ("message", models.TextField()), - ( - "level", - models.PositiveIntegerField( - choices=[ - (10, "Debugging"), - (20, "Informational"), - (30, "Warning"), - (40, "Error"), - (50, "Critical"), - ], - default=20, - ), - ), - ( - "component", - models.PositiveIntegerField( - choices=[(1, "Consumer"), (2, "Mail Fetcher")], - ), - ), - ("created", models.DateTimeField(auto_now_add=True)), - ("modified", models.DateTimeField(auto_now=True)), - ], - options={ - "ordering": ("-modified",), - }, - ), - migrations.RenameModel( - 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", - ), - ] diff --git a/src/documents/migrations/0005_auto_20160123_0313.py b/src/documents/migrations/0005_auto_20160123_0313.py deleted file mode 100644 index b0ccc5825..000000000 --- a/src/documents/migrations/0005_auto_20160123_0313.py +++ /dev/null @@ -1,16 +0,0 @@ -# Generated by Django 1.9 on 2016-01-23 03:13 - -from django.db import migrations - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "0004_auto_20160114_1844"), - ] - - operations = [ - migrations.AlterModelOptions( - name="sender", - options={"ordering": ("name",)}, - ), - ] diff --git a/src/documents/migrations/0006_auto_20160123_0430.py b/src/documents/migrations/0006_auto_20160123_0430.py deleted file mode 100644 index 315b9646f..000000000 --- a/src/documents/migrations/0006_auto_20160123_0430.py +++ /dev/null @@ -1,64 +0,0 @@ -# Generated by Django 1.9 on 2016-01-23 04:30 - -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "0005_auto_20160123_0313"), - ] - - operations = [ - migrations.CreateModel( - name="Tag", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("name", models.CharField(max_length=128, unique=True)), - ("slug", models.SlugField(blank=True)), - ( - "colour", - models.PositiveIntegerField( - choices=[ - (1, "#a6cee3"), - (2, "#1f78b4"), - (3, "#b2df8a"), - (4, "#33a02c"), - (5, "#fb9a99"), - (6, "#e31a1c"), - (7, "#fdbf6f"), - (8, "#ff7f00"), - (9, "#cab2d6"), - (10, "#6a3d9a"), - (11, "#ffff99"), - (12, "#b15928"), - (13, "#000000"), - (14, "#cccccc"), - ], - default=1, - ), - ), - ], - options={ - "abstract": False, - }, - ), - migrations.AlterField( - model_name="sender", - name="slug", - field=models.SlugField(blank=True), - ), - migrations.AddField( - model_name="document", - name="tags", - field=models.ManyToManyField(related_name="documents", to="documents.Tag"), - ), - ] diff --git a/src/documents/migrations/0007_auto_20160126_2114.py b/src/documents/migrations/0007_auto_20160126_2114.py deleted file mode 100644 index 04ccc0589..000000000 --- a/src/documents/migrations/0007_auto_20160126_2114.py +++ /dev/null @@ -1,55 +0,0 @@ -# Generated by Django 1.9 on 2016-01-26 21:14 - -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "0006_auto_20160123_0430"), - ] - - operations = [ - migrations.AddField( - model_name="tag", - name="match", - field=models.CharField(blank=True, max_length=256), - ), - migrations.AddField( - model_name="tag", - name="matching_algorithm", - field=models.PositiveIntegerField( - blank=True, - choices=[ - (1, "Any"), - (2, "All"), - (3, "Literal"), - (4, "Regular Expression"), - ], - help_text='Which algorithm you want to use when matching text to the OCR\'d PDF. Here, "any" looks for any occurrence of any word provided in the PDF, while "all" requires that every word provided appear in the PDF, albeit not in the order provided. A "literal" match means that the text you enter must appear in the PDF exactly as you\'ve entered it, and "regular expression" uses a regex to match the PDF. If you don\'t know what a regex is, you probably don\'t want this option.', - null=True, - ), - ), - migrations.AlterField( - model_name="tag", - name="colour", - field=models.PositiveIntegerField( - choices=[ - (1, "#a6cee3"), - (2, "#1f78b4"), - (3, "#b2df8a"), - (4, "#33a02c"), - (5, "#fb9a99"), - (6, "#e31a1c"), - (7, "#fdbf6f"), - (8, "#ff7f00"), - (9, "#cab2d6"), - (10, "#6a3d9a"), - (11, "#b15928"), - (12, "#000000"), - (13, "#cccccc"), - ], - default=1, - ), - ), - ] diff --git a/src/documents/migrations/0008_document_file_type.py b/src/documents/migrations/0008_document_file_type.py deleted file mode 100644 index 7787f8622..000000000 --- a/src/documents/migrations/0008_document_file_type.py +++ /dev/null @@ -1,39 +0,0 @@ -# Generated by Django 1.9 on 2016-01-29 22:58 - -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "0007_auto_20160126_2114"), - ] - - operations = [ - migrations.AddField( - model_name="document", - name="file_type", - field=models.CharField( - choices=[ - ("pdf", "PDF"), - ("png", "PNG"), - ("jpg", "JPG"), - ("gif", "GIF"), - ("tiff", "TIFF"), - ], - default="pdf", - editable=False, - max_length=4, - ), - preserve_default=False, - ), - migrations.AlterField( - model_name="document", - name="tags", - field=models.ManyToManyField( - blank=True, - related_name="documents", - to="documents.Tag", - ), - ), - ] diff --git a/src/documents/migrations/0009_auto_20160214_0040.py b/src/documents/migrations/0009_auto_20160214_0040.py deleted file mode 100644 index 5c95e1035..000000000 --- a/src/documents/migrations/0009_auto_20160214_0040.py +++ /dev/null @@ -1,27 +0,0 @@ -# Generated by Django 1.9 on 2016-02-14 00:40 - -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "0008_document_file_type"), - ] - - operations = [ - migrations.AlterField( - model_name="tag", - name="matching_algorithm", - field=models.PositiveIntegerField( - choices=[ - (1, "Any"), - (2, "All"), - (3, "Literal"), - (4, "Regular Expression"), - ], - default=1, - help_text='Which algorithm you want to use when matching text to the OCR\'d PDF. Here, "any" looks for any occurrence of any word provided in the PDF, while "all" requires that every word provided appear in the PDF, albeit not in the order provided. A "literal" match means that the text you enter must appear in the PDF exactly as you\'ve entered it, and "regular expression" uses a regex to match the PDF. If you don\'t know what a regex is, you probably don\'t want this option.', - ), - ), - ] diff --git a/src/documents/migrations/0010_log.py b/src/documents/migrations/0010_log.py deleted file mode 100644 index 8f015cab0..000000000 --- a/src/documents/migrations/0010_log.py +++ /dev/null @@ -1,53 +0,0 @@ -# Generated by Django 1.9 on 2016-02-27 17:54 - -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "0009_auto_20160214_0040"), - ] - - operations = [ - migrations.CreateModel( - name="Log", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("group", models.UUIDField(blank=True)), - ("message", models.TextField()), - ( - "level", - models.PositiveIntegerField( - choices=[ - (10, "Debugging"), - (20, "Informational"), - (30, "Warning"), - (40, "Error"), - (50, "Critical"), - ], - default=20, - ), - ), - ( - "component", - models.PositiveIntegerField( - choices=[(1, "Consumer"), (2, "Mail Fetcher")], - ), - ), - ("created", models.DateTimeField(auto_now_add=True)), - ("modified", models.DateTimeField(auto_now=True)), - ], - options={ - "ordering": ("-modified",), - }, - ), - ] diff --git a/src/documents/migrations/0011_auto_20160303_1929.py b/src/documents/migrations/0011_auto_20160303_1929.py deleted file mode 100644 index 4c4fcd3ad..000000000 --- a/src/documents/migrations/0011_auto_20160303_1929.py +++ /dev/null @@ -1,26 +0,0 @@ -# Generated by Django 1.9.2 on 2016-03-03 19:29 - -from django.db import migrations - - -class Migration(migrations.Migration): - atomic = False - dependencies = [ - ("documents", "0010_log"), - ] - - operations = [ - migrations.RenameModel( - 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", - ), - ] diff --git a/src/documents/migrations/0012_auto_20160305_0040.py b/src/documents/migrations/0012_auto_20160305_0040.py deleted file mode 100644 index 097661137..000000000 --- a/src/documents/migrations/0012_auto_20160305_0040.py +++ /dev/null @@ -1,128 +0,0 @@ -# Generated by Django 1.9.2 on 2016-03-05 00:40 - -import os -import re -import shutil -import subprocess -import tempfile -from pathlib import Path - -import gnupg -from django.conf import settings -from django.db import migrations -from django.utils.termcolors import colorize as colourise # Spelling hurts me - - -class GnuPG: - """ - A handy singleton to use when handling encrypted files. - """ - - gpg = gnupg.GPG(gnupghome=settings.GNUPG_HOME) - - @classmethod - def decrypted(cls, file_handle): - return cls.gpg.decrypt_file(file_handle, passphrase=settings.PASSPHRASE).data - - @classmethod - def encrypted(cls, file_handle): - return cls.gpg.encrypt_file( - file_handle, - recipients=None, - passphrase=settings.PASSPHRASE, - symmetric=True, - ).data - - -def move_documents_and_create_thumbnails(apps, schema_editor): - (Path(settings.MEDIA_ROOT) / "documents" / "originals").mkdir( - parents=True, - exist_ok=True, - ) - (Path(settings.MEDIA_ROOT) / "documents" / "thumbnails").mkdir( - parents=True, - exist_ok=True, - ) - - documents: list[str] = os.listdir(Path(settings.MEDIA_ROOT) / "documents") # noqa: PTH208 - - if set(documents) == {"originals", "thumbnails"}: - return - - print( - colourise( - "\n\n" - " This is a one-time only migration to generate thumbnails for all of your\n" - " documents so that future UIs will have something to work with. If you have\n" - " a lot of documents though, this may take a while, so a coffee break may be\n" - " in order." - "\n", - opts=("bold",), - ), - ) - - Path(settings.SCRATCH_DIR).mkdir(parents=True, exist_ok=True) - - for f in sorted(documents): - if not f.endswith("gpg"): - continue - - print( - " {} {} {}".format( - colourise("*", fg="green"), - colourise("Generating a thumbnail for", fg="white"), - colourise(f, fg="cyan"), - ), - ) - - thumb_temp: str = tempfile.mkdtemp(prefix="paperless", dir=settings.SCRATCH_DIR) - orig_temp: str = tempfile.mkdtemp(prefix="paperless", dir=settings.SCRATCH_DIR) - - orig_source: Path = Path(settings.MEDIA_ROOT) / "documents" / f - orig_target: Path = Path(orig_temp) / f.replace(".gpg", "") - - with orig_source.open("rb") as encrypted, orig_target.open("wb") as unencrypted: - unencrypted.write(GnuPG.decrypted(encrypted)) - - subprocess.Popen( - ( - settings.CONVERT_BINARY, - "-scale", - "500x5000", - "-alpha", - "remove", - orig_target, - Path(thumb_temp) / "convert-%04d.png", - ), - ).wait() - - thumb_source: Path = Path(thumb_temp) / "convert-0000.png" - thumb_target: Path = ( - Path(settings.MEDIA_ROOT) - / "documents" - / "thumbnails" - / re.sub(r"(\d+)\.\w+(\.gpg)", "\\1.png\\2", f) - ) - with ( - thumb_source.open("rb") as unencrypted, - thumb_target.open("wb") as encrypted, - ): - encrypted.write(GnuPG.encrypted(unencrypted)) - - shutil.rmtree(thumb_temp) - shutil.rmtree(orig_temp) - - shutil.move( - Path(settings.MEDIA_ROOT) / "documents" / f, - Path(settings.MEDIA_ROOT) / "documents" / "originals" / f, - ) - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "0011_auto_20160303_1929"), - ] - - operations = [ - migrations.RunPython(move_documents_and_create_thumbnails), - ] diff --git a/src/documents/migrations/0013_auto_20160325_2111.py b/src/documents/migrations/0013_auto_20160325_2111.py deleted file mode 100644 index 1d3f8b07d..000000000 --- a/src/documents/migrations/0013_auto_20160325_2111.py +++ /dev/null @@ -1,42 +0,0 @@ -# Generated by Django 1.9.4 on 2016-03-25 21:11 - -import django.utils.timezone -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "0012_auto_20160305_0040"), - ] - - operations = [ - migrations.AddField( - model_name="correspondent", - name="match", - field=models.CharField(blank=True, max_length=256), - ), - migrations.AddField( - model_name="correspondent", - name="matching_algorithm", - field=models.PositiveIntegerField( - choices=[ - (1, "Any"), - (2, "All"), - (3, "Literal"), - (4, "Regular Expression"), - ], - default=1, - help_text='Which algorithm you want to use when matching text to the OCR\'d PDF. Here, "any" looks for any occurrence of any word provided in the PDF, while "all" requires that every word provided appear in the PDF, albeit not in the order provided. A "literal" match means that the text you enter must appear in the PDF exactly as you\'ve entered it, and "regular expression" uses a regex to match the PDF. If you don\'t know what a regex is, you probably don\'t want this option.', - ), - ), - migrations.AlterField( - model_name="document", - name="created", - field=models.DateTimeField(default=django.utils.timezone.now), - ), - migrations.RemoveField( - model_name="log", - name="component", - ), - ] diff --git a/src/documents/migrations/0014_document_checksum.py b/src/documents/migrations/0014_document_checksum.py deleted file mode 100644 index de256ced7..000000000 --- a/src/documents/migrations/0014_document_checksum.py +++ /dev/null @@ -1,182 +0,0 @@ -# Generated by Django 1.9.4 on 2016-03-28 19:09 - -import hashlib -from pathlib import Path - -import django.utils.timezone -import gnupg -from django.conf import settings -from django.db import migrations -from django.db import models -from django.template.defaultfilters import slugify -from django.utils.termcolors import colorize as colourise # Spelling hurts me - - -class GnuPG: - """ - A handy singleton to use when handling encrypted files. - """ - - gpg = gnupg.GPG(gnupghome=settings.GNUPG_HOME) - - @classmethod - def decrypted(cls, file_handle): - return cls.gpg.decrypt_file(file_handle, passphrase=settings.PASSPHRASE).data - - @classmethod - def encrypted(cls, file_handle): - return cls.gpg.encrypt_file( - file_handle, - recipients=None, - passphrase=settings.PASSPHRASE, - symmetric=True, - ).data - - -class Document: - """ - Django's migrations restrict access to model methods, so this is a snapshot - of the methods that existed at the time this migration was written, since - we need to make use of a lot of these shortcuts here. - """ - - def __init__(self, doc): - self.pk = doc.pk - self.correspondent = doc.correspondent - self.title = doc.title - self.file_type = doc.file_type - self.tags = doc.tags - self.created = doc.created - - def __str__(self): - created = self.created.strftime("%Y%m%d%H%M%S") - if self.correspondent and self.title: - return f"{created}: {self.correspondent} - {self.title}" - if self.correspondent or self.title: - return f"{created}: {self.correspondent or self.title}" - return str(created) - - @property - def source_path(self): - return ( - Path(settings.MEDIA_ROOT) - / "documents" - / "originals" - / f"{self.pk:07}.{self.file_type}.gpg" - ) - - @property - def source_file(self): - return self.source_path.open("rb") - - @property - def file_name(self): - return slugify(str(self)) + "." + self.file_type - - -def set_checksums(apps, schema_editor): - document_model = apps.get_model("documents", "Document") - - if not document_model.objects.all().exists(): - return - - print( - colourise( - "\n\n" - " This is a one-time only migration to generate checksums for all\n" - " of your existing documents. If you have a lot of documents\n" - " though, this may take a while, so a coffee break may be in\n" - " order." - "\n", - opts=("bold",), - ), - ) - - sums = {} - for d in document_model.objects.all(): - document = Document(d) - - print( - " {} {} {}".format( - colourise("*", fg="green"), - colourise("Generating a checksum for", fg="white"), - colourise(document.file_name, fg="cyan"), - ), - ) - - with document.source_file as encrypted: - checksum = hashlib.md5(GnuPG.decrypted(encrypted)).hexdigest() - - if checksum in sums: - error = "\n{line}{p1}\n\n{doc1}\n{doc2}\n\n{p2}\n\n{code}\n\n{p3}{line}".format( - p1=colourise( - "It appears that you have two identical documents in your collection and \nPaperless no longer supports this (see issue #97). The documents in question\nare:", - fg="yellow", - ), - p2=colourise( - "To fix this problem, you'll have to remove one of them from the database, a task\nmost easily done by running the following command in the same\ndirectory as manage.py:", - fg="yellow", - ), - p3=colourise( - "When that's finished, re-run the migrate, and provided that there aren't any\nother duplicates, you should be good to go.", - fg="yellow", - ), - doc1=colourise( - f" * {sums[checksum][1]} (id: {sums[checksum][0]})", - fg="red", - ), - doc2=colourise( - f" * {document.file_name} (id: {document.pk})", - fg="red", - ), - code=colourise( - f" $ echo 'DELETE FROM documents_document WHERE id = {document.pk};' | ./manage.py dbshell", - fg="green", - ), - line=colourise("\n{}\n".format("=" * 80), fg="white", opts=("bold",)), - ) - raise RuntimeError(error) - sums[checksum] = (document.pk, document.file_name) - - document_model.objects.filter(pk=document.pk).update(checksum=checksum) - - -def do_nothing(apps, schema_editor): - pass - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "0013_auto_20160325_2111"), - ] - - operations = [ - migrations.AddField( - model_name="document", - name="checksum", - field=models.CharField( - default="-", - db_index=True, - editable=False, - max_length=32, - help_text="The checksum of the original document (before it " - "was encrypted). We use this to prevent duplicate " - "document imports.", - ), - preserve_default=False, - ), - migrations.RunPython(set_checksums, do_nothing), - migrations.AlterField( - model_name="document", - name="created", - field=models.DateTimeField( - db_index=True, - default=django.utils.timezone.now, - ), - ), - migrations.AlterField( - model_name="document", - name="modified", - field=models.DateTimeField(auto_now=True, db_index=True), - ), - ] diff --git a/src/documents/migrations/0015_add_insensitive_to_match.py b/src/documents/migrations/0015_add_insensitive_to_match.py deleted file mode 100644 index 0761cc929..000000000 --- a/src/documents/migrations/0015_add_insensitive_to_match.py +++ /dev/null @@ -1,33 +0,0 @@ -# Generated by Django 1.10.2 on 2016-10-05 21:38 - -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "0014_document_checksum"), - ] - - operations = [ - migrations.AlterField( - model_name="document", - name="checksum", - field=models.CharField( - editable=False, - help_text="The checksum of the original document (before it was encrypted). We use this to prevent duplicate document imports.", - max_length=32, - unique=True, - ), - ), - migrations.AddField( - model_name="correspondent", - name="is_insensitive", - field=models.BooleanField(default=True), - ), - migrations.AddField( - model_name="tag", - name="is_insensitive", - field=models.BooleanField(default=True), - ), - ] diff --git a/src/documents/migrations/0015_add_insensitive_to_match_squashed_0018_auto_20170715_1712.py b/src/documents/migrations/0015_add_insensitive_to_match_squashed_0018_auto_20170715_1712.py deleted file mode 100644 index bfa897601..000000000 --- a/src/documents/migrations/0015_add_insensitive_to_match_squashed_0018_auto_20170715_1712.py +++ /dev/null @@ -1,92 +0,0 @@ -# Generated by Django 4.2.13 on 2024-06-28 17:57 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - replaces = [ - ("documents", "0015_add_insensitive_to_match"), - ("documents", "0016_auto_20170325_1558"), - ("documents", "0017_auto_20170512_0507"), - ("documents", "0018_auto_20170715_1712"), - ] - - dependencies = [ - ("documents", "0014_document_checksum"), - ] - - operations = [ - migrations.AlterField( - model_name="document", - name="checksum", - field=models.CharField( - editable=False, - help_text="The checksum of the original document (before it was encrypted). We use this to prevent duplicate document imports.", - max_length=32, - unique=True, - ), - ), - migrations.AddField( - model_name="correspondent", - name="is_insensitive", - field=models.BooleanField(default=True), - ), - migrations.AddField( - model_name="tag", - name="is_insensitive", - field=models.BooleanField(default=True), - ), - migrations.AlterField( - model_name="document", - name="content", - field=models.TextField( - blank=True, - db_index=("mysql" not in settings.DATABASES["default"]["ENGINE"]), - help_text="The raw, text-only data of the document. This field is primarily used for searching.", - ), - ), - migrations.AlterField( - model_name="correspondent", - name="matching_algorithm", - field=models.PositiveIntegerField( - choices=[ - (1, "Any"), - (2, "All"), - (3, "Literal"), - (4, "Regular Expression"), - (5, "Fuzzy Match"), - ], - default=1, - help_text='Which algorithm you want to use when matching text to the OCR\'d PDF. Here, "any" looks for any occurrence of any word provided in the PDF, while "all" requires that every word provided appear in the PDF, albeit not in the order provided. A "literal" match means that the text you enter must appear in the PDF exactly as you\'ve entered it, and "regular expression" uses a regex to match the PDF. (If you don\'t know what a regex is, you probably don\'t want this option.) Finally, a "fuzzy match" looks for words or phrases that are mostly—but not exactly—the same, which can be useful for matching against documents containing imperfections that foil accurate OCR.', - ), - ), - migrations.AlterField( - model_name="tag", - name="matching_algorithm", - field=models.PositiveIntegerField( - choices=[ - (1, "Any"), - (2, "All"), - (3, "Literal"), - (4, "Regular Expression"), - (5, "Fuzzy Match"), - ], - default=1, - help_text='Which algorithm you want to use when matching text to the OCR\'d PDF. Here, "any" looks for any occurrence of any word provided in the PDF, while "all" requires that every word provided appear in the PDF, albeit not in the order provided. A "literal" match means that the text you enter must appear in the PDF exactly as you\'ve entered it, and "regular expression" uses a regex to match the PDF. (If you don\'t know what a regex is, you probably don\'t want this option.) Finally, a "fuzzy match" looks for words or phrases that are mostly—but not exactly—the same, which can be useful for matching against documents containing imperfections that foil accurate OCR.', - ), - ), - migrations.AlterField( - model_name="document", - name="correspondent", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="documents", - to="documents.correspondent", - ), - ), - ] diff --git a/src/documents/migrations/0016_auto_20170325_1558.py b/src/documents/migrations/0016_auto_20170325_1558.py deleted file mode 100644 index 743097d0c..000000000 --- a/src/documents/migrations/0016_auto_20170325_1558.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 1.10.5 on 2017-03-25 15:58 - -from django.conf import settings -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "0015_add_insensitive_to_match"), - ] - - operations = [ - migrations.AlterField( - model_name="document", - name="content", - field=models.TextField( - blank=True, - db_index=("mysql" not in settings.DATABASES["default"]["ENGINE"]), - help_text="The raw, text-only data of the document. This field is primarily used for searching.", - ), - ), - ] diff --git a/src/documents/migrations/0017_auto_20170512_0507.py b/src/documents/migrations/0017_auto_20170512_0507.py deleted file mode 100644 index b9477a06c..000000000 --- a/src/documents/migrations/0017_auto_20170512_0507.py +++ /dev/null @@ -1,43 +0,0 @@ -# Generated by Django 1.10.5 on 2017-05-12 05:07 - -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "0016_auto_20170325_1558"), - ] - - operations = [ - migrations.AlterField( - model_name="correspondent", - name="matching_algorithm", - field=models.PositiveIntegerField( - choices=[ - (1, "Any"), - (2, "All"), - (3, "Literal"), - (4, "Regular Expression"), - (5, "Fuzzy Match"), - ], - default=1, - help_text='Which algorithm you want to use when matching text to the OCR\'d PDF. Here, "any" looks for any occurrence of any word provided in the PDF, while "all" requires that every word provided appear in the PDF, albeit not in the order provided. A "literal" match means that the text you enter must appear in the PDF exactly as you\'ve entered it, and "regular expression" uses a regex to match the PDF. (If you don\'t know what a regex is, you probably don\'t want this option.) Finally, a "fuzzy match" looks for words or phrases that are mostly—but not exactly—the same, which can be useful for matching against documents containing imperfections that foil accurate OCR.', - ), - ), - migrations.AlterField( - model_name="tag", - name="matching_algorithm", - field=models.PositiveIntegerField( - choices=[ - (1, "Any"), - (2, "All"), - (3, "Literal"), - (4, "Regular Expression"), - (5, "Fuzzy Match"), - ], - default=1, - help_text='Which algorithm you want to use when matching text to the OCR\'d PDF. Here, "any" looks for any occurrence of any word provided in the PDF, while "all" requires that every word provided appear in the PDF, albeit not in the order provided. A "literal" match means that the text you enter must appear in the PDF exactly as you\'ve entered it, and "regular expression" uses a regex to match the PDF. (If you don\'t know what a regex is, you probably don\'t want this option.) Finally, a "fuzzy match" looks for words or phrases that are mostly—but not exactly—the same, which can be useful for matching against documents containing imperfections that foil accurate OCR.', - ), - ), - ] diff --git a/src/documents/migrations/0018_auto_20170715_1712.py b/src/documents/migrations/0018_auto_20170715_1712.py deleted file mode 100644 index 838d79ddc..000000000 --- a/src/documents/migrations/0018_auto_20170715_1712.py +++ /dev/null @@ -1,25 +0,0 @@ -# Generated by Django 1.10.5 on 2017-07-15 17:12 - -import django.db.models.deletion -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "0017_auto_20170512_0507"), - ] - - operations = [ - migrations.AlterField( - model_name="document", - name="correspondent", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="documents", - to="documents.Correspondent", - ), - ), - ] diff --git a/src/documents/migrations/0019_add_consumer_user.py b/src/documents/migrations/0019_add_consumer_user.py deleted file mode 100644 index b38d88538..000000000 --- a/src/documents/migrations/0019_add_consumer_user.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 1.10.5 on 2017-07-15 17:12 - -from django.contrib.auth.models import User -from django.db import migrations - - -def forwards_func(apps, schema_editor): - User.objects.create(username="consumer") - - -def reverse_func(apps, schema_editor): - User.objects.get(username="consumer").delete() - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "0018_auto_20170715_1712"), - ] - - operations = [ - migrations.RunPython(forwards_func, reverse_func), - ] diff --git a/src/documents/migrations/0020_document_added.py b/src/documents/migrations/0020_document_added.py deleted file mode 100644 index 34042eedf..000000000 --- a/src/documents/migrations/0020_document_added.py +++ /dev/null @@ -1,29 +0,0 @@ -import django.utils.timezone -from django.db import migrations -from django.db import models - - -def set_added_time_to_created_time(apps, schema_editor): - Document = apps.get_model("documents", "Document") - for doc in Document.objects.all(): - doc.added = doc.created - doc.save() - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "0019_add_consumer_user"), - ] - - operations = [ - migrations.AddField( - model_name="document", - name="added", - field=models.DateTimeField( - db_index=True, - default=django.utils.timezone.now, - editable=False, - ), - ), - migrations.RunPython(set_added_time_to_created_time), - ] diff --git a/src/documents/migrations/0021_document_storage_type.py b/src/documents/migrations/0021_document_storage_type.py deleted file mode 100644 index b35fe75ed..000000000 --- a/src/documents/migrations/0021_document_storage_type.py +++ /dev/null @@ -1,41 +0,0 @@ -# Generated by Django 1.11.10 on 2018-02-04 13:07 - -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "0020_document_added"), - ] - - operations = [ - # Add the field with the default GPG-encrypted value - migrations.AddField( - model_name="document", - name="storage_type", - field=models.CharField( - choices=[ - ("unencrypted", "Unencrypted"), - ("gpg", "Encrypted with GNU Privacy Guard"), - ], - default="gpg", - editable=False, - max_length=11, - ), - ), - # Now that the field is added, change the default to unencrypted - migrations.AlterField( - model_name="document", - name="storage_type", - field=models.CharField( - choices=[ - ("unencrypted", "Unencrypted"), - ("gpg", "Encrypted with GNU Privacy Guard"), - ], - default="unencrypted", - editable=False, - max_length=11, - ), - ), - ] diff --git a/src/documents/migrations/0022_auto_20181007_1420.py b/src/documents/migrations/0022_auto_20181007_1420.py deleted file mode 100644 index 02dfa6d2b..000000000 --- a/src/documents/migrations/0022_auto_20181007_1420.py +++ /dev/null @@ -1,61 +0,0 @@ -# Generated by Django 2.0.8 on 2018-10-07 14:20 - -from django.db import migrations -from django.db import models -from django.utils.text import slugify - - -def re_slug_all_the_things(apps, schema_editor): - """ - Rewrite all slug values to make sure they're actually slugs before we brand - them as uneditable. - """ - - Tag = apps.get_model("documents", "Tag") - Correspondent = apps.get_model("documents", "Correspondent") - - for klass in (Tag, Correspondent): - for instance in klass.objects.all(): - klass.objects.filter(pk=instance.pk).update(slug=slugify(instance.slug)) - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "0021_document_storage_type"), - ] - - operations = [ - migrations.AlterModelOptions( - name="tag", - options={"ordering": ("name",)}, - ), - migrations.AlterField( - model_name="correspondent", - name="slug", - field=models.SlugField(blank=True, editable=False), - ), - migrations.AlterField( - model_name="document", - name="file_type", - field=models.CharField( - choices=[ - ("pdf", "PDF"), - ("png", "PNG"), - ("jpg", "JPG"), - ("gif", "GIF"), - ("tiff", "TIFF"), - ("txt", "TXT"), - ("csv", "CSV"), - ("md", "MD"), - ], - editable=False, - max_length=4, - ), - ), - migrations.AlterField( - model_name="tag", - name="slug", - field=models.SlugField(blank=True, editable=False), - ), - migrations.RunPython(re_slug_all_the_things, migrations.RunPython.noop), - ] diff --git a/src/documents/migrations/0023_document_current_filename.py b/src/documents/migrations/0023_document_current_filename.py deleted file mode 100644 index 5f52e1c89..000000000 --- a/src/documents/migrations/0023_document_current_filename.py +++ /dev/null @@ -1,39 +0,0 @@ -# Generated by Django 2.0.10 on 2019-04-26 18:57 - -from django.db import migrations -from django.db import models - - -def set_filename(apps, schema_editor): - Document = apps.get_model("documents", "Document") - for doc in Document.objects.all(): - file_name = f"{doc.pk:07}.{doc.file_type}" - if doc.storage_type == "gpg": - file_name += ".gpg" - - # Set filename - doc.filename = file_name - - # Save document - doc.save() - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "0022_auto_20181007_1420"), - ] - - operations = [ - migrations.AddField( - model_name="document", - name="filename", - field=models.FilePathField( - default=None, - null=True, - editable=False, - help_text="Current filename in storage", - max_length=256, - ), - ), - migrations.RunPython(set_filename), - ] diff --git a/src/documents/migrations/1000_update_paperless_all.py b/src/documents/migrations/1000_update_paperless_all.py deleted file mode 100644 index ae0d217f6..000000000 --- a/src/documents/migrations/1000_update_paperless_all.py +++ /dev/null @@ -1,147 +0,0 @@ -# Generated by Django 3.1.3 on 2020-11-07 12:35 -import uuid - -import django.db.models.deletion -from django.db import migrations -from django.db import models - - -def logs_set_default_group(apps, schema_editor): - Log = apps.get_model("documents", "Log") - for log in Log.objects.all(): - if log.group is None: - log.group = uuid.uuid4() - log.save() - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "0023_document_current_filename"), - ] - - operations = [ - migrations.AddField( - model_name="document", - name="archive_serial_number", - field=models.IntegerField( - blank=True, - db_index=True, - help_text="The position of this document in your physical document archive.", - null=True, - unique=True, - ), - ), - migrations.AddField( - model_name="tag", - name="is_inbox_tag", - field=models.BooleanField( - default=False, - help_text="Marks this tag as an inbox tag: All newly consumed documents will be tagged with inbox tags.", - ), - ), - migrations.CreateModel( - name="DocumentType", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("name", models.CharField(max_length=128, unique=True)), - ("slug", models.SlugField(blank=True, editable=False)), - ("match", models.CharField(blank=True, max_length=256)), - ( - "matching_algorithm", - models.PositiveIntegerField( - choices=[ - (1, "Any"), - (2, "All"), - (3, "Literal"), - (4, "Regular Expression"), - (5, "Fuzzy Match"), - (6, "Automatic Classification"), - ], - default=1, - help_text='Which algorithm you want to use when matching text to the OCR\'d PDF. Here, "any" looks for any occurrence of any word provided in the PDF, while "all" requires that every word provided appear in the PDF, albeit not in the order provided. A "literal" match means that the text you enter must appear in the PDF exactly as you\'ve entered it, and "regular expression" uses a regex to match the PDF. (If you don\'t know what a regex is, you probably don\'t want this option.) Finally, a "fuzzy match" looks for words or phrases that are mostly—but not exactly—the same, which can be useful for matching against documents containing imperfections that foil accurate OCR.', - ), - ), - ("is_insensitive", models.BooleanField(default=True)), - ], - options={ - "abstract": False, - "ordering": ("name",), - }, - ), - migrations.AddField( - model_name="document", - name="document_type", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="documents", - to="documents.documenttype", - ), - ), - migrations.AlterField( - model_name="correspondent", - name="matching_algorithm", - field=models.PositiveIntegerField( - choices=[ - (1, "Any"), - (2, "All"), - (3, "Literal"), - (4, "Regular Expression"), - (5, "Fuzzy Match"), - (6, "Automatic Classification"), - ], - default=1, - help_text='Which algorithm you want to use when matching text to the OCR\'d PDF. Here, "any" looks for any occurrence of any word provided in the PDF, while "all" requires that every word provided appear in the PDF, albeit not in the order provided. A "literal" match means that the text you enter must appear in the PDF exactly as you\'ve entered it, and "regular expression" uses a regex to match the PDF. (If you don\'t know what a regex is, you probably don\'t want this option.) Finally, a "fuzzy match" looks for words or phrases that are mostly—but not exactly—the same, which can be useful for matching against documents containing imperfections that foil accurate OCR.', - ), - ), - migrations.AlterField( - model_name="tag", - name="matching_algorithm", - field=models.PositiveIntegerField( - choices=[ - (1, "Any"), - (2, "All"), - (3, "Literal"), - (4, "Regular Expression"), - (5, "Fuzzy Match"), - (6, "Automatic Classification"), - ], - default=1, - help_text='Which algorithm you want to use when matching text to the OCR\'d PDF. Here, "any" looks for any occurrence of any word provided in the PDF, while "all" requires that every word provided appear in the PDF, albeit not in the order provided. A "literal" match means that the text you enter must appear in the PDF exactly as you\'ve entered it, and "regular expression" uses a regex to match the PDF. (If you don\'t know what a regex is, you probably don\'t want this option.) Finally, a "fuzzy match" looks for words or phrases that are mostly—but not exactly—the same, which can be useful for matching against documents containing imperfections that foil accurate OCR.', - ), - ), - migrations.AlterField( - model_name="document", - name="content", - field=models.TextField( - blank=True, - help_text="The raw, text-only data of the document. This field is primarily used for searching.", - ), - ), - migrations.AlterModelOptions( - name="log", - options={"ordering": ("-created",)}, - ), - migrations.RemoveField( - model_name="log", - name="modified", - ), - migrations.AlterField( - model_name="log", - name="group", - field=models.UUIDField(blank=True, null=True), - ), - migrations.RunPython( - code=django.db.migrations.operations.special.RunPython.noop, - reverse_code=logs_set_default_group, - ), - ] diff --git a/src/documents/migrations/1001_auto_20201109_1636.py b/src/documents/migrations/1001_auto_20201109_1636.py deleted file mode 100644 index 7477a118c..000000000 --- a/src/documents/migrations/1001_auto_20201109_1636.py +++ /dev/null @@ -1,13 +0,0 @@ -# Generated by Django 3.1.3 on 2020-11-09 16:36 - -from django.db import migrations - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1000_update_paperless_all"), - ] - - operations = [ - migrations.RunPython(migrations.RunPython.noop, migrations.RunPython.noop), - ] diff --git a/src/documents/migrations/1002_auto_20201111_1105.py b/src/documents/migrations/1002_auto_20201111_1105.py deleted file mode 100644 index 1835c4ca9..000000000 --- a/src/documents/migrations/1002_auto_20201111_1105.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by Django 3.1.3 on 2020-11-11 11:05 - -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1001_auto_20201109_1636"), - ] - - operations = [ - migrations.AlterField( - model_name="document", - name="filename", - field=models.FilePathField( - default=None, - editable=False, - help_text="Current filename in storage", - max_length=1024, - null=True, - ), - ), - ] diff --git a/src/documents/migrations/1003_mime_types.py b/src/documents/migrations/1003_mime_types.py deleted file mode 100644 index 4c7ddb492..000000000 --- a/src/documents/migrations/1003_mime_types.py +++ /dev/null @@ -1,92 +0,0 @@ -# Generated by Django 3.1.3 on 2020-11-20 11:21 -from pathlib import Path - -import magic -from django.conf import settings -from django.db import migrations -from django.db import models - -from paperless.db import GnuPG - -STORAGE_TYPE_UNENCRYPTED = "unencrypted" -STORAGE_TYPE_GPG = "gpg" - - -def source_path(self) -> Path: - if self.filename: - fname: str = str(self.filename) - else: - fname = f"{self.pk:07}.{self.file_type}" - if self.storage_type == STORAGE_TYPE_GPG: - fname += ".gpg" - - return Path(settings.ORIGINALS_DIR) / fname - - -def add_mime_types(apps, schema_editor): - Document = apps.get_model("documents", "Document") - documents = Document.objects.all() - - for d in documents: - with Path(source_path(d)).open("rb") as f: - if d.storage_type == STORAGE_TYPE_GPG: - data = GnuPG.decrypted(f) - else: - data = f.read(1024) - - d.mime_type = magic.from_buffer(data, mime=True) - d.save() - - -def add_file_extensions(apps, schema_editor): - Document = apps.get_model("documents", "Document") - documents = Document.objects.all() - - for d in documents: - d.file_type = Path(d.filename).suffix.lstrip(".") - d.save() - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1002_auto_20201111_1105"), - ] - - operations = [ - migrations.AddField( - model_name="document", - name="mime_type", - field=models.CharField(default="-", editable=False, max_length=256), - preserve_default=False, - ), - migrations.RunPython(add_mime_types, migrations.RunPython.noop), - # This operation is here so that we can revert the entire migration: - # By allowing this field to be blank and null, we can revert the - # remove operation further down and the database won't complain about - # NOT NULL violations. - migrations.AlterField( - model_name="document", - name="file_type", - field=models.CharField( - choices=[ - ("pdf", "PDF"), - ("png", "PNG"), - ("jpg", "JPG"), - ("gif", "GIF"), - ("tiff", "TIFF"), - ("txt", "TXT"), - ("csv", "CSV"), - ("md", "MD"), - ], - editable=False, - max_length=4, - null=True, - blank=True, - ), - ), - migrations.RunPython(migrations.RunPython.noop, add_file_extensions), - migrations.RemoveField( - model_name="document", - name="file_type", - ), - ] diff --git a/src/documents/migrations/1004_sanity_check_schedule.py b/src/documents/migrations/1004_sanity_check_schedule.py deleted file mode 100644 index 018cf2492..000000000 --- a/src/documents/migrations/1004_sanity_check_schedule.py +++ /dev/null @@ -1,12 +0,0 @@ -# Generated by Django 3.1.3 on 2020-11-25 14:53 - -from django.db import migrations -from django.db.migrations import RunPython - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1003_mime_types"), - ] - - operations = [RunPython(migrations.RunPython.noop, migrations.RunPython.noop)] diff --git a/src/documents/migrations/1005_checksums.py b/src/documents/migrations/1005_checksums.py deleted file mode 100644 index 4637e06ce..000000000 --- a/src/documents/migrations/1005_checksums.py +++ /dev/null @@ -1,34 +0,0 @@ -# Generated by Django 3.1.3 on 2020-11-29 00:48 - -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1004_sanity_check_schedule"), - ] - - operations = [ - migrations.AddField( - model_name="document", - name="archive_checksum", - field=models.CharField( - blank=True, - editable=False, - help_text="The checksum of the archived document.", - max_length=32, - null=True, - ), - ), - migrations.AlterField( - model_name="document", - name="checksum", - field=models.CharField( - editable=False, - help_text="The checksum of the original document.", - max_length=32, - unique=True, - ), - ), - ] diff --git a/src/documents/migrations/1006_auto_20201208_2209.py b/src/documents/migrations/1006_auto_20201208_2209.py deleted file mode 100644 index 425f0a768..000000000 --- a/src/documents/migrations/1006_auto_20201208_2209.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by Django 3.1.4 on 2020-12-08 22:09 - -from django.db import migrations - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1005_checksums"), - ] - - operations = [ - migrations.RemoveField( - model_name="correspondent", - name="slug", - ), - migrations.RemoveField( - model_name="documenttype", - name="slug", - ), - migrations.RemoveField( - model_name="tag", - name="slug", - ), - ] diff --git a/src/documents/migrations/1006_auto_20201208_2209_squashed_1011_auto_20210101_2340.py b/src/documents/migrations/1006_auto_20201208_2209_squashed_1011_auto_20210101_2340.py deleted file mode 100644 index aa8cb6deb..000000000 --- a/src/documents/migrations/1006_auto_20201208_2209_squashed_1011_auto_20210101_2340.py +++ /dev/null @@ -1,485 +0,0 @@ -# Generated by Django 4.2.13 on 2024-06-28 18:01 - -import django.db.models.deletion -import django.utils.timezone -from django.conf import settings -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - replaces = [ - ("documents", "1006_auto_20201208_2209"), - ("documents", "1007_savedview_savedviewfilterrule"), - ("documents", "1008_auto_20201216_1736"), - ("documents", "1009_auto_20201216_2005"), - ("documents", "1010_auto_20210101_2159"), - ("documents", "1011_auto_20210101_2340"), - ] - - dependencies = [ - ("documents", "1005_checksums"), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.RemoveField( - model_name="correspondent", - name="slug", - ), - migrations.RemoveField( - model_name="documenttype", - name="slug", - ), - migrations.RemoveField( - model_name="tag", - name="slug", - ), - migrations.CreateModel( - name="SavedView", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("name", models.CharField(max_length=128, verbose_name="name")), - ( - "show_on_dashboard", - models.BooleanField(verbose_name="show on dashboard"), - ), - ( - "show_in_sidebar", - models.BooleanField(verbose_name="show in sidebar"), - ), - ( - "sort_field", - models.CharField(max_length=128, verbose_name="sort field"), - ), - ( - "sort_reverse", - models.BooleanField(default=False, verbose_name="sort reverse"), - ), - ( - "user", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to=settings.AUTH_USER_MODEL, - verbose_name="user", - ), - ), - ], - options={ - "ordering": ("name",), - "verbose_name": "saved view", - "verbose_name_plural": "saved views", - }, - ), - migrations.AlterModelOptions( - name="correspondent", - options={ - "ordering": ("name",), - "verbose_name": "correspondent", - "verbose_name_plural": "correspondents", - }, - ), - migrations.AlterModelOptions( - name="document", - options={ - "ordering": ("-created",), - "verbose_name": "document", - "verbose_name_plural": "documents", - }, - ), - migrations.AlterModelOptions( - name="documenttype", - options={ - "verbose_name": "document type", - "verbose_name_plural": "document types", - }, - ), - migrations.AlterModelOptions( - name="log", - options={ - "ordering": ("-created",), - "verbose_name": "log", - "verbose_name_plural": "logs", - }, - ), - migrations.AlterModelOptions( - name="tag", - options={"verbose_name": "tag", "verbose_name_plural": "tags"}, - ), - migrations.AlterField( - model_name="correspondent", - name="is_insensitive", - field=models.BooleanField(default=True, verbose_name="is insensitive"), - ), - migrations.AlterField( - model_name="correspondent", - name="match", - field=models.CharField(blank=True, max_length=256, verbose_name="match"), - ), - migrations.AlterField( - model_name="correspondent", - name="matching_algorithm", - field=models.PositiveIntegerField( - choices=[ - (1, "Any word"), - (2, "All words"), - (3, "Exact match"), - (4, "Regular expression"), - (5, "Fuzzy word"), - (6, "Automatic"), - ], - default=1, - verbose_name="matching algorithm", - ), - ), - migrations.AlterField( - model_name="correspondent", - name="name", - field=models.CharField(max_length=128, unique=True, verbose_name="name"), - ), - migrations.AlterField( - model_name="document", - name="added", - field=models.DateTimeField( - db_index=True, - default=django.utils.timezone.now, - editable=False, - verbose_name="added", - ), - ), - migrations.AlterField( - model_name="document", - name="archive_checksum", - field=models.CharField( - blank=True, - editable=False, - help_text="The checksum of the archived document.", - max_length=32, - null=True, - verbose_name="archive checksum", - ), - ), - migrations.AlterField( - model_name="document", - name="archive_serial_number", - field=models.IntegerField( - blank=True, - db_index=True, - help_text="The position of this document in your physical document archive.", - null=True, - unique=True, - verbose_name="archive serial number", - ), - ), - migrations.AlterField( - model_name="document", - name="checksum", - field=models.CharField( - editable=False, - help_text="The checksum of the original document.", - max_length=32, - unique=True, - verbose_name="checksum", - ), - ), - migrations.AlterField( - model_name="document", - name="content", - field=models.TextField( - blank=True, - help_text="The raw, text-only data of the document. This field is primarily used for searching.", - verbose_name="content", - ), - ), - migrations.AlterField( - model_name="document", - name="correspondent", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="documents", - to="documents.correspondent", - verbose_name="correspondent", - ), - ), - migrations.AlterField( - model_name="document", - name="created", - field=models.DateTimeField( - db_index=True, - default=django.utils.timezone.now, - verbose_name="created", - ), - ), - migrations.AlterField( - model_name="document", - name="document_type", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="documents", - to="documents.documenttype", - verbose_name="document type", - ), - ), - migrations.AlterField( - model_name="document", - name="filename", - field=models.FilePathField( - default=None, - editable=False, - help_text="Current filename in storage", - max_length=1024, - null=True, - verbose_name="filename", - ), - ), - migrations.AlterField( - model_name="document", - name="mime_type", - field=models.CharField( - editable=False, - max_length=256, - verbose_name="mime type", - ), - ), - migrations.AlterField( - model_name="document", - name="modified", - field=models.DateTimeField( - auto_now=True, - db_index=True, - verbose_name="modified", - ), - ), - migrations.AlterField( - model_name="document", - name="storage_type", - field=models.CharField( - choices=[ - ("unencrypted", "Unencrypted"), - ("gpg", "Encrypted with GNU Privacy Guard"), - ], - default="unencrypted", - editable=False, - max_length=11, - verbose_name="storage type", - ), - ), - migrations.AlterField( - model_name="document", - name="tags", - field=models.ManyToManyField( - blank=True, - related_name="documents", - to="documents.tag", - verbose_name="tags", - ), - ), - migrations.AlterField( - model_name="document", - name="title", - field=models.CharField( - blank=True, - db_index=True, - max_length=128, - verbose_name="title", - ), - ), - migrations.AlterField( - model_name="documenttype", - name="is_insensitive", - field=models.BooleanField(default=True, verbose_name="is insensitive"), - ), - migrations.AlterField( - model_name="documenttype", - name="match", - field=models.CharField(blank=True, max_length=256, verbose_name="match"), - ), - migrations.AlterField( - model_name="documenttype", - name="matching_algorithm", - field=models.PositiveIntegerField( - choices=[ - (1, "Any word"), - (2, "All words"), - (3, "Exact match"), - (4, "Regular expression"), - (5, "Fuzzy word"), - (6, "Automatic"), - ], - default=1, - verbose_name="matching algorithm", - ), - ), - migrations.AlterField( - model_name="documenttype", - name="name", - field=models.CharField(max_length=128, unique=True, verbose_name="name"), - ), - migrations.AlterField( - model_name="log", - name="created", - field=models.DateTimeField(auto_now_add=True, verbose_name="created"), - ), - migrations.AlterField( - model_name="log", - name="group", - field=models.UUIDField(blank=True, null=True, verbose_name="group"), - ), - migrations.AlterField( - model_name="log", - name="level", - field=models.PositiveIntegerField( - choices=[ - (10, "debug"), - (20, "information"), - (30, "warning"), - (40, "error"), - (50, "critical"), - ], - default=20, - verbose_name="level", - ), - ), - migrations.AlterField( - model_name="log", - name="message", - field=models.TextField(verbose_name="message"), - ), - migrations.CreateModel( - name="SavedViewFilterRule", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "rule_type", - models.PositiveIntegerField( - choices=[ - (0, "title contains"), - (1, "content contains"), - (2, "ASN is"), - (3, "correspondent is"), - (4, "document type is"), - (5, "is in inbox"), - (6, "has tag"), - (7, "has any tag"), - (8, "created before"), - (9, "created after"), - (10, "created year is"), - (11, "created month is"), - (12, "created day is"), - (13, "added before"), - (14, "added after"), - (15, "modified before"), - (16, "modified after"), - (17, "does not have tag"), - ], - verbose_name="rule type", - ), - ), - ( - "value", - models.CharField( - blank=True, - max_length=128, - null=True, - verbose_name="value", - ), - ), - ( - "saved_view", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="filter_rules", - to="documents.savedview", - verbose_name="saved view", - ), - ), - ], - options={ - "verbose_name": "filter rule", - "verbose_name_plural": "filter rules", - }, - ), - migrations.AlterField( - model_name="tag", - name="colour", - field=models.PositiveIntegerField( - choices=[ - (1, "#a6cee3"), - (2, "#1f78b4"), - (3, "#b2df8a"), - (4, "#33a02c"), - (5, "#fb9a99"), - (6, "#e31a1c"), - (7, "#fdbf6f"), - (8, "#ff7f00"), - (9, "#cab2d6"), - (10, "#6a3d9a"), - (11, "#b15928"), - (12, "#000000"), - (13, "#cccccc"), - ], - default=1, - verbose_name="color", - ), - ), - migrations.AlterField( - model_name="tag", - name="is_inbox_tag", - field=models.BooleanField( - default=False, - help_text="Marks this tag as an inbox tag: All newly consumed documents will be tagged with inbox tags.", - verbose_name="is inbox tag", - ), - ), - migrations.AlterField( - model_name="tag", - name="is_insensitive", - field=models.BooleanField(default=True, verbose_name="is insensitive"), - ), - migrations.AlterField( - model_name="tag", - name="match", - field=models.CharField(blank=True, max_length=256, verbose_name="match"), - ), - migrations.AlterField( - model_name="tag", - name="matching_algorithm", - field=models.PositiveIntegerField( - choices=[ - (1, "Any word"), - (2, "All words"), - (3, "Exact match"), - (4, "Regular expression"), - (5, "Fuzzy word"), - (6, "Automatic"), - ], - default=1, - verbose_name="matching algorithm", - ), - ), - migrations.AlterField( - model_name="tag", - name="name", - field=models.CharField(max_length=128, unique=True, verbose_name="name"), - ), - ] diff --git a/src/documents/migrations/1007_savedview_savedviewfilterrule.py b/src/documents/migrations/1007_savedview_savedviewfilterrule.py deleted file mode 100644 index 64564c6af..000000000 --- a/src/documents/migrations/1007_savedview_savedviewfilterrule.py +++ /dev/null @@ -1,90 +0,0 @@ -# Generated by Django 3.1.4 on 2020-12-12 14:41 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ("documents", "1006_auto_20201208_2209"), - ] - - operations = [ - migrations.CreateModel( - name="SavedView", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("name", models.CharField(max_length=128)), - ("show_on_dashboard", models.BooleanField()), - ("show_in_sidebar", models.BooleanField()), - ("sort_field", models.CharField(max_length=128)), - ("sort_reverse", models.BooleanField(default=False)), - ( - "user", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to=settings.AUTH_USER_MODEL, - ), - ), - ], - ), - migrations.CreateModel( - name="SavedViewFilterRule", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "rule_type", - models.PositiveIntegerField( - choices=[ - (0, "Title contains"), - (1, "Content contains"), - (2, "ASN is"), - (3, "Correspondent is"), - (4, "Document type is"), - (5, "Is in inbox"), - (6, "Has tag"), - (7, "Has any tag"), - (8, "Created before"), - (9, "Created after"), - (10, "Created year is"), - (11, "Created month is"), - (12, "Created day is"), - (13, "Added before"), - (14, "Added after"), - (15, "Modified before"), - (16, "Modified after"), - (17, "Does not have tag"), - ], - ), - ), - ("value", models.CharField(max_length=128)), - ( - "saved_view", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="filter_rules", - to="documents.savedview", - ), - ), - ], - ), - ] diff --git a/src/documents/migrations/1008_auto_20201216_1736.py b/src/documents/migrations/1008_auto_20201216_1736.py deleted file mode 100644 index 76f0343b1..000000000 --- a/src/documents/migrations/1008_auto_20201216_1736.py +++ /dev/null @@ -1,33 +0,0 @@ -# Generated by Django 3.1.4 on 2020-12-16 17:36 - -import django.db.models.functions.text -from django.db import migrations - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1007_savedview_savedviewfilterrule"), - ] - - operations = [ - migrations.AlterModelOptions( - name="correspondent", - options={"ordering": (django.db.models.functions.text.Lower("name"),)}, - ), - migrations.AlterModelOptions( - name="document", - options={"ordering": ("-created",)}, - ), - migrations.AlterModelOptions( - name="documenttype", - options={"ordering": (django.db.models.functions.text.Lower("name"),)}, - ), - migrations.AlterModelOptions( - name="savedview", - options={"ordering": (django.db.models.functions.text.Lower("name"),)}, - ), - migrations.AlterModelOptions( - name="tag", - options={"ordering": (django.db.models.functions.text.Lower("name"),)}, - ), - ] diff --git a/src/documents/migrations/1009_auto_20201216_2005.py b/src/documents/migrations/1009_auto_20201216_2005.py deleted file mode 100644 index 37bae8881..000000000 --- a/src/documents/migrations/1009_auto_20201216_2005.py +++ /dev/null @@ -1,28 +0,0 @@ -# Generated by Django 3.1.4 on 2020-12-16 20:05 - -from django.db import migrations - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1008_auto_20201216_1736"), - ] - - operations = [ - migrations.AlterModelOptions( - name="correspondent", - options={"ordering": ("name",)}, - ), - migrations.AlterModelOptions( - name="documenttype", - options={"ordering": ("name",)}, - ), - migrations.AlterModelOptions( - name="savedview", - options={"ordering": ("name",)}, - ), - migrations.AlterModelOptions( - name="tag", - options={"ordering": ("name",)}, - ), - ] diff --git a/src/documents/migrations/1010_auto_20210101_2159.py b/src/documents/migrations/1010_auto_20210101_2159.py deleted file mode 100644 index 0c6a42d30..000000000 --- a/src/documents/migrations/1010_auto_20210101_2159.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 3.1.4 on 2021-01-01 21:59 - -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1009_auto_20201216_2005"), - ] - - operations = [ - migrations.AlterField( - model_name="savedviewfilterrule", - name="value", - field=models.CharField(blank=True, max_length=128, null=True), - ), - ] diff --git a/src/documents/migrations/1011_auto_20210101_2340.py b/src/documents/migrations/1011_auto_20210101_2340.py deleted file mode 100644 index dea107715..000000000 --- a/src/documents/migrations/1011_auto_20210101_2340.py +++ /dev/null @@ -1,454 +0,0 @@ -# Generated by Django 3.1.4 on 2021-01-01 23:40 - -import django.db.models.deletion -import django.utils.timezone -from django.conf import settings -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ("documents", "1010_auto_20210101_2159"), - ] - - operations = [ - migrations.AlterModelOptions( - name="correspondent", - options={ - "ordering": ("name",), - "verbose_name": "correspondent", - "verbose_name_plural": "correspondents", - }, - ), - migrations.AlterModelOptions( - name="document", - options={ - "ordering": ("-created",), - "verbose_name": "document", - "verbose_name_plural": "documents", - }, - ), - migrations.AlterModelOptions( - name="documenttype", - options={ - "verbose_name": "document type", - "verbose_name_plural": "document types", - }, - ), - migrations.AlterModelOptions( - name="log", - options={ - "ordering": ("-created",), - "verbose_name": "log", - "verbose_name_plural": "logs", - }, - ), - migrations.AlterModelOptions( - name="savedview", - options={ - "ordering": ("name",), - "verbose_name": "saved view", - "verbose_name_plural": "saved views", - }, - ), - migrations.AlterModelOptions( - name="savedviewfilterrule", - options={ - "verbose_name": "filter rule", - "verbose_name_plural": "filter rules", - }, - ), - migrations.AlterModelOptions( - name="tag", - options={"verbose_name": "tag", "verbose_name_plural": "tags"}, - ), - migrations.AlterField( - model_name="correspondent", - name="is_insensitive", - field=models.BooleanField(default=True, verbose_name="is insensitive"), - ), - migrations.AlterField( - model_name="correspondent", - name="match", - field=models.CharField(blank=True, max_length=256, verbose_name="match"), - ), - migrations.AlterField( - model_name="correspondent", - name="matching_algorithm", - field=models.PositiveIntegerField( - choices=[ - (1, "Any word"), - (2, "All words"), - (3, "Exact match"), - (4, "Regular expression"), - (5, "Fuzzy word"), - (6, "Automatic"), - ], - default=1, - verbose_name="matching algorithm", - ), - ), - migrations.AlterField( - model_name="correspondent", - name="name", - field=models.CharField(max_length=128, unique=True, verbose_name="name"), - ), - migrations.AlterField( - model_name="document", - name="added", - field=models.DateTimeField( - db_index=True, - default=django.utils.timezone.now, - editable=False, - verbose_name="added", - ), - ), - migrations.AlterField( - model_name="document", - name="archive_checksum", - field=models.CharField( - blank=True, - editable=False, - help_text="The checksum of the archived document.", - max_length=32, - null=True, - verbose_name="archive checksum", - ), - ), - migrations.AlterField( - model_name="document", - name="archive_serial_number", - field=models.IntegerField( - blank=True, - db_index=True, - help_text="The position of this document in your physical document archive.", - null=True, - unique=True, - verbose_name="archive serial number", - ), - ), - migrations.AlterField( - model_name="document", - name="checksum", - field=models.CharField( - editable=False, - help_text="The checksum of the original document.", - max_length=32, - unique=True, - verbose_name="checksum", - ), - ), - migrations.AlterField( - model_name="document", - name="content", - field=models.TextField( - blank=True, - help_text="The raw, text-only data of the document. This field is primarily used for searching.", - verbose_name="content", - ), - ), - migrations.AlterField( - model_name="document", - name="correspondent", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="documents", - to="documents.correspondent", - verbose_name="correspondent", - ), - ), - migrations.AlterField( - model_name="document", - name="created", - field=models.DateTimeField( - db_index=True, - default=django.utils.timezone.now, - verbose_name="created", - ), - ), - migrations.AlterField( - model_name="document", - name="document_type", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="documents", - to="documents.documenttype", - verbose_name="document type", - ), - ), - migrations.AlterField( - model_name="document", - name="filename", - field=models.FilePathField( - default=None, - editable=False, - help_text="Current filename in storage", - max_length=1024, - null=True, - verbose_name="filename", - ), - ), - migrations.AlterField( - model_name="document", - name="mime_type", - field=models.CharField( - editable=False, - max_length=256, - verbose_name="mime type", - ), - ), - migrations.AlterField( - model_name="document", - name="modified", - field=models.DateTimeField( - auto_now=True, - db_index=True, - verbose_name="modified", - ), - ), - migrations.AlterField( - model_name="document", - name="storage_type", - field=models.CharField( - choices=[ - ("unencrypted", "Unencrypted"), - ("gpg", "Encrypted with GNU Privacy Guard"), - ], - default="unencrypted", - editable=False, - max_length=11, - verbose_name="storage type", - ), - ), - migrations.AlterField( - model_name="document", - name="tags", - field=models.ManyToManyField( - blank=True, - related_name="documents", - to="documents.Tag", - verbose_name="tags", - ), - ), - migrations.AlterField( - model_name="document", - name="title", - field=models.CharField( - blank=True, - db_index=True, - max_length=128, - verbose_name="title", - ), - ), - migrations.AlterField( - model_name="documenttype", - name="is_insensitive", - field=models.BooleanField(default=True, verbose_name="is insensitive"), - ), - migrations.AlterField( - model_name="documenttype", - name="match", - field=models.CharField(blank=True, max_length=256, verbose_name="match"), - ), - migrations.AlterField( - model_name="documenttype", - name="matching_algorithm", - field=models.PositiveIntegerField( - choices=[ - (1, "Any word"), - (2, "All words"), - (3, "Exact match"), - (4, "Regular expression"), - (5, "Fuzzy word"), - (6, "Automatic"), - ], - default=1, - verbose_name="matching algorithm", - ), - ), - migrations.AlterField( - model_name="documenttype", - name="name", - field=models.CharField(max_length=128, unique=True, verbose_name="name"), - ), - migrations.AlterField( - model_name="log", - name="created", - field=models.DateTimeField(auto_now_add=True, verbose_name="created"), - ), - migrations.AlterField( - model_name="log", - name="group", - field=models.UUIDField(blank=True, null=True, verbose_name="group"), - ), - migrations.AlterField( - model_name="log", - name="level", - field=models.PositiveIntegerField( - choices=[ - (10, "debug"), - (20, "information"), - (30, "warning"), - (40, "error"), - (50, "critical"), - ], - default=20, - verbose_name="level", - ), - ), - migrations.AlterField( - model_name="log", - name="message", - field=models.TextField(verbose_name="message"), - ), - migrations.AlterField( - model_name="savedview", - name="name", - field=models.CharField(max_length=128, verbose_name="name"), - ), - migrations.AlterField( - model_name="savedview", - name="show_in_sidebar", - field=models.BooleanField(verbose_name="show in sidebar"), - ), - migrations.AlterField( - model_name="savedview", - name="show_on_dashboard", - field=models.BooleanField(verbose_name="show on dashboard"), - ), - migrations.AlterField( - model_name="savedview", - name="sort_field", - field=models.CharField(max_length=128, verbose_name="sort field"), - ), - migrations.AlterField( - model_name="savedview", - name="sort_reverse", - field=models.BooleanField(default=False, verbose_name="sort reverse"), - ), - migrations.AlterField( - model_name="savedview", - name="user", - field=models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to=settings.AUTH_USER_MODEL, - verbose_name="user", - ), - ), - migrations.AlterField( - model_name="savedviewfilterrule", - name="rule_type", - field=models.PositiveIntegerField( - choices=[ - (0, "title contains"), - (1, "content contains"), - (2, "ASN is"), - (3, "correspondent is"), - (4, "document type is"), - (5, "is in inbox"), - (6, "has tag"), - (7, "has any tag"), - (8, "created before"), - (9, "created after"), - (10, "created year is"), - (11, "created month is"), - (12, "created day is"), - (13, "added before"), - (14, "added after"), - (15, "modified before"), - (16, "modified after"), - (17, "does not have tag"), - ], - verbose_name="rule type", - ), - ), - migrations.AlterField( - model_name="savedviewfilterrule", - name="saved_view", - field=models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="filter_rules", - to="documents.savedview", - verbose_name="saved view", - ), - ), - migrations.AlterField( - model_name="savedviewfilterrule", - name="value", - field=models.CharField( - blank=True, - max_length=128, - null=True, - verbose_name="value", - ), - ), - migrations.AlterField( - model_name="tag", - name="colour", - field=models.PositiveIntegerField( - choices=[ - (1, "#a6cee3"), - (2, "#1f78b4"), - (3, "#b2df8a"), - (4, "#33a02c"), - (5, "#fb9a99"), - (6, "#e31a1c"), - (7, "#fdbf6f"), - (8, "#ff7f00"), - (9, "#cab2d6"), - (10, "#6a3d9a"), - (11, "#b15928"), - (12, "#000000"), - (13, "#cccccc"), - ], - default=1, - verbose_name="color", - ), - ), - migrations.AlterField( - model_name="tag", - name="is_inbox_tag", - field=models.BooleanField( - default=False, - help_text="Marks this tag as an inbox tag: All newly consumed documents will be tagged with inbox tags.", - verbose_name="is inbox tag", - ), - ), - migrations.AlterField( - model_name="tag", - name="is_insensitive", - field=models.BooleanField(default=True, verbose_name="is insensitive"), - ), - migrations.AlterField( - model_name="tag", - name="match", - field=models.CharField(blank=True, max_length=256, verbose_name="match"), - ), - migrations.AlterField( - model_name="tag", - name="matching_algorithm", - field=models.PositiveIntegerField( - choices=[ - (1, "Any word"), - (2, "All words"), - (3, "Exact match"), - (4, "Regular expression"), - (5, "Fuzzy word"), - (6, "Automatic"), - ], - default=1, - verbose_name="matching algorithm", - ), - ), - migrations.AlterField( - model_name="tag", - name="name", - field=models.CharField(max_length=128, unique=True, verbose_name="name"), - ), - ] diff --git a/src/documents/migrations/1012_fix_archive_files.py b/src/documents/migrations/1012_fix_archive_files.py deleted file mode 100644 index a97fa7a80..000000000 --- a/src/documents/migrations/1012_fix_archive_files.py +++ /dev/null @@ -1,367 +0,0 @@ -# Generated by Django 3.1.6 on 2021-02-07 22:26 -import datetime -import hashlib -import logging -import os -import shutil -from collections import defaultdict -from pathlib import Path -from time import sleep - -import pathvalidate -from django.conf import settings -from django.db import migrations -from django.db import models -from django.template.defaultfilters import slugify - -logger = logging.getLogger("paperless.migrations") - - -############################################################################### -# This is code copied straight paperless before the change. -############################################################################### -class defaultdictNoStr(defaultdict): - def __str__(self): # pragma: no cover - raise ValueError("Don't use {tags} directly.") - - -def many_to_dictionary(field): # pragma: no cover - # Converts ManyToManyField to dictionary by assuming, that field - # entries contain an _ or - which will be used as a delimiter - mydictionary = dict() - - for index, t in enumerate(field.all()): - # Populate tag names by index - mydictionary[index] = slugify(t.name) - - # Find delimiter - delimiter = t.name.find("_") - - if delimiter == -1: - delimiter = t.name.find("-") - - if delimiter == -1: - continue - - key = t.name[:delimiter] - value = t.name[delimiter + 1 :] - - mydictionary[slugify(key)] = slugify(value) - - return mydictionary - - -def archive_name_from_filename(filename: Path) -> Path: - return Path(filename.stem + ".pdf") - - -def archive_path_old(doc) -> Path: - if doc.filename: - fname = archive_name_from_filename(Path(doc.filename)) - else: - fname = Path(f"{doc.pk:07}.pdf") - - return settings.ARCHIVE_DIR / fname - - -STORAGE_TYPE_GPG = "gpg" - - -def archive_path_new(doc) -> Path | None: - if doc.archive_filename is not None: - return settings.ARCHIVE_DIR / doc.archive_filename - else: - return None - - -def source_path(doc) -> Path: - if doc.filename: - fname = doc.filename - else: - fname = f"{doc.pk:07}{doc.file_type}" - if doc.storage_type == STORAGE_TYPE_GPG: - fname = Path(str(fname) + ".gpg") # pragma: no cover - - return settings.ORIGINALS_DIR / fname - - -def generate_unique_filename(doc, *, archive_filename=False): - if archive_filename: - old_filename = doc.archive_filename - root = settings.ARCHIVE_DIR - else: - old_filename = doc.filename - root = settings.ORIGINALS_DIR - - counter = 0 - - while True: - new_filename = generate_filename( - doc, - counter=counter, - archive_filename=archive_filename, - ) - if new_filename == old_filename: - # still the same as before. - return new_filename - - if (root / new_filename).exists(): - counter += 1 - else: - return new_filename - - -def generate_filename(doc, *, counter=0, append_gpg=True, archive_filename=False): - path = "" - - try: - if settings.FILENAME_FORMAT is not None: - tags = defaultdictNoStr(lambda: slugify(None), many_to_dictionary(doc.tags)) - - tag_list = pathvalidate.sanitize_filename( - ",".join(sorted([tag.name for tag in doc.tags.all()])), - replacement_text="-", - ) - - if doc.correspondent: - correspondent = pathvalidate.sanitize_filename( - doc.correspondent.name, - replacement_text="-", - ) - else: - correspondent = "none" - - if doc.document_type: - document_type = pathvalidate.sanitize_filename( - doc.document_type.name, - replacement_text="-", - ) - else: - document_type = "none" - - path = settings.FILENAME_FORMAT.format( - title=pathvalidate.sanitize_filename(doc.title, replacement_text="-"), - correspondent=correspondent, - document_type=document_type, - created=datetime.date.isoformat(doc.created), - created_year=doc.created.year if doc.created else "none", - created_month=f"{doc.created.month:02}" if doc.created else "none", - created_day=f"{doc.created.day:02}" if doc.created else "none", - added=datetime.date.isoformat(doc.added), - added_year=doc.added.year if doc.added else "none", - added_month=f"{doc.added.month:02}" if doc.added else "none", - added_day=f"{doc.added.day:02}" if doc.added else "none", - tags=tags, - tag_list=tag_list, - ).strip() - - path = path.strip(os.sep) - - except (ValueError, KeyError, IndexError): - logger.warning( - f"Invalid PAPERLESS_FILENAME_FORMAT: " - f"{settings.FILENAME_FORMAT}, falling back to default", - ) - - counter_str = f"_{counter:02}" if counter else "" - - filetype_str = ".pdf" if archive_filename else doc.file_type - - if len(path) > 0: - filename = f"{path}{counter_str}{filetype_str}" - else: - filename = f"{doc.pk:07}{counter_str}{filetype_str}" - - # Append .gpg for encrypted files - if append_gpg and doc.storage_type == STORAGE_TYPE_GPG: - filename += ".gpg" - - return filename - - -############################################################################### -# This code performs bidirection archive file transformation. -############################################################################### - - -def parse_wrapper(parser, path, mime_type, file_name): - # this is here so that I can mock this out for testing. - parser.parse(path, mime_type, file_name) - - -def create_archive_version(doc, retry_count=3): - from documents.parsers import DocumentParser - from documents.parsers import ParseError - from documents.parsers import get_parser_class_for_mime_type - - logger.info(f"Regenerating archive document for document ID:{doc.id}") - parser_class = get_parser_class_for_mime_type(doc.mime_type) - for try_num in range(retry_count): - parser: DocumentParser = parser_class(None, None) - try: - parse_wrapper( - parser, - source_path(doc), - doc.mime_type, - Path(doc.filename).name, - ) - doc.content = parser.get_text() - - if parser.get_archive_path() and Path(parser.get_archive_path()).is_file(): - doc.archive_filename = generate_unique_filename( - doc, - archive_filename=True, - ) - with Path(parser.get_archive_path()).open("rb") as f: - doc.archive_checksum = hashlib.md5(f.read()).hexdigest() - archive_path_new(doc).parent.mkdir(parents=True, exist_ok=True) - shutil.copy2(parser.get_archive_path(), archive_path_new(doc)) - else: - doc.archive_checksum = None - logger.error( - f"Parser did not return an archive document for document " - f"ID:{doc.id}. Removing archive document.", - ) - doc.save() - return - except ParseError: - if try_num + 1 == retry_count: - logger.exception( - f"Unable to regenerate archive document for ID:{doc.id}. You " - f"need to invoke the document_archiver management command " - f"manually for that document.", - ) - doc.archive_checksum = None - doc.save() - return - else: - # This is mostly here for the tika parser in docker - # environments. The servers for parsing need to come up first, - # and the docker setup doesn't ensure that tika is running - # before attempting migrations. - logger.error("Parse error, will try again in 5 seconds...") - sleep(5) - finally: - parser.cleanup() - - -def move_old_to_new_locations(apps, schema_editor): - Document = apps.get_model("documents", "Document") - - affected_document_ids = set() - - old_archive_path_to_id = {} - - # check for documents that have incorrect archive versions - for doc in Document.objects.filter(archive_checksum__isnull=False): - old_path = archive_path_old(doc) - - if old_path in old_archive_path_to_id: - affected_document_ids.add(doc.id) - affected_document_ids.add(old_archive_path_to_id[old_path]) - else: - old_archive_path_to_id[old_path] = doc.id - - # check that archive files of all unaffected documents are in place - for doc in Document.objects.filter(archive_checksum__isnull=False): - old_path = archive_path_old(doc) - if doc.id not in affected_document_ids and not old_path.is_file(): - raise ValueError( - f"Archived document ID:{doc.id} does not exist at: {old_path}", - ) - - # check that we can regenerate affected archive versions - for doc_id in affected_document_ids: - from documents.parsers import get_parser_class_for_mime_type - - doc = Document.objects.get(id=doc_id) - parser_class = get_parser_class_for_mime_type(doc.mime_type) - if not parser_class: - raise ValueError( - f"Document ID:{doc.id} has an invalid archived document, " - f"but no parsers are available. Cannot migrate.", - ) - - for doc in Document.objects.filter(archive_checksum__isnull=False): - if doc.id in affected_document_ids: - old_path = archive_path_old(doc) - # remove affected archive versions - if old_path.is_file(): - logger.debug(f"Removing {old_path}") - old_path.unlink() - else: - # Set archive path for unaffected files - doc.archive_filename = archive_name_from_filename(Path(doc.filename)) - Document.objects.filter(id=doc.id).update( - archive_filename=doc.archive_filename, - ) - - # regenerate archive documents - for doc_id in affected_document_ids: - doc = Document.objects.get(id=doc_id) - create_archive_version(doc) - - -def move_new_to_old_locations(apps, schema_editor): - Document = apps.get_model("documents", "Document") - - old_archive_paths = set() - - for doc in Document.objects.filter(archive_checksum__isnull=False): - new_archive_path = archive_path_new(doc) - old_archive_path = archive_path_old(doc) - if old_archive_path in old_archive_paths: - raise ValueError( - f"Cannot migrate: Archive file name {old_archive_path} of " - f"document {doc.filename} would clash with another archive " - f"filename.", - ) - old_archive_paths.add(old_archive_path) - if new_archive_path != old_archive_path and old_archive_path.is_file(): - raise ValueError( - f"Cannot migrate: Cannot move {new_archive_path} to " - f"{old_archive_path}: file already exists.", - ) - - for doc in Document.objects.filter(archive_checksum__isnull=False): - new_archive_path = archive_path_new(doc) - old_archive_path = archive_path_old(doc) - if new_archive_path != old_archive_path: - logger.debug(f"Moving {new_archive_path} to {old_archive_path}") - shutil.move(new_archive_path, old_archive_path) - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1011_auto_20210101_2340"), - ] - - operations = [ - migrations.AddField( - model_name="document", - name="archive_filename", - field=models.FilePathField( - default=None, - editable=False, - help_text="Current archive filename in storage", - max_length=1024, - null=True, - unique=True, - verbose_name="archive filename", - ), - ), - migrations.AlterField( - model_name="document", - name="filename", - field=models.FilePathField( - default=None, - editable=False, - help_text="Current filename in storage", - max_length=1024, - null=True, - unique=True, - verbose_name="filename", - ), - ), - migrations.RunPython(move_old_to_new_locations, move_new_to_old_locations), - ] diff --git a/src/documents/migrations/1013_migrate_tag_colour.py b/src/documents/migrations/1013_migrate_tag_colour.py deleted file mode 100644 index 6cae10898..000000000 --- a/src/documents/migrations/1013_migrate_tag_colour.py +++ /dev/null @@ -1,74 +0,0 @@ -# Generated by Django 3.1.4 on 2020-12-02 21:43 - -from django.db import migrations -from django.db import models - -COLOURS_OLD = { - 1: "#a6cee3", - 2: "#1f78b4", - 3: "#b2df8a", - 4: "#33a02c", - 5: "#fb9a99", - 6: "#e31a1c", - 7: "#fdbf6f", - 8: "#ff7f00", - 9: "#cab2d6", - 10: "#6a3d9a", - 11: "#b15928", - 12: "#000000", - 13: "#cccccc", -} - - -def forward(apps, schema_editor): - Tag = apps.get_model("documents", "Tag") - - for tag in Tag.objects.all(): - colour_old_id = tag.colour_old - rgb = COLOURS_OLD[colour_old_id] - tag.color = rgb - tag.save() - - -def reverse(apps, schema_editor): - Tag = apps.get_model("documents", "Tag") - - def _get_colour_id(rdb): - for idx, rdbx in COLOURS_OLD.items(): - if rdbx == rdb: - return idx - # Return colour 1 if we can't match anything - return 1 - - for tag in Tag.objects.all(): - colour_id = _get_colour_id(tag.color) - tag.colour_old = colour_id - tag.save() - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1012_fix_archive_files"), - ] - - operations = [ - migrations.RenameField( - model_name="tag", - old_name="colour", - new_name="colour_old", - ), - migrations.AddField( - model_name="tag", - name="color", - field=models.CharField( - default="#a6cee3", - max_length=7, - verbose_name="color", - ), - ), - migrations.RunPython(forward, reverse), - migrations.RemoveField( - model_name="tag", - name="colour_old", - ), - ] diff --git a/src/documents/migrations/1014_auto_20210228_1614.py b/src/documents/migrations/1014_auto_20210228_1614.py deleted file mode 100644 index 5785bcb53..000000000 --- a/src/documents/migrations/1014_auto_20210228_1614.py +++ /dev/null @@ -1,42 +0,0 @@ -# Generated by Django 3.1.7 on 2021-02-28 15:14 - -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1013_migrate_tag_colour"), - ] - - operations = [ - migrations.AlterField( - model_name="savedviewfilterrule", - name="rule_type", - field=models.PositiveIntegerField( - choices=[ - (0, "title contains"), - (1, "content contains"), - (2, "ASN is"), - (3, "correspondent is"), - (4, "document type is"), - (5, "is in inbox"), - (6, "has tag"), - (7, "has any tag"), - (8, "created before"), - (9, "created after"), - (10, "created year is"), - (11, "created month is"), - (12, "created day is"), - (13, "added before"), - (14, "added after"), - (15, "modified before"), - (16, "modified after"), - (17, "does not have tag"), - (18, "does not have ASN"), - (19, "title or content contains"), - ], - verbose_name="rule type", - ), - ), - ] diff --git a/src/documents/migrations/1015_remove_null_characters.py b/src/documents/migrations/1015_remove_null_characters.py deleted file mode 100644 index 9872b3a75..000000000 --- a/src/documents/migrations/1015_remove_null_characters.py +++ /dev/null @@ -1,27 +0,0 @@ -# Generated by Django 3.1.7 on 2021-04-04 18:28 -import logging - -from django.db import migrations - -logger = logging.getLogger("paperless.migrations") - - -def remove_null_characters(apps, schema_editor): - Document = apps.get_model("documents", "Document") - - for doc in Document.objects.all(): - content: str = doc.content - if "\0" in content: - logger.info(f"Removing null characters from document {doc}...") - doc.content = content.replace("\0", " ") - doc.save() - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1014_auto_20210228_1614"), - ] - - operations = [ - migrations.RunPython(remove_null_characters, migrations.RunPython.noop), - ] diff --git a/src/documents/migrations/1016_auto_20210317_1351.py b/src/documents/migrations/1016_auto_20210317_1351.py deleted file mode 100644 index 67147fd4c..000000000 --- a/src/documents/migrations/1016_auto_20210317_1351.py +++ /dev/null @@ -1,54 +0,0 @@ -# Generated by Django 3.1.7 on 2021-03-17 12:51 - -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1015_remove_null_characters"), - ] - - operations = [ - migrations.AlterField( - model_name="savedview", - name="sort_field", - field=models.CharField( - blank=True, - max_length=128, - null=True, - verbose_name="sort field", - ), - ), - migrations.AlterField( - model_name="savedviewfilterrule", - name="rule_type", - field=models.PositiveIntegerField( - choices=[ - (0, "title contains"), - (1, "content contains"), - (2, "ASN is"), - (3, "correspondent is"), - (4, "document type is"), - (5, "is in inbox"), - (6, "has tag"), - (7, "has any tag"), - (8, "created before"), - (9, "created after"), - (10, "created year is"), - (11, "created month is"), - (12, "created day is"), - (13, "added before"), - (14, "added after"), - (15, "modified before"), - (16, "modified after"), - (17, "does not have tag"), - (18, "does not have ASN"), - (19, "title or content contains"), - (20, "fulltext query"), - (21, "more like this"), - ], - verbose_name="rule type", - ), - ), - ] diff --git a/src/documents/migrations/1016_auto_20210317_1351_squashed_1020_merge_20220518_1839.py b/src/documents/migrations/1016_auto_20210317_1351_squashed_1020_merge_20220518_1839.py deleted file mode 100644 index a7f92e931..000000000 --- a/src/documents/migrations/1016_auto_20210317_1351_squashed_1020_merge_20220518_1839.py +++ /dev/null @@ -1,190 +0,0 @@ -# Generated by Django 4.2.13 on 2024-06-28 18:09 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - replaces = [ - ("documents", "1016_auto_20210317_1351"), - ("documents", "1017_alter_savedviewfilterrule_rule_type"), - ("documents", "1018_alter_savedviewfilterrule_value"), - ("documents", "1019_uisettings"), - ("documents", "1019_storagepath_document_storage_path"), - ("documents", "1020_merge_20220518_1839"), - ] - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ("documents", "1015_remove_null_characters"), - ] - - operations = [ - migrations.AlterField( - model_name="savedview", - name="sort_field", - field=models.CharField( - blank=True, - max_length=128, - null=True, - verbose_name="sort field", - ), - ), - migrations.AlterField( - model_name="savedviewfilterrule", - name="rule_type", - field=models.PositiveIntegerField( - choices=[ - (0, "title contains"), - (1, "content contains"), - (2, "ASN is"), - (3, "correspondent is"), - (4, "document type is"), - (5, "is in inbox"), - (6, "has tag"), - (7, "has any tag"), - (8, "created before"), - (9, "created after"), - (10, "created year is"), - (11, "created month is"), - (12, "created day is"), - (13, "added before"), - (14, "added after"), - (15, "modified before"), - (16, "modified after"), - (17, "does not have tag"), - (18, "does not have ASN"), - (19, "title or content contains"), - (20, "fulltext query"), - (21, "more like this"), - ], - verbose_name="rule type", - ), - ), - migrations.AlterField( - model_name="savedviewfilterrule", - name="rule_type", - field=models.PositiveIntegerField( - choices=[ - (0, "title contains"), - (1, "content contains"), - (2, "ASN is"), - (3, "correspondent is"), - (4, "document type is"), - (5, "is in inbox"), - (6, "has tag"), - (7, "has any tag"), - (8, "created before"), - (9, "created after"), - (10, "created year is"), - (11, "created month is"), - (12, "created day is"), - (13, "added before"), - (14, "added after"), - (15, "modified before"), - (16, "modified after"), - (17, "does not have tag"), - (18, "does not have ASN"), - (19, "title or content contains"), - (20, "fulltext query"), - (21, "more like this"), - (22, "has tags in"), - ], - verbose_name="rule type", - ), - ), - migrations.AlterField( - model_name="savedviewfilterrule", - name="value", - field=models.CharField( - blank=True, - max_length=255, - null=True, - verbose_name="value", - ), - ), - migrations.CreateModel( - name="UiSettings", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("settings", models.JSONField(null=True)), - ( - "user", - models.OneToOneField( - on_delete=django.db.models.deletion.CASCADE, - related_name="ui_settings", - to=settings.AUTH_USER_MODEL, - ), - ), - ], - ), - migrations.CreateModel( - name="StoragePath", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "name", - models.CharField(max_length=128, unique=True, verbose_name="name"), - ), - ( - "match", - models.CharField(blank=True, max_length=256, verbose_name="match"), - ), - ( - "matching_algorithm", - models.PositiveIntegerField( - choices=[ - (1, "Any word"), - (2, "All words"), - (3, "Exact match"), - (4, "Regular expression"), - (5, "Fuzzy word"), - (6, "Automatic"), - ], - default=1, - verbose_name="matching algorithm", - ), - ), - ( - "is_insensitive", - models.BooleanField(default=True, verbose_name="is insensitive"), - ), - ("path", models.CharField(max_length=512, verbose_name="path")), - ], - options={ - "verbose_name": "storage path", - "verbose_name_plural": "storage paths", - "ordering": ("name",), - }, - ), - migrations.AddField( - model_name="document", - name="storage_path", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="documents", - to="documents.storagepath", - verbose_name="storage path", - ), - ), - ] diff --git a/src/documents/migrations/1017_alter_savedviewfilterrule_rule_type.py b/src/documents/migrations/1017_alter_savedviewfilterrule_rule_type.py deleted file mode 100644 index ab18f1bc1..000000000 --- a/src/documents/migrations/1017_alter_savedviewfilterrule_rule_type.py +++ /dev/null @@ -1,45 +0,0 @@ -# Generated by Django 3.2.12 on 2022-03-17 11:59 - -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1016_auto_20210317_1351"), - ] - - operations = [ - migrations.AlterField( - model_name="savedviewfilterrule", - name="rule_type", - field=models.PositiveIntegerField( - choices=[ - (0, "title contains"), - (1, "content contains"), - (2, "ASN is"), - (3, "correspondent is"), - (4, "document type is"), - (5, "is in inbox"), - (6, "has tag"), - (7, "has any tag"), - (8, "created before"), - (9, "created after"), - (10, "created year is"), - (11, "created month is"), - (12, "created day is"), - (13, "added before"), - (14, "added after"), - (15, "modified before"), - (16, "modified after"), - (17, "does not have tag"), - (18, "does not have ASN"), - (19, "title or content contains"), - (20, "fulltext query"), - (21, "more like this"), - (22, "has tags in"), - ], - verbose_name="rule type", - ), - ), - ] diff --git a/src/documents/migrations/1018_alter_savedviewfilterrule_value.py b/src/documents/migrations/1018_alter_savedviewfilterrule_value.py deleted file mode 100644 index 95ef4861d..000000000 --- a/src/documents/migrations/1018_alter_savedviewfilterrule_value.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 4.0.3 on 2022-04-01 22:50 - -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1017_alter_savedviewfilterrule_rule_type"), - ] - - operations = [ - migrations.AlterField( - model_name="savedviewfilterrule", - name="value", - field=models.CharField( - blank=True, - max_length=255, - null=True, - verbose_name="value", - ), - ), - ] diff --git a/src/documents/migrations/1019_storagepath_document_storage_path.py b/src/documents/migrations/1019_storagepath_document_storage_path.py deleted file mode 100644 index b09941bf5..000000000 --- a/src/documents/migrations/1019_storagepath_document_storage_path.py +++ /dev/null @@ -1,73 +0,0 @@ -# Generated by Django 4.0.4 on 2022-05-02 15:56 - -import django.db.models.deletion -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1018_alter_savedviewfilterrule_value"), - ] - - operations = [ - migrations.CreateModel( - name="StoragePath", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "name", - models.CharField(max_length=128, unique=True, verbose_name="name"), - ), - ( - "match", - models.CharField(blank=True, max_length=256, verbose_name="match"), - ), - ( - "matching_algorithm", - models.PositiveIntegerField( - choices=[ - (1, "Any word"), - (2, "All words"), - (3, "Exact match"), - (4, "Regular expression"), - (5, "Fuzzy word"), - (6, "Automatic"), - ], - default=1, - verbose_name="matching algorithm", - ), - ), - ( - "is_insensitive", - models.BooleanField(default=True, verbose_name="is insensitive"), - ), - ("path", models.CharField(max_length=512, verbose_name="path")), - ], - options={ - "verbose_name": "storage path", - "verbose_name_plural": "storage paths", - "ordering": ("name",), - }, - ), - migrations.AddField( - model_name="document", - name="storage_path", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="documents", - to="documents.storagepath", - verbose_name="storage path", - ), - ), - ] diff --git a/src/documents/migrations/1019_uisettings.py b/src/documents/migrations/1019_uisettings.py deleted file mode 100644 index e84138077..000000000 --- a/src/documents/migrations/1019_uisettings.py +++ /dev/null @@ -1,39 +0,0 @@ -# Generated by Django 4.0.4 on 2022-05-07 05:10 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ("documents", "1018_alter_savedviewfilterrule_value"), - ] - - operations = [ - migrations.CreateModel( - name="UiSettings", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("settings", models.JSONField(null=True)), - ( - "user", - models.OneToOneField( - on_delete=django.db.models.deletion.CASCADE, - related_name="ui_settings", - to=settings.AUTH_USER_MODEL, - ), - ), - ], - ), - ] diff --git a/src/documents/migrations/1020_merge_20220518_1839.py b/src/documents/migrations/1020_merge_20220518_1839.py deleted file mode 100644 index a766aaa20..000000000 --- a/src/documents/migrations/1020_merge_20220518_1839.py +++ /dev/null @@ -1,12 +0,0 @@ -# Generated by Django 4.0.4 on 2022-05-18 18:39 - -from django.db import migrations - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1019_storagepath_document_storage_path"), - ("documents", "1019_uisettings"), - ] - - operations = [] diff --git a/src/documents/migrations/1021_webp_thumbnail_conversion.py b/src/documents/migrations/1021_webp_thumbnail_conversion.py deleted file mode 100644 index 50b12b156..000000000 --- a/src/documents/migrations/1021_webp_thumbnail_conversion.py +++ /dev/null @@ -1,104 +0,0 @@ -# Generated by Django 4.0.5 on 2022-06-11 15:40 -import logging -import multiprocessing.pool -import shutil -import tempfile -import time -from pathlib import Path - -from django.conf import settings -from django.db import migrations - -from documents.parsers import run_convert - -logger = logging.getLogger("paperless.migrations") - - -def _do_convert(work_package): - existing_thumbnail, converted_thumbnail = work_package - try: - logger.info(f"Converting thumbnail: {existing_thumbnail}") - - # Run actual conversion - run_convert( - density=300, - scale="500x5000>", - alpha="remove", - strip=True, - trim=False, - auto_orient=True, - input_file=f"{existing_thumbnail}[0]", - output_file=str(converted_thumbnail), - ) - - # Copy newly created thumbnail to thumbnail directory - shutil.copy(converted_thumbnail, existing_thumbnail.parent) - - # Remove the PNG version - existing_thumbnail.unlink() - - logger.info( - "Conversion to WebP completed, " - f"replaced {existing_thumbnail.name} with {converted_thumbnail.name}", - ) - - except Exception as e: - logger.error(f"Error converting thumbnail (existing file unchanged): {e}") - - -def _convert_thumbnails_to_webp(apps, schema_editor): - start = time.time() - - with tempfile.TemporaryDirectory() as tempdir: - work_packages = [] - - for file in Path(settings.THUMBNAIL_DIR).glob("*.png"): - existing_thumbnail = file.resolve() - - # Change the existing filename suffix from png to webp - converted_thumbnail_name = existing_thumbnail.with_suffix( - ".webp", - ).name - - # Create the expected output filename in the tempdir - converted_thumbnail = ( - Path(tempdir) / Path(converted_thumbnail_name) - ).resolve() - - # Package up the necessary info - work_packages.append( - (existing_thumbnail, converted_thumbnail), - ) - - if work_packages: - logger.info( - "\n\n" - " This is a one-time only migration to convert thumbnails for all of your\n" - " documents into WebP format. If you have a lot of documents though, \n" - " this may take a while, so a coffee break may be in order." - "\n", - ) - - with multiprocessing.pool.Pool( - processes=min(multiprocessing.cpu_count(), 4), - maxtasksperchild=4, - ) as pool: - pool.map(_do_convert, work_packages) - - end = time.time() - duration = end - start - - logger.info(f"Conversion completed in {duration:.3f}s") - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1020_merge_20220518_1839"), - ] - - operations = [ - migrations.RunPython( - code=_convert_thumbnails_to_webp, - reverse_code=migrations.RunPython.noop, - ), - ] diff --git a/src/documents/migrations/1022_paperlesstask.py b/src/documents/migrations/1022_paperlesstask.py deleted file mode 100644 index c7b3f7744..000000000 --- a/src/documents/migrations/1022_paperlesstask.py +++ /dev/null @@ -1,52 +0,0 @@ -# Generated by Django 4.0.4 on 2022-05-23 07:14 - -import django.db.models.deletion -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1021_webp_thumbnail_conversion"), - ] - - operations = [ - migrations.CreateModel( - name="PaperlessTask", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("task_id", models.CharField(max_length=128)), - ("name", models.CharField(max_length=256, null=True)), - ( - "created", - models.DateTimeField(auto_now=True, verbose_name="created"), - ), - ( - "started", - models.DateTimeField(null=True, verbose_name="started"), - ), - ("acknowledged", models.BooleanField(default=False)), - ( - "attempted_task", - models.OneToOneField( - blank=True, - null=True, - on_delete=django.db.models.deletion.CASCADE, - related_name="attempted_task", - # This is a dummy field, 1026 will fix up the column - # This manual change is required, as django doesn't django doesn't really support - # removing an app which has migration deps like this - to="documents.document", - ), - ), - ], - ), - ] diff --git a/src/documents/migrations/1022_paperlesstask_squashed_1036_alter_savedviewfilterrule_rule_type.py b/src/documents/migrations/1022_paperlesstask_squashed_1036_alter_savedviewfilterrule_rule_type.py deleted file mode 100644 index dc73a51a2..000000000 --- a/src/documents/migrations/1022_paperlesstask_squashed_1036_alter_savedviewfilterrule_rule_type.py +++ /dev/null @@ -1,668 +0,0 @@ -# Generated by Django 4.2.13 on 2024-06-28 18:10 - -import django.core.validators -import django.db.models.deletion -import django.utils.timezone -from django.conf import settings -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - replaces = [ - ("documents", "1022_paperlesstask"), - ("documents", "1023_add_comments"), - ("documents", "1024_document_original_filename"), - ("documents", "1025_alter_savedviewfilterrule_rule_type"), - ("documents", "1026_transition_to_celery"), - ("documents", "1027_remove_paperlesstask_attempted_task_and_more"), - ("documents", "1028_remove_paperlesstask_task_args_and_more"), - ("documents", "1029_alter_document_archive_serial_number"), - ("documents", "1030_alter_paperlesstask_task_file_name"), - ("documents", "1031_remove_savedview_user_correspondent_owner_and_more"), - ("documents", "1032_alter_correspondent_matching_algorithm_and_more"), - ("documents", "1033_alter_documenttype_options_alter_tag_options_and_more"), - ("documents", "1034_alter_savedviewfilterrule_rule_type"), - ("documents", "1035_rename_comment_note"), - ("documents", "1036_alter_savedviewfilterrule_rule_type"), - ] - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ("django_celery_results", "0011_taskresult_periodic_task_name"), - ("documents", "1021_webp_thumbnail_conversion"), - ] - - operations = [ - migrations.CreateModel( - name="Comment", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "comment", - models.TextField( - blank=True, - help_text="Comment for the document", - verbose_name="content", - ), - ), - ( - "created", - models.DateTimeField( - db_index=True, - default=django.utils.timezone.now, - verbose_name="created", - ), - ), - ( - "document", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.CASCADE, - related_name="documents", - to="documents.document", - verbose_name="document", - ), - ), - ( - "user", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="users", - to=settings.AUTH_USER_MODEL, - verbose_name="user", - ), - ), - ], - options={ - "verbose_name": "comment", - "verbose_name_plural": "comments", - "ordering": ("created",), - }, - ), - migrations.AddField( - model_name="document", - name="original_filename", - field=models.CharField( - default=None, - editable=False, - help_text="The original name of the file when it was uploaded", - max_length=1024, - null=True, - verbose_name="original filename", - ), - ), - migrations.AlterField( - model_name="savedviewfilterrule", - name="rule_type", - field=models.PositiveIntegerField( - choices=[ - (0, "title contains"), - (1, "content contains"), - (2, "ASN is"), - (3, "correspondent is"), - (4, "document type is"), - (5, "is in inbox"), - (6, "has tag"), - (7, "has any tag"), - (8, "created before"), - (9, "created after"), - (10, "created year is"), - (11, "created month is"), - (12, "created day is"), - (13, "added before"), - (14, "added after"), - (15, "modified before"), - (16, "modified after"), - (17, "does not have tag"), - (18, "does not have ASN"), - (19, "title or content contains"), - (20, "fulltext query"), - (21, "more like this"), - (22, "has tags in"), - (23, "ASN greater than"), - (24, "ASN less than"), - (25, "storage path is"), - ], - verbose_name="rule type", - ), - ), - migrations.CreateModel( - name="PaperlessTask", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("task_id", models.CharField(max_length=128)), - ("acknowledged", models.BooleanField(default=False)), - ( - "attempted_task", - models.OneToOneField( - blank=True, - null=True, - on_delete=django.db.models.deletion.CASCADE, - related_name="attempted_task", - to="django_celery_results.taskresult", - ), - ), - ], - ), - migrations.RunSQL( - sql="DROP TABLE IF EXISTS django_q_ormq", - reverse_sql="", - ), - migrations.RunSQL( - sql="DROP TABLE IF EXISTS django_q_schedule", - reverse_sql="", - ), - migrations.RunSQL( - sql="DROP TABLE IF EXISTS django_q_task", - reverse_sql="", - ), - migrations.RemoveField( - model_name="paperlesstask", - name="attempted_task", - ), - migrations.AddField( - model_name="paperlesstask", - name="date_created", - field=models.DateTimeField( - default=django.utils.timezone.now, - help_text="Datetime field when the task result was created in UTC", - null=True, - verbose_name="Created DateTime", - ), - ), - migrations.AddField( - model_name="paperlesstask", - name="date_done", - field=models.DateTimeField( - default=None, - help_text="Datetime field when the task was completed in UTC", - null=True, - verbose_name="Completed DateTime", - ), - ), - migrations.AddField( - model_name="paperlesstask", - name="date_started", - field=models.DateTimeField( - default=None, - help_text="Datetime field when the task was started in UTC", - null=True, - verbose_name="Started DateTime", - ), - ), - migrations.AddField( - model_name="paperlesstask", - name="result", - field=models.TextField( - default=None, - help_text="The data returned by the task", - null=True, - verbose_name="Result Data", - ), - ), - migrations.AddField( - model_name="paperlesstask", - name="status", - field=models.CharField( - choices=[ - ("FAILURE", "FAILURE"), - ("PENDING", "PENDING"), - ("RECEIVED", "RECEIVED"), - ("RETRY", "RETRY"), - ("REVOKED", "REVOKED"), - ("STARTED", "STARTED"), - ("SUCCESS", "SUCCESS"), - ], - default="PENDING", - help_text="Current state of the task being run", - max_length=30, - verbose_name="Task State", - ), - ), - migrations.AddField( - model_name="paperlesstask", - name="task_name", - field=models.CharField( - help_text="Name of the Task which was run", - max_length=255, - null=True, - verbose_name="Task Name", - ), - ), - migrations.AlterField( - model_name="paperlesstask", - name="acknowledged", - field=models.BooleanField( - default=False, - help_text="If the task is acknowledged via the frontend or API", - verbose_name="Acknowledged", - ), - ), - migrations.AlterField( - model_name="paperlesstask", - name="task_id", - field=models.CharField( - help_text="Celery ID for the Task that was run", - max_length=255, - unique=True, - verbose_name="Task ID", - ), - ), - migrations.AlterField( - model_name="document", - name="archive_serial_number", - field=models.PositiveIntegerField( - blank=True, - db_index=True, - help_text="The position of this document in your physical document archive.", - null=True, - unique=True, - validators=[ - django.core.validators.MaxValueValidator(4294967295), - django.core.validators.MinValueValidator(0), - ], - verbose_name="archive serial number", - ), - ), - migrations.AddField( - model_name="paperlesstask", - name="task_file_name", - field=models.CharField( - help_text="Name of the file which the Task was run for", - max_length=255, - null=True, - verbose_name="Task Filename", - ), - ), - migrations.RenameField( - model_name="savedview", - old_name="user", - new_name="owner", - ), - migrations.AlterField( - model_name="savedview", - name="owner", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to=settings.AUTH_USER_MODEL, - verbose_name="owner", - ), - ), - migrations.AddField( - model_name="correspondent", - name="owner", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to=settings.AUTH_USER_MODEL, - verbose_name="owner", - ), - ), - migrations.AddField( - model_name="document", - name="owner", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to=settings.AUTH_USER_MODEL, - verbose_name="owner", - ), - ), - migrations.AddField( - model_name="documenttype", - name="owner", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to=settings.AUTH_USER_MODEL, - verbose_name="owner", - ), - ), - migrations.AddField( - model_name="storagepath", - name="owner", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to=settings.AUTH_USER_MODEL, - verbose_name="owner", - ), - ), - migrations.AddField( - model_name="tag", - name="owner", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to=settings.AUTH_USER_MODEL, - verbose_name="owner", - ), - ), - migrations.AlterField( - model_name="correspondent", - name="matching_algorithm", - field=models.PositiveIntegerField( - choices=[ - (0, "None"), - (1, "Any word"), - (2, "All words"), - (3, "Exact match"), - (4, "Regular expression"), - (5, "Fuzzy word"), - (6, "Automatic"), - ], - default=1, - verbose_name="matching algorithm", - ), - ), - migrations.AlterField( - model_name="documenttype", - name="matching_algorithm", - field=models.PositiveIntegerField( - choices=[ - (0, "None"), - (1, "Any word"), - (2, "All words"), - (3, "Exact match"), - (4, "Regular expression"), - (5, "Fuzzy word"), - (6, "Automatic"), - ], - default=1, - verbose_name="matching algorithm", - ), - ), - migrations.AlterField( - model_name="storagepath", - name="matching_algorithm", - field=models.PositiveIntegerField( - choices=[ - (0, "None"), - (1, "Any word"), - (2, "All words"), - (3, "Exact match"), - (4, "Regular expression"), - (5, "Fuzzy word"), - (6, "Automatic"), - ], - default=1, - verbose_name="matching algorithm", - ), - ), - migrations.AlterField( - model_name="tag", - name="matching_algorithm", - field=models.PositiveIntegerField( - choices=[ - (0, "None"), - (1, "Any word"), - (2, "All words"), - (3, "Exact match"), - (4, "Regular expression"), - (5, "Fuzzy word"), - (6, "Automatic"), - ], - default=1, - verbose_name="matching algorithm", - ), - ), - migrations.AlterModelOptions( - name="documenttype", - options={ - "ordering": ("name",), - "verbose_name": "document type", - "verbose_name_plural": "document types", - }, - ), - migrations.AlterModelOptions( - name="tag", - options={ - "ordering": ("name",), - "verbose_name": "tag", - "verbose_name_plural": "tags", - }, - ), - migrations.AlterField( - model_name="correspondent", - name="name", - field=models.CharField(max_length=128, verbose_name="name"), - ), - migrations.AlterField( - model_name="documenttype", - name="name", - field=models.CharField(max_length=128, verbose_name="name"), - ), - migrations.AlterField( - model_name="storagepath", - name="name", - field=models.CharField(max_length=128, verbose_name="name"), - ), - migrations.AlterField( - model_name="tag", - name="name", - field=models.CharField(max_length=128, verbose_name="name"), - ), - migrations.AddConstraint( - model_name="correspondent", - constraint=models.UniqueConstraint( - fields=("name", "owner"), - name="documents_correspondent_unique_name_owner", - ), - ), - migrations.AddConstraint( - model_name="correspondent", - constraint=models.UniqueConstraint( - condition=models.Q(("owner__isnull", True)), - fields=("name",), - name="documents_correspondent_name_uniq", - ), - ), - migrations.AddConstraint( - model_name="documenttype", - constraint=models.UniqueConstraint( - fields=("name", "owner"), - name="documents_documenttype_unique_name_owner", - ), - ), - migrations.AddConstraint( - model_name="documenttype", - constraint=models.UniqueConstraint( - condition=models.Q(("owner__isnull", True)), - fields=("name",), - name="documents_documenttype_name_uniq", - ), - ), - migrations.AddConstraint( - model_name="storagepath", - constraint=models.UniqueConstraint( - fields=("name", "owner"), - name="documents_storagepath_unique_name_owner", - ), - ), - migrations.AddConstraint( - model_name="storagepath", - constraint=models.UniqueConstraint( - condition=models.Q(("owner__isnull", True)), - fields=("name",), - name="documents_storagepath_name_uniq", - ), - ), - migrations.AddConstraint( - model_name="tag", - constraint=models.UniqueConstraint( - fields=("name", "owner"), - name="documents_tag_unique_name_owner", - ), - ), - migrations.AddConstraint( - model_name="tag", - constraint=models.UniqueConstraint( - condition=models.Q(("owner__isnull", True)), - fields=("name",), - name="documents_tag_name_uniq", - ), - ), - migrations.AlterField( - model_name="savedviewfilterrule", - name="rule_type", - field=models.PositiveIntegerField( - choices=[ - (0, "title contains"), - (1, "content contains"), - (2, "ASN is"), - (3, "correspondent is"), - (4, "document type is"), - (5, "is in inbox"), - (6, "has tag"), - (7, "has any tag"), - (8, "created before"), - (9, "created after"), - (10, "created year is"), - (11, "created month is"), - (12, "created day is"), - (13, "added before"), - (14, "added after"), - (15, "modified before"), - (16, "modified after"), - (17, "does not have tag"), - (18, "does not have ASN"), - (19, "title or content contains"), - (20, "fulltext query"), - (21, "more like this"), - (22, "has tags in"), - (23, "ASN greater than"), - (24, "ASN less than"), - (25, "storage path is"), - (26, "has correspondent in"), - (27, "does not have correspondent in"), - (28, "has document type in"), - (29, "does not have document type in"), - (30, "has storage path in"), - (31, "does not have storage path in"), - ], - verbose_name="rule type", - ), - ), - migrations.RenameModel( - old_name="Comment", - new_name="Note", - ), - migrations.RenameField( - model_name="note", - old_name="comment", - new_name="note", - ), - migrations.AlterModelOptions( - name="note", - options={ - "ordering": ("created",), - "verbose_name": "note", - "verbose_name_plural": "notes", - }, - ), - migrations.AlterField( - model_name="note", - name="document", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.CASCADE, - related_name="notes", - to="documents.document", - verbose_name="document", - ), - ), - migrations.AlterField( - model_name="note", - name="note", - field=models.TextField( - blank=True, - help_text="Note for the document", - verbose_name="content", - ), - ), - migrations.AlterField( - model_name="note", - name="user", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="notes", - to=settings.AUTH_USER_MODEL, - verbose_name="user", - ), - ), - migrations.AlterField( - model_name="savedviewfilterrule", - name="rule_type", - field=models.PositiveIntegerField( - choices=[ - (0, "title contains"), - (1, "content contains"), - (2, "ASN is"), - (3, "correspondent is"), - (4, "document type is"), - (5, "is in inbox"), - (6, "has tag"), - (7, "has any tag"), - (8, "created before"), - (9, "created after"), - (10, "created year is"), - (11, "created month is"), - (12, "created day is"), - (13, "added before"), - (14, "added after"), - (15, "modified before"), - (16, "modified after"), - (17, "does not have tag"), - (18, "does not have ASN"), - (19, "title or content contains"), - (20, "fulltext query"), - (21, "more like this"), - (22, "has tags in"), - (23, "ASN greater than"), - (24, "ASN less than"), - (25, "storage path is"), - (26, "has correspondent in"), - (27, "does not have correspondent in"), - (28, "has document type in"), - (29, "does not have document type in"), - (30, "has storage path in"), - (31, "does not have storage path in"), - (32, "owner is"), - (33, "has owner in"), - (34, "does not have owner"), - (35, "does not have owner in"), - ], - verbose_name="rule type", - ), - ), - ] diff --git a/src/documents/migrations/1023_add_comments.py b/src/documents/migrations/1023_add_comments.py deleted file mode 100644 index 0b26739bc..000000000 --- a/src/documents/migrations/1023_add_comments.py +++ /dev/null @@ -1,70 +0,0 @@ -import django.utils.timezone -from django.conf import settings -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1022_paperlesstask"), - ] - - operations = [ - migrations.CreateModel( - name="Comment", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "comment", - models.TextField( - blank=True, - help_text="Comment for the document", - verbose_name="content", - ), - ), - ( - "created", - models.DateTimeField( - db_index=True, - default=django.utils.timezone.now, - verbose_name="created", - ), - ), - ( - "document", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.CASCADE, - related_name="documents", - to="documents.document", - verbose_name="document", - ), - ), - ( - "user", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="users", - to=settings.AUTH_USER_MODEL, - verbose_name="user", - ), - ), - ], - options={ - "verbose_name": "comment", - "verbose_name_plural": "comments", - "ordering": ("created",), - }, - ), - ] diff --git a/src/documents/migrations/1024_document_original_filename.py b/src/documents/migrations/1024_document_original_filename.py deleted file mode 100644 index 05be7269e..000000000 --- a/src/documents/migrations/1024_document_original_filename.py +++ /dev/null @@ -1,25 +0,0 @@ -# Generated by Django 4.0.6 on 2022-07-25 06:34 - -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1023_add_comments"), - ] - - operations = [ - migrations.AddField( - model_name="document", - name="original_filename", - field=models.CharField( - default=None, - editable=False, - help_text="The original name of the file when it was uploaded", - max_length=1024, - null=True, - verbose_name="original filename", - ), - ), - ] diff --git a/src/documents/migrations/1025_alter_savedviewfilterrule_rule_type.py b/src/documents/migrations/1025_alter_savedviewfilterrule_rule_type.py deleted file mode 100644 index a2deb9579..000000000 --- a/src/documents/migrations/1025_alter_savedviewfilterrule_rule_type.py +++ /dev/null @@ -1,48 +0,0 @@ -# Generated by Django 4.0.5 on 2022-08-26 16:49 - -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1024_document_original_filename"), - ] - - operations = [ - migrations.AlterField( - model_name="savedviewfilterrule", - name="rule_type", - field=models.PositiveIntegerField( - choices=[ - (0, "title contains"), - (1, "content contains"), - (2, "ASN is"), - (3, "correspondent is"), - (4, "document type is"), - (5, "is in inbox"), - (6, "has tag"), - (7, "has any tag"), - (8, "created before"), - (9, "created after"), - (10, "created year is"), - (11, "created month is"), - (12, "created day is"), - (13, "added before"), - (14, "added after"), - (15, "modified before"), - (16, "modified after"), - (17, "does not have tag"), - (18, "does not have ASN"), - (19, "title or content contains"), - (20, "fulltext query"), - (21, "more like this"), - (22, "has tags in"), - (23, "ASN greater than"), - (24, "ASN less than"), - (25, "storage path is"), - ], - verbose_name="rule type", - ), - ), - ] diff --git a/src/documents/migrations/1026_transition_to_celery.py b/src/documents/migrations/1026_transition_to_celery.py deleted file mode 100644 index 227188d22..000000000 --- a/src/documents/migrations/1026_transition_to_celery.py +++ /dev/null @@ -1,60 +0,0 @@ -# Generated by Django 4.1.1 on 2022-09-27 19:31 - -import django.db.models.deletion -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("django_celery_results", "0011_taskresult_periodic_task_name"), - ("documents", "1025_alter_savedviewfilterrule_rule_type"), - ] - - operations = [ - migrations.RemoveField( - model_name="paperlesstask", - name="created", - ), - migrations.RemoveField( - model_name="paperlesstask", - name="name", - ), - migrations.RemoveField( - model_name="paperlesstask", - name="started", - ), - # Remove the field from the model - migrations.RemoveField( - model_name="paperlesstask", - name="attempted_task", - ), - # Add the field back, pointing to the correct model - # This resolves a problem where the temporary change in 1022 - # results in a type mismatch - migrations.AddField( - model_name="paperlesstask", - name="attempted_task", - field=models.OneToOneField( - blank=True, - null=True, - on_delete=django.db.models.deletion.CASCADE, - related_name="attempted_task", - to="django_celery_results.taskresult", - ), - ), - # Drop the django-q tables entirely - # Must be done last or there could be references here - migrations.RunSQL( - "DROP TABLE IF EXISTS django_q_ormq", - reverse_sql=migrations.RunSQL.noop, - ), - migrations.RunSQL( - "DROP TABLE IF EXISTS django_q_schedule", - reverse_sql=migrations.RunSQL.noop, - ), - migrations.RunSQL( - "DROP TABLE IF EXISTS django_q_task", - reverse_sql=migrations.RunSQL.noop, - ), - ] diff --git a/src/documents/migrations/1027_remove_paperlesstask_attempted_task_and_more.py b/src/documents/migrations/1027_remove_paperlesstask_attempted_task_and_more.py deleted file mode 100644 index c169c3096..000000000 --- a/src/documents/migrations/1027_remove_paperlesstask_attempted_task_and_more.py +++ /dev/null @@ -1,134 +0,0 @@ -# Generated by Django 4.1.2 on 2022-10-17 16:31 - -import django.utils.timezone -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1026_transition_to_celery"), - ] - - operations = [ - migrations.RemoveField( - model_name="paperlesstask", - name="attempted_task", - ), - migrations.AddField( - model_name="paperlesstask", - name="date_created", - field=models.DateTimeField( - default=django.utils.timezone.now, - help_text="Datetime field when the task result was created in UTC", - null=True, - verbose_name="Created DateTime", - ), - ), - migrations.AddField( - model_name="paperlesstask", - name="date_done", - field=models.DateTimeField( - default=None, - help_text="Datetime field when the task was completed in UTC", - null=True, - verbose_name="Completed DateTime", - ), - ), - migrations.AddField( - model_name="paperlesstask", - name="date_started", - field=models.DateTimeField( - default=None, - help_text="Datetime field when the task was started in UTC", - null=True, - verbose_name="Started DateTime", - ), - ), - migrations.AddField( - model_name="paperlesstask", - name="result", - field=models.TextField( - default=None, - help_text="The data returned by the task", - null=True, - verbose_name="Result Data", - ), - ), - migrations.AddField( - model_name="paperlesstask", - name="status", - field=models.CharField( - choices=[ - ("FAILURE", "FAILURE"), - ("PENDING", "PENDING"), - ("RECEIVED", "RECEIVED"), - ("RETRY", "RETRY"), - ("REVOKED", "REVOKED"), - ("STARTED", "STARTED"), - ("SUCCESS", "SUCCESS"), - ], - default="PENDING", - help_text="Current state of the task being run", - max_length=30, - verbose_name="Task State", - ), - ), - migrations.AddField( - model_name="paperlesstask", - name="task_args", - field=models.JSONField( - help_text="JSON representation of the positional arguments used with the task", - null=True, - verbose_name="Task Positional Arguments", - ), - ), - migrations.AddField( - model_name="paperlesstask", - name="task_file_name", - field=models.CharField( - help_text="Name of the file which the Task was run for", - max_length=255, - null=True, - verbose_name="Task Name", - ), - ), - migrations.AddField( - model_name="paperlesstask", - name="task_kwargs", - field=models.JSONField( - help_text="JSON representation of the named arguments used with the task", - null=True, - verbose_name="Task Named Arguments", - ), - ), - migrations.AddField( - model_name="paperlesstask", - name="task_name", - field=models.CharField( - help_text="Name of the Task which was run", - max_length=255, - null=True, - verbose_name="Task Name", - ), - ), - migrations.AlterField( - model_name="paperlesstask", - name="acknowledged", - field=models.BooleanField( - default=False, - help_text="If the task is acknowledged via the frontend or API", - verbose_name="Acknowledged", - ), - ), - migrations.AlterField( - model_name="paperlesstask", - name="task_id", - field=models.CharField( - help_text="Celery ID for the Task that was run", - max_length=255, - unique=True, - verbose_name="Task ID", - ), - ), - ] diff --git a/src/documents/migrations/1028_remove_paperlesstask_task_args_and_more.py b/src/documents/migrations/1028_remove_paperlesstask_task_args_and_more.py deleted file mode 100644 index 6e03c124b..000000000 --- a/src/documents/migrations/1028_remove_paperlesstask_task_args_and_more.py +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by Django 4.1.3 on 2022-11-22 17:50 - -from django.db import migrations - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1027_remove_paperlesstask_attempted_task_and_more"), - ] - - operations = [ - migrations.RemoveField( - model_name="paperlesstask", - name="task_args", - ), - migrations.RemoveField( - model_name="paperlesstask", - name="task_kwargs", - ), - ] diff --git a/src/documents/migrations/1029_alter_document_archive_serial_number.py b/src/documents/migrations/1029_alter_document_archive_serial_number.py deleted file mode 100644 index 57848b2dc..000000000 --- a/src/documents/migrations/1029_alter_document_archive_serial_number.py +++ /dev/null @@ -1,30 +0,0 @@ -# Generated by Django 4.1.4 on 2023-01-24 17:56 - -import django.core.validators -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1028_remove_paperlesstask_task_args_and_more"), - ] - - operations = [ - migrations.AlterField( - model_name="document", - name="archive_serial_number", - field=models.PositiveIntegerField( - blank=True, - db_index=True, - help_text="The position of this document in your physical document archive.", - null=True, - unique=True, - validators=[ - django.core.validators.MaxValueValidator(4294967295), - django.core.validators.MinValueValidator(0), - ], - verbose_name="archive serial number", - ), - ), - ] diff --git a/src/documents/migrations/1030_alter_paperlesstask_task_file_name.py b/src/documents/migrations/1030_alter_paperlesstask_task_file_name.py deleted file mode 100644 index 37e918bee..000000000 --- a/src/documents/migrations/1030_alter_paperlesstask_task_file_name.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 4.1.5 on 2023-02-03 21:53 - -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1029_alter_document_archive_serial_number"), - ] - - operations = [ - migrations.AlterField( - model_name="paperlesstask", - name="task_file_name", - field=models.CharField( - help_text="Name of the file which the Task was run for", - max_length=255, - null=True, - verbose_name="Task Filename", - ), - ), - ] diff --git a/src/documents/migrations/1031_remove_savedview_user_correspondent_owner_and_more.py b/src/documents/migrations/1031_remove_savedview_user_correspondent_owner_and_more.py deleted file mode 100644 index 56e4355ef..000000000 --- a/src/documents/migrations/1031_remove_savedview_user_correspondent_owner_and_more.py +++ /dev/null @@ -1,87 +0,0 @@ -# Generated by Django 4.1.4 on 2022-02-03 04:24 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ("documents", "1030_alter_paperlesstask_task_file_name"), - ] - - operations = [ - migrations.RenameField( - model_name="savedview", - old_name="user", - new_name="owner", - ), - migrations.AlterField( - model_name="savedview", - name="owner", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to=settings.AUTH_USER_MODEL, - verbose_name="owner", - ), - ), - migrations.AddField( - model_name="correspondent", - name="owner", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to=settings.AUTH_USER_MODEL, - verbose_name="owner", - ), - ), - migrations.AddField( - model_name="document", - name="owner", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to=settings.AUTH_USER_MODEL, - verbose_name="owner", - ), - ), - migrations.AddField( - model_name="documenttype", - name="owner", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to=settings.AUTH_USER_MODEL, - verbose_name="owner", - ), - ), - migrations.AddField( - model_name="storagepath", - name="owner", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to=settings.AUTH_USER_MODEL, - verbose_name="owner", - ), - ), - migrations.AddField( - model_name="tag", - name="owner", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to=settings.AUTH_USER_MODEL, - verbose_name="owner", - ), - ), - ] diff --git a/src/documents/migrations/1032_alter_correspondent_matching_algorithm_and_more.py b/src/documents/migrations/1032_alter_correspondent_matching_algorithm_and_more.py deleted file mode 100644 index 3d1c5658a..000000000 --- a/src/documents/migrations/1032_alter_correspondent_matching_algorithm_and_more.py +++ /dev/null @@ -1,81 +0,0 @@ -# Generated by Django 4.1.7 on 2023-02-22 00:45 - -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1031_remove_savedview_user_correspondent_owner_and_more"), - ] - - operations = [ - migrations.AlterField( - model_name="correspondent", - name="matching_algorithm", - field=models.PositiveIntegerField( - choices=[ - (0, "None"), - (1, "Any word"), - (2, "All words"), - (3, "Exact match"), - (4, "Regular expression"), - (5, "Fuzzy word"), - (6, "Automatic"), - ], - default=1, - verbose_name="matching algorithm", - ), - ), - migrations.AlterField( - model_name="documenttype", - name="matching_algorithm", - field=models.PositiveIntegerField( - choices=[ - (0, "None"), - (1, "Any word"), - (2, "All words"), - (3, "Exact match"), - (4, "Regular expression"), - (5, "Fuzzy word"), - (6, "Automatic"), - ], - default=1, - verbose_name="matching algorithm", - ), - ), - migrations.AlterField( - model_name="storagepath", - name="matching_algorithm", - field=models.PositiveIntegerField( - choices=[ - (0, "None"), - (1, "Any word"), - (2, "All words"), - (3, "Exact match"), - (4, "Regular expression"), - (5, "Fuzzy word"), - (6, "Automatic"), - ], - default=1, - verbose_name="matching algorithm", - ), - ), - migrations.AlterField( - model_name="tag", - name="matching_algorithm", - field=models.PositiveIntegerField( - choices=[ - (0, "None"), - (1, "Any word"), - (2, "All words"), - (3, "Exact match"), - (4, "Regular expression"), - (5, "Fuzzy word"), - (6, "Automatic"), - ], - default=1, - verbose_name="matching algorithm", - ), - ), - ] diff --git a/src/documents/migrations/1034_alter_savedviewfilterrule_rule_type.py b/src/documents/migrations/1034_alter_savedviewfilterrule_rule_type.py deleted file mode 100644 index b648ac839..000000000 --- a/src/documents/migrations/1034_alter_savedviewfilterrule_rule_type.py +++ /dev/null @@ -1,54 +0,0 @@ -# Generated by Django 4.1.5 on 2023-03-15 07:10 - -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1033_alter_documenttype_options_alter_tag_options_and_more"), - ] - - operations = [ - migrations.AlterField( - model_name="savedviewfilterrule", - name="rule_type", - field=models.PositiveIntegerField( - choices=[ - (0, "title contains"), - (1, "content contains"), - (2, "ASN is"), - (3, "correspondent is"), - (4, "document type is"), - (5, "is in inbox"), - (6, "has tag"), - (7, "has any tag"), - (8, "created before"), - (9, "created after"), - (10, "created year is"), - (11, "created month is"), - (12, "created day is"), - (13, "added before"), - (14, "added after"), - (15, "modified before"), - (16, "modified after"), - (17, "does not have tag"), - (18, "does not have ASN"), - (19, "title or content contains"), - (20, "fulltext query"), - (21, "more like this"), - (22, "has tags in"), - (23, "ASN greater than"), - (24, "ASN less than"), - (25, "storage path is"), - (26, "has correspondent in"), - (27, "does not have correspondent in"), - (28, "has document type in"), - (29, "does not have document type in"), - (30, "has storage path in"), - (31, "does not have storage path in"), - ], - verbose_name="rule type", - ), - ), - ] diff --git a/src/documents/migrations/1035_rename_comment_note.py b/src/documents/migrations/1035_rename_comment_note.py deleted file mode 100644 index 9f9aaca94..000000000 --- a/src/documents/migrations/1035_rename_comment_note.py +++ /dev/null @@ -1,62 +0,0 @@ -# Generated by Django 4.1.5 on 2023-03-17 22:15 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ("documents", "1034_alter_savedviewfilterrule_rule_type"), - ] - - operations = [ - migrations.RenameModel( - old_name="Comment", - new_name="Note", - ), - migrations.RenameField(model_name="note", old_name="comment", new_name="note"), - migrations.AlterModelOptions( - name="note", - options={ - "ordering": ("created",), - "verbose_name": "note", - "verbose_name_plural": "notes", - }, - ), - migrations.AlterField( - model_name="note", - name="document", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.CASCADE, - related_name="notes", - to="documents.document", - verbose_name="document", - ), - ), - migrations.AlterField( - model_name="note", - name="note", - field=models.TextField( - blank=True, - help_text="Note for the document", - verbose_name="content", - ), - ), - migrations.AlterField( - model_name="note", - name="user", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="notes", - to=settings.AUTH_USER_MODEL, - verbose_name="user", - ), - ), - ] diff --git a/src/documents/migrations/1036_alter_savedviewfilterrule_rule_type.py b/src/documents/migrations/1036_alter_savedviewfilterrule_rule_type.py deleted file mode 100644 index e65586ad8..000000000 --- a/src/documents/migrations/1036_alter_savedviewfilterrule_rule_type.py +++ /dev/null @@ -1,58 +0,0 @@ -# Generated by Django 4.1.7 on 2023-05-04 04:11 - -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1035_rename_comment_note"), - ] - - operations = [ - migrations.AlterField( - model_name="savedviewfilterrule", - name="rule_type", - field=models.PositiveIntegerField( - choices=[ - (0, "title contains"), - (1, "content contains"), - (2, "ASN is"), - (3, "correspondent is"), - (4, "document type is"), - (5, "is in inbox"), - (6, "has tag"), - (7, "has any tag"), - (8, "created before"), - (9, "created after"), - (10, "created year is"), - (11, "created month is"), - (12, "created day is"), - (13, "added before"), - (14, "added after"), - (15, "modified before"), - (16, "modified after"), - (17, "does not have tag"), - (18, "does not have ASN"), - (19, "title or content contains"), - (20, "fulltext query"), - (21, "more like this"), - (22, "has tags in"), - (23, "ASN greater than"), - (24, "ASN less than"), - (25, "storage path is"), - (26, "has correspondent in"), - (27, "does not have correspondent in"), - (28, "has document type in"), - (29, "does not have document type in"), - (30, "has storage path in"), - (31, "does not have storage path in"), - (32, "owner is"), - (33, "has owner in"), - (34, "does not have owner"), - (35, "does not have owner in"), - ], - verbose_name="rule type", - ), - ), - ] diff --git a/src/documents/migrations/1037_webp_encrypted_thumbnail_conversion.py b/src/documents/migrations/1037_webp_encrypted_thumbnail_conversion.py deleted file mode 100644 index 13996132f..000000000 --- a/src/documents/migrations/1037_webp_encrypted_thumbnail_conversion.py +++ /dev/null @@ -1,164 +0,0 @@ -# Generated by Django 4.1.9 on 2023-06-29 19:29 -import logging -import multiprocessing.pool -import shutil -import tempfile -import time -from pathlib import Path - -import gnupg -from django.conf import settings -from django.db import migrations - -from documents.parsers import run_convert - -logger = logging.getLogger("paperless.migrations") - - -def _do_convert(work_package) -> None: - ( - existing_encrypted_thumbnail, - converted_encrypted_thumbnail, - passphrase, - ) = work_package - - try: - gpg = gnupg.GPG(gnupghome=settings.GNUPG_HOME) - - logger.info(f"Decrypting thumbnail: {existing_encrypted_thumbnail}") - - # Decrypt png - decrypted_thumbnail = existing_encrypted_thumbnail.with_suffix("").resolve() - - with existing_encrypted_thumbnail.open("rb") as existing_encrypted_file: - raw_thumb = gpg.decrypt_file( - existing_encrypted_file, - passphrase=passphrase, - always_trust=True, - ).data - with Path(decrypted_thumbnail).open("wb") as decrypted_file: - decrypted_file.write(raw_thumb) - - converted_decrypted_thumbnail = Path( - str(converted_encrypted_thumbnail).replace("webp.gpg", "webp"), - ).resolve() - - logger.info(f"Converting decrypted thumbnail: {decrypted_thumbnail}") - - # Convert to webp - run_convert( - density=300, - scale="500x5000>", - alpha="remove", - strip=True, - trim=False, - auto_orient=True, - input_file=f"{decrypted_thumbnail}[0]", - output_file=str(converted_decrypted_thumbnail), - ) - - logger.info( - f"Encrypting converted thumbnail: {converted_decrypted_thumbnail}", - ) - - # Encrypt webp - with Path(converted_decrypted_thumbnail).open("rb") as converted_decrypted_file: - encrypted = gpg.encrypt_file( - fileobj_or_path=converted_decrypted_file, - recipients=None, - passphrase=passphrase, - symmetric=True, - always_trust=True, - ).data - - with Path(converted_encrypted_thumbnail).open( - "wb", - ) as converted_encrypted_file: - converted_encrypted_file.write(encrypted) - - # Copy newly created thumbnail to thumbnail directory - shutil.copy(converted_encrypted_thumbnail, existing_encrypted_thumbnail.parent) - - # Remove the existing encrypted PNG version - existing_encrypted_thumbnail.unlink() - - # Remove the decrypted PNG version - decrypted_thumbnail.unlink() - - # Remove the decrypted WebP version - converted_decrypted_thumbnail.unlink() - - logger.info( - "Conversion to WebP completed, " - f"replaced {existing_encrypted_thumbnail.name} with {converted_encrypted_thumbnail.name}", - ) - - except Exception as e: - logger.error(f"Error converting thumbnail (existing file unchanged): {e}") - - -def _convert_encrypted_thumbnails_to_webp(apps, schema_editor) -> None: - start: float = time.time() - - with tempfile.TemporaryDirectory() as tempdir: - work_packages = [] - - if len(list(Path(settings.THUMBNAIL_DIR).glob("*.png.gpg"))) > 0: - passphrase = settings.PASSPHRASE - - if not passphrase: - raise Exception( - "Passphrase not defined, encrypted thumbnails cannot be migrated" - "without this", - ) - - for file in Path(settings.THUMBNAIL_DIR).glob("*.png.gpg"): - existing_thumbnail: Path = file.resolve() - - # Change the existing filename suffix from png to webp - converted_thumbnail_name: str = Path( - str(existing_thumbnail).replace(".png.gpg", ".webp.gpg"), - ).name - - # Create the expected output filename in the tempdir - converted_thumbnail: Path = ( - Path(tempdir) / Path(converted_thumbnail_name) - ).resolve() - - # Package up the necessary info - work_packages.append( - (existing_thumbnail, converted_thumbnail, passphrase), - ) - - if work_packages: - logger.info( - "\n\n" - " This is a one-time only migration to convert thumbnails for all of your\n" - " *encrypted* documents into WebP format. If you have a lot of encrypted documents, \n" - " this may take a while, so a coffee break may be in order." - "\n", - ) - - with multiprocessing.pool.Pool( - processes=min(multiprocessing.cpu_count(), 4), - maxtasksperchild=4, - ) as pool: - pool.map(_do_convert, work_packages) - - end: float = time.time() - duration: float = end - start - - logger.info(f"Conversion completed in {duration:.3f}s") - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1036_alter_savedviewfilterrule_rule_type"), - ] - - operations = [ - migrations.RunPython( - code=_convert_encrypted_thumbnails_to_webp, - reverse_code=migrations.RunPython.noop, - ), - ] diff --git a/src/documents/migrations/1038_sharelink.py b/src/documents/migrations/1038_sharelink.py deleted file mode 100644 index fa2860b6f..000000000 --- a/src/documents/migrations/1038_sharelink.py +++ /dev/null @@ -1,126 +0,0 @@ -# Generated by Django 4.1.10 on 2023-08-14 14:51 - -import django.db.models.deletion -import django.utils.timezone -from django.conf import settings -from django.contrib.auth.management import create_permissions -from django.contrib.auth.models import Group -from django.contrib.auth.models import Permission -from django.contrib.auth.models import User -from django.db import migrations -from django.db import models -from django.db.models import Q - - -def add_sharelink_permissions(apps, schema_editor): - # create permissions without waiting for post_migrate signal - for app_config in apps.get_app_configs(): - app_config.models_module = True - create_permissions(app_config, apps=apps, verbosity=0) - app_config.models_module = None - - add_permission = Permission.objects.get(codename="add_document") - sharelink_permissions = Permission.objects.filter(codename__contains="sharelink") - - for user in User.objects.filter(Q(user_permissions=add_permission)).distinct(): - user.user_permissions.add(*sharelink_permissions) - - for group in Group.objects.filter(Q(permissions=add_permission)).distinct(): - group.permissions.add(*sharelink_permissions) - - -def remove_sharelink_permissions(apps, schema_editor): - sharelink_permissions = Permission.objects.filter(codename__contains="sharelink") - - for user in User.objects.all(): - user.user_permissions.remove(*sharelink_permissions) - - for group in Group.objects.all(): - group.permissions.remove(*sharelink_permissions) - - -class Migration(migrations.Migration): - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ("documents", "1037_webp_encrypted_thumbnail_conversion"), - ] - - operations = [ - migrations.CreateModel( - name="ShareLink", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "created", - models.DateTimeField( - blank=True, - db_index=True, - default=django.utils.timezone.now, - editable=False, - verbose_name="created", - ), - ), - ( - "expiration", - models.DateTimeField( - blank=True, - db_index=True, - null=True, - verbose_name="expiration", - ), - ), - ( - "slug", - models.SlugField( - blank=True, - editable=False, - unique=True, - verbose_name="slug", - ), - ), - ( - "file_version", - models.CharField( - choices=[("archive", "Archive"), ("original", "Original")], - default="archive", - max_length=50, - ), - ), - ( - "document", - models.ForeignKey( - blank=True, - on_delete=django.db.models.deletion.CASCADE, - related_name="share_links", - to="documents.document", - verbose_name="document", - ), - ), - ( - "owner", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="share_links", - to=settings.AUTH_USER_MODEL, - verbose_name="owner", - ), - ), - ], - options={ - "verbose_name": "share link", - "verbose_name_plural": "share links", - "ordering": ("created",), - }, - ), - migrations.RunPython(add_sharelink_permissions, remove_sharelink_permissions), - ] diff --git a/src/documents/migrations/1039_consumptiontemplate.py b/src/documents/migrations/1039_consumptiontemplate.py deleted file mode 100644 index cf8b9fd91..000000000 --- a/src/documents/migrations/1039_consumptiontemplate.py +++ /dev/null @@ -1,219 +0,0 @@ -# Generated by Django 4.1.11 on 2023-09-16 18:04 - -import django.db.models.deletion -import multiselectfield.db.fields -from django.conf import settings -from django.contrib.auth.management import create_permissions -from django.contrib.auth.models import Group -from django.contrib.auth.models import Permission -from django.contrib.auth.models import User -from django.db import migrations -from django.db import models -from django.db.models import Q - - -def add_consumptiontemplate_permissions(apps, schema_editor): - # create permissions without waiting for post_migrate signal - for app_config in apps.get_app_configs(): - app_config.models_module = True - create_permissions(app_config, apps=apps, verbosity=0) - app_config.models_module = None - - add_permission = Permission.objects.get(codename="add_document") - consumptiontemplate_permissions = Permission.objects.filter( - codename__contains="consumptiontemplate", - ) - - for user in User.objects.filter(Q(user_permissions=add_permission)).distinct(): - user.user_permissions.add(*consumptiontemplate_permissions) - - for group in Group.objects.filter(Q(permissions=add_permission)).distinct(): - group.permissions.add(*consumptiontemplate_permissions) - - -def remove_consumptiontemplate_permissions(apps, schema_editor): - consumptiontemplate_permissions = Permission.objects.filter( - codename__contains="consumptiontemplate", - ) - - for user in User.objects.all(): - user.user_permissions.remove(*consumptiontemplate_permissions) - - for group in Group.objects.all(): - group.permissions.remove(*consumptiontemplate_permissions) - - -class Migration(migrations.Migration): - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ("auth", "0012_alter_user_first_name_max_length"), - ("documents", "1038_sharelink"), - ("paperless_mail", "0021_alter_mailaccount_password"), - ] - - operations = [ - migrations.CreateModel( - name="ConsumptionTemplate", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "name", - models.CharField(max_length=256, unique=True, verbose_name="name"), - ), - ("order", models.IntegerField(default=0, verbose_name="order")), - ( - "sources", - multiselectfield.db.fields.MultiSelectField( - choices=[ - (1, "Consume Folder"), - (2, "Api Upload"), - (3, "Mail Fetch"), - ], - default="1,2,3", - max_length=3, - ), - ), - ( - "filter_path", - models.CharField( - blank=True, - help_text="Only consume documents with a path that matches this if specified. Wildcards specified as * are allowed. Case insensitive.", - max_length=256, - null=True, - verbose_name="filter path", - ), - ), - ( - "filter_filename", - models.CharField( - blank=True, - help_text="Only consume documents which entirely match this filename if specified. Wildcards such as *.pdf or *invoice* are allowed. Case insensitive.", - max_length=256, - null=True, - verbose_name="filter filename", - ), - ), - ( - "filter_mailrule", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to="paperless_mail.mailrule", - verbose_name="filter documents from this mail rule", - ), - ), - ( - "assign_change_groups", - models.ManyToManyField( - blank=True, - related_name="+", - to="auth.group", - verbose_name="grant change permissions to these groups", - ), - ), - ( - "assign_change_users", - models.ManyToManyField( - blank=True, - related_name="+", - to=settings.AUTH_USER_MODEL, - verbose_name="grant change permissions to these users", - ), - ), - ( - "assign_correspondent", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to="documents.correspondent", - verbose_name="assign this correspondent", - ), - ), - ( - "assign_document_type", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to="documents.documenttype", - verbose_name="assign this document type", - ), - ), - ( - "assign_owner", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="+", - to=settings.AUTH_USER_MODEL, - verbose_name="assign this owner", - ), - ), - ( - "assign_storage_path", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to="documents.storagepath", - verbose_name="assign this storage path", - ), - ), - ( - "assign_tags", - models.ManyToManyField( - blank=True, - to="documents.tag", - verbose_name="assign this tag", - ), - ), - ( - "assign_title", - models.CharField( - blank=True, - help_text="Assign a document title, can include some placeholders, see documentation.", - max_length=256, - null=True, - verbose_name="assign title", - ), - ), - ( - "assign_view_groups", - models.ManyToManyField( - blank=True, - related_name="+", - to="auth.group", - verbose_name="grant view permissions to these groups", - ), - ), - ( - "assign_view_users", - models.ManyToManyField( - blank=True, - related_name="+", - to=settings.AUTH_USER_MODEL, - verbose_name="grant view permissions to these users", - ), - ), - ], - options={ - "verbose_name": "consumption template", - "verbose_name_plural": "consumption templates", - }, - ), - migrations.RunPython( - add_consumptiontemplate_permissions, - remove_consumptiontemplate_permissions, - ), - ] diff --git a/src/documents/migrations/1040_customfield_customfieldinstance_and_more.py b/src/documents/migrations/1040_customfield_customfieldinstance_and_more.py deleted file mode 100644 index ecd715a57..000000000 --- a/src/documents/migrations/1040_customfield_customfieldinstance_and_more.py +++ /dev/null @@ -1,171 +0,0 @@ -# Generated by Django 4.2.6 on 2023-11-02 17:38 - -import django.db.models.deletion -import django.utils.timezone -from django.contrib.auth.management import create_permissions -from django.contrib.auth.models import Group -from django.contrib.auth.models import Permission -from django.contrib.auth.models import User -from django.db import migrations -from django.db import models -from django.db.models import Q - - -def add_customfield_permissions(apps, schema_editor): - # create permissions without waiting for post_migrate signal - for app_config in apps.get_app_configs(): - app_config.models_module = True - create_permissions(app_config, apps=apps, verbosity=0) - app_config.models_module = None - - add_permission = Permission.objects.get(codename="add_document") - customfield_permissions = Permission.objects.filter( - codename__contains="customfield", - ) - - for user in User.objects.filter(Q(user_permissions=add_permission)).distinct(): - user.user_permissions.add(*customfield_permissions) - - for group in Group.objects.filter(Q(permissions=add_permission)).distinct(): - group.permissions.add(*customfield_permissions) - - -def remove_customfield_permissions(apps, schema_editor): - customfield_permissions = Permission.objects.filter( - codename__contains="customfield", - ) - - for user in User.objects.all(): - user.user_permissions.remove(*customfield_permissions) - - for group in Group.objects.all(): - group.permissions.remove(*customfield_permissions) - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1039_consumptiontemplate"), - ] - - operations = [ - migrations.CreateModel( - name="CustomField", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "created", - models.DateTimeField( - db_index=True, - default=django.utils.timezone.now, - editable=False, - verbose_name="created", - ), - ), - ("name", models.CharField(max_length=128)), - ( - "data_type", - models.CharField( - choices=[ - ("string", "String"), - ("url", "URL"), - ("date", "Date"), - ("boolean", "Boolean"), - ("integer", "Integer"), - ("float", "Float"), - ("monetary", "Monetary"), - ], - editable=False, - max_length=50, - verbose_name="data type", - ), - ), - ], - options={ - "verbose_name": "custom field", - "verbose_name_plural": "custom fields", - "ordering": ("created",), - }, - ), - migrations.CreateModel( - name="CustomFieldInstance", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "created", - models.DateTimeField( - db_index=True, - default=django.utils.timezone.now, - editable=False, - verbose_name="created", - ), - ), - ("value_text", models.CharField(max_length=128, null=True)), - ("value_bool", models.BooleanField(null=True)), - ("value_url", models.URLField(null=True)), - ("value_date", models.DateField(null=True)), - ("value_int", models.IntegerField(null=True)), - ("value_float", models.FloatField(null=True)), - ( - "value_monetary", - models.DecimalField(decimal_places=2, max_digits=12, null=True), - ), - ( - "document", - models.ForeignKey( - editable=False, - on_delete=django.db.models.deletion.CASCADE, - related_name="custom_fields", - to="documents.document", - ), - ), - ( - "field", - models.ForeignKey( - editable=False, - on_delete=django.db.models.deletion.CASCADE, - related_name="fields", - to="documents.customfield", - ), - ), - ], - options={ - "verbose_name": "custom field instance", - "verbose_name_plural": "custom field instances", - "ordering": ("created",), - }, - ), - migrations.AddConstraint( - model_name="customfield", - constraint=models.UniqueConstraint( - fields=("name",), - name="documents_customfield_unique_name", - ), - ), - migrations.AddConstraint( - model_name="customfieldinstance", - constraint=models.UniqueConstraint( - fields=("document", "field"), - name="documents_customfieldinstance_unique_document_field", - ), - ), - migrations.RunPython( - add_customfield_permissions, - remove_customfield_permissions, - ), - ] diff --git a/src/documents/migrations/1041_alter_consumptiontemplate_sources.py b/src/documents/migrations/1041_alter_consumptiontemplate_sources.py deleted file mode 100644 index c96dc53cf..000000000 --- a/src/documents/migrations/1041_alter_consumptiontemplate_sources.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 4.2.7 on 2023-11-30 14:29 - -import multiselectfield.db.fields -from django.db import migrations - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1040_customfield_customfieldinstance_and_more"), - ] - - operations = [ - migrations.AlterField( - model_name="consumptiontemplate", - name="sources", - field=multiselectfield.db.fields.MultiSelectField( - choices=[(1, "Consume Folder"), (2, "Api Upload"), (3, "Mail Fetch")], - default="1,2,3", - max_length=5, - ), - ), - ] diff --git a/src/documents/migrations/1042_consumptiontemplate_assign_custom_fields_and_more.py b/src/documents/migrations/1042_consumptiontemplate_assign_custom_fields_and_more.py deleted file mode 100644 index ffd0dbefa..000000000 --- a/src/documents/migrations/1042_consumptiontemplate_assign_custom_fields_and_more.py +++ /dev/null @@ -1,47 +0,0 @@ -# Generated by Django 4.2.7 on 2023-12-04 04:03 - -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1041_alter_consumptiontemplate_sources"), - ] - - operations = [ - migrations.AddField( - model_name="consumptiontemplate", - name="assign_custom_fields", - field=models.ManyToManyField( - blank=True, - related_name="+", - to="documents.customfield", - verbose_name="assign these custom fields", - ), - ), - migrations.AddField( - model_name="customfieldinstance", - name="value_document_ids", - field=models.JSONField(null=True), - ), - migrations.AlterField( - model_name="customfield", - name="data_type", - field=models.CharField( - choices=[ - ("string", "String"), - ("url", "URL"), - ("date", "Date"), - ("boolean", "Boolean"), - ("integer", "Integer"), - ("float", "Float"), - ("monetary", "Monetary"), - ("documentlink", "Document Link"), - ], - editable=False, - max_length=50, - verbose_name="data type", - ), - ), - ] diff --git a/src/documents/migrations/1043_alter_savedviewfilterrule_rule_type.py b/src/documents/migrations/1043_alter_savedviewfilterrule_rule_type.py deleted file mode 100644 index bd62673df..000000000 --- a/src/documents/migrations/1043_alter_savedviewfilterrule_rule_type.py +++ /dev/null @@ -1,60 +0,0 @@ -# Generated by Django 4.2.7 on 2023-12-09 18:13 - -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1042_consumptiontemplate_assign_custom_fields_and_more"), - ] - - operations = [ - migrations.AlterField( - model_name="savedviewfilterrule", - name="rule_type", - field=models.PositiveIntegerField( - choices=[ - (0, "title contains"), - (1, "content contains"), - (2, "ASN is"), - (3, "correspondent is"), - (4, "document type is"), - (5, "is in inbox"), - (6, "has tag"), - (7, "has any tag"), - (8, "created before"), - (9, "created after"), - (10, "created year is"), - (11, "created month is"), - (12, "created day is"), - (13, "added before"), - (14, "added after"), - (15, "modified before"), - (16, "modified after"), - (17, "does not have tag"), - (18, "does not have ASN"), - (19, "title or content contains"), - (20, "fulltext query"), - (21, "more like this"), - (22, "has tags in"), - (23, "ASN greater than"), - (24, "ASN less than"), - (25, "storage path is"), - (26, "has correspondent in"), - (27, "does not have correspondent in"), - (28, "has document type in"), - (29, "does not have document type in"), - (30, "has storage path in"), - (31, "does not have storage path in"), - (32, "owner is"), - (33, "has owner in"), - (34, "does not have owner"), - (35, "does not have owner in"), - (36, "has custom field value"), - (37, "is shared by me"), - ], - verbose_name="rule type", - ), - ), - ] diff --git a/src/documents/migrations/1044_workflow_workflowaction_workflowtrigger_and_more.py b/src/documents/migrations/1044_workflow_workflowaction_workflowtrigger_and_more.py deleted file mode 100644 index 2cdd631bb..000000000 --- a/src/documents/migrations/1044_workflow_workflowaction_workflowtrigger_and_more.py +++ /dev/null @@ -1,524 +0,0 @@ -# Generated by Django 4.2.7 on 2023-12-23 22:51 - -import django.db.models.deletion -import multiselectfield.db.fields -from django.conf import settings -from django.contrib.auth.management import create_permissions -from django.db import migrations -from django.db import models -from django.db import transaction -from django.db.models import Q - - -def add_workflow_permissions(apps, schema_editor): - app_name = "auth" - User = apps.get_model(app_label=app_name, model_name="User") - Group = apps.get_model(app_label=app_name, model_name="Group") - Permission = apps.get_model(app_label=app_name, model_name="Permission") - # create permissions without waiting for post_migrate signal - for app_config in apps.get_app_configs(): - app_config.models_module = True - create_permissions(app_config, apps=apps, verbosity=0) - app_config.models_module = None - - add_permission = Permission.objects.get(codename="add_document") - workflow_permissions = Permission.objects.filter( - codename__contains="workflow", - ) - - for user in User.objects.filter(Q(user_permissions=add_permission)).distinct(): - user.user_permissions.add(*workflow_permissions) - - for group in Group.objects.filter(Q(permissions=add_permission)).distinct(): - group.permissions.add(*workflow_permissions) - - -def remove_workflow_permissions(apps, schema_editor): - app_name = "auth" - User = apps.get_model(app_label=app_name, model_name="User") - Group = apps.get_model(app_label=app_name, model_name="Group") - Permission = apps.get_model(app_label=app_name, model_name="Permission") - workflow_permissions = Permission.objects.filter( - codename__contains="workflow", - ) - - for user in User.objects.all(): - user.user_permissions.remove(*workflow_permissions) - - for group in Group.objects.all(): - group.permissions.remove(*workflow_permissions) - - -def migrate_consumption_templates(apps, schema_editor): - """ - Migrate consumption templates to workflows. At this point ConsumptionTemplate still exists - but objects are not returned as their true model so we have to manually do that - """ - app_name = "documents" - - ConsumptionTemplate = apps.get_model( - app_label=app_name, - model_name="ConsumptionTemplate", - ) - Workflow = apps.get_model(app_label=app_name, model_name="Workflow") - WorkflowAction = apps.get_model(app_label=app_name, model_name="WorkflowAction") - WorkflowTrigger = apps.get_model(app_label=app_name, model_name="WorkflowTrigger") - DocumentType = apps.get_model(app_label=app_name, model_name="DocumentType") - Correspondent = apps.get_model(app_label=app_name, model_name="Correspondent") - StoragePath = apps.get_model(app_label=app_name, model_name="StoragePath") - Tag = apps.get_model(app_label=app_name, model_name="Tag") - CustomField = apps.get_model(app_label=app_name, model_name="CustomField") - MailRule = apps.get_model(app_label="paperless_mail", model_name="MailRule") - User = apps.get_model(app_label="auth", model_name="User") - Group = apps.get_model(app_label="auth", model_name="Group") - - with transaction.atomic(): - for template in ConsumptionTemplate.objects.all(): - trigger = WorkflowTrigger( - type=1, # WorkflowTriggerType.CONSUMPTION - sources=template.sources, - filter_path=template.filter_path, - filter_filename=template.filter_filename, - ) - if template.filter_mailrule is not None: - trigger.filter_mailrule = MailRule.objects.get( - id=template.filter_mailrule.id, - ) - trigger.save() - - action = WorkflowAction.objects.create( - assign_title=template.assign_title, - ) - if template.assign_document_type is not None: - action.assign_document_type = DocumentType.objects.get( - id=template.assign_document_type.id, - ) - if template.assign_correspondent is not None: - action.assign_correspondent = Correspondent.objects.get( - id=template.assign_correspondent.id, - ) - if template.assign_storage_path is not None: - action.assign_storage_path = StoragePath.objects.get( - id=template.assign_storage_path.id, - ) - if template.assign_owner is not None: - action.assign_owner = User.objects.get(id=template.assign_owner.id) - if template.assign_tags is not None: - action.assign_tags.set( - Tag.objects.filter( - id__in=[t.id for t in template.assign_tags.all()], - ).all(), - ) - if template.assign_view_users is not None: - action.assign_view_users.set( - User.objects.filter( - id__in=[u.id for u in template.assign_view_users.all()], - ).all(), - ) - if template.assign_view_groups is not None: - action.assign_view_groups.set( - Group.objects.filter( - id__in=[g.id for g in template.assign_view_groups.all()], - ).all(), - ) - if template.assign_change_users is not None: - action.assign_change_users.set( - User.objects.filter( - id__in=[u.id for u in template.assign_change_users.all()], - ).all(), - ) - if template.assign_change_groups is not None: - action.assign_change_groups.set( - Group.objects.filter( - id__in=[g.id for g in template.assign_change_groups.all()], - ).all(), - ) - if template.assign_custom_fields is not None: - action.assign_custom_fields.set( - CustomField.objects.filter( - id__in=[cf.id for cf in template.assign_custom_fields.all()], - ).all(), - ) - action.save() - - workflow = Workflow.objects.create( - name=template.name, - order=template.order, - ) - workflow.triggers.set([trigger]) - workflow.actions.set([action]) - workflow.save() - - -def unmigrate_consumption_templates(apps, schema_editor): - app_name = "documents" - - ConsumptionTemplate = apps.get_model( - app_label=app_name, - model_name="ConsumptionTemplate", - ) - Workflow = apps.get_model(app_label=app_name, model_name="Workflow") - - for workflow in Workflow.objects.all(): - template = ConsumptionTemplate.objects.create( - name=workflow.name, - order=workflow.order, - sources=workflow.triggers.first().sources, - filter_path=workflow.triggers.first().filter_path, - filter_filename=workflow.triggers.first().filter_filename, - filter_mailrule=workflow.triggers.first().filter_mailrule, - assign_title=workflow.actions.first().assign_title, - assign_document_type=workflow.actions.first().assign_document_type, - assign_correspondent=workflow.actions.first().assign_correspondent, - assign_storage_path=workflow.actions.first().assign_storage_path, - assign_owner=workflow.actions.first().assign_owner, - ) - template.assign_tags.set(workflow.actions.first().assign_tags.all()) - template.assign_view_users.set(workflow.actions.first().assign_view_users.all()) - template.assign_view_groups.set( - workflow.actions.first().assign_view_groups.all(), - ) - template.assign_change_users.set( - workflow.actions.first().assign_change_users.all(), - ) - template.assign_change_groups.set( - workflow.actions.first().assign_change_groups.all(), - ) - template.assign_custom_fields.set( - workflow.actions.first().assign_custom_fields.all(), - ) - template.save() - - -def delete_consumption_template_content_type(apps, schema_editor): - with transaction.atomic(): - apps.get_model("contenttypes", "ContentType").objects.filter( - app_label="documents", - model="consumptiontemplate", - ).delete() - - -def undelete_consumption_template_content_type(apps, schema_editor): - apps.get_model("contenttypes", "ContentType").objects.create( - app_label="documents", - model="consumptiontemplate", - ) - - -class Migration(migrations.Migration): - dependencies = [ - ("paperless_mail", "0023_remove_mailrule_filter_attachment_filename_and_more"), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ("auth", "0012_alter_user_first_name_max_length"), - ("documents", "1043_alter_savedviewfilterrule_rule_type"), - ] - - operations = [ - migrations.CreateModel( - name="Workflow", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "name", - models.CharField(max_length=256, unique=True, verbose_name="name"), - ), - ("order", models.IntegerField(default=0, verbose_name="order")), - ( - "enabled", - models.BooleanField(default=True, verbose_name="enabled"), - ), - ], - ), - migrations.CreateModel( - name="WorkflowAction", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "type", - models.PositiveIntegerField( - choices=[(1, "Assignment")], - default=1, - verbose_name="Workflow Action Type", - ), - ), - ( - "assign_title", - models.CharField( - blank=True, - help_text="Assign a document title, can include some placeholders, see documentation.", - max_length=256, - null=True, - verbose_name="assign title", - ), - ), - ( - "assign_change_groups", - models.ManyToManyField( - blank=True, - related_name="+", - to="auth.group", - verbose_name="grant change permissions to these groups", - ), - ), - ( - "assign_change_users", - models.ManyToManyField( - blank=True, - related_name="+", - to=settings.AUTH_USER_MODEL, - verbose_name="grant change permissions to these users", - ), - ), - ( - "assign_correspondent", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to="documents.correspondent", - verbose_name="assign this correspondent", - ), - ), - ( - "assign_custom_fields", - models.ManyToManyField( - blank=True, - related_name="+", - to="documents.customfield", - verbose_name="assign these custom fields", - ), - ), - ( - "assign_document_type", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to="documents.documenttype", - verbose_name="assign this document type", - ), - ), - ( - "assign_owner", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="+", - to=settings.AUTH_USER_MODEL, - verbose_name="assign this owner", - ), - ), - ( - "assign_storage_path", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to="documents.storagepath", - verbose_name="assign this storage path", - ), - ), - ( - "assign_tags", - models.ManyToManyField( - blank=True, - to="documents.tag", - verbose_name="assign this tag", - ), - ), - ( - "assign_view_groups", - models.ManyToManyField( - blank=True, - related_name="+", - to="auth.group", - verbose_name="grant view permissions to these groups", - ), - ), - ( - "assign_view_users", - models.ManyToManyField( - blank=True, - related_name="+", - to=settings.AUTH_USER_MODEL, - verbose_name="grant view permissions to these users", - ), - ), - ], - options={ - "verbose_name": "workflow action", - "verbose_name_plural": "workflow actions", - }, - ), - migrations.CreateModel( - name="WorkflowTrigger", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "type", - models.PositiveIntegerField( - choices=[ - (1, "Consumption Started"), - (2, "Document Added"), - (3, "Document Updated"), - ], - default=1, - verbose_name="Workflow Trigger Type", - ), - ), - ( - "sources", - multiselectfield.db.fields.MultiSelectField( - choices=[ - (1, "Consume Folder"), - (2, "Api Upload"), - (3, "Mail Fetch"), - ], - default="1,2,3", - max_length=5, - ), - ), - ( - "filter_path", - models.CharField( - blank=True, - help_text="Only consume documents with a path that matches this if specified. Wildcards specified as * are allowed. Case insensitive.", - max_length=256, - null=True, - verbose_name="filter path", - ), - ), - ( - "filter_filename", - models.CharField( - blank=True, - help_text="Only consume documents which entirely match this filename if specified. Wildcards such as *.pdf or *invoice* are allowed. Case insensitive.", - max_length=256, - null=True, - verbose_name="filter filename", - ), - ), - ( - "filter_mailrule", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to="paperless_mail.mailrule", - verbose_name="filter documents from this mail rule", - ), - ), - ( - "matching_algorithm", - models.PositiveIntegerField( - choices=[ - (0, "None"), - (1, "Any word"), - (2, "All words"), - (3, "Exact match"), - (4, "Regular expression"), - (5, "Fuzzy word"), - ], - default=0, - verbose_name="matching algorithm", - ), - ), - ( - "match", - models.CharField(blank=True, max_length=256, verbose_name="match"), - ), - ( - "is_insensitive", - models.BooleanField(default=True, verbose_name="is insensitive"), - ), - ( - "filter_has_tags", - models.ManyToManyField( - blank=True, - to="documents.tag", - verbose_name="has these tag(s)", - ), - ), - ( - "filter_has_document_type", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to="documents.documenttype", - verbose_name="has this document type", - ), - ), - ( - "filter_has_correspondent", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to="documents.correspondent", - verbose_name="has this correspondent", - ), - ), - ], - options={ - "verbose_name": "workflow trigger", - "verbose_name_plural": "workflow triggers", - }, - ), - migrations.RunPython( - add_workflow_permissions, - remove_workflow_permissions, - ), - migrations.AddField( - model_name="workflow", - name="actions", - field=models.ManyToManyField( - related_name="workflows", - to="documents.workflowaction", - verbose_name="actions", - ), - ), - migrations.AddField( - model_name="workflow", - name="triggers", - field=models.ManyToManyField( - related_name="workflows", - to="documents.workflowtrigger", - verbose_name="triggers", - ), - ), - migrations.RunPython( - migrate_consumption_templates, - unmigrate_consumption_templates, - ), - migrations.DeleteModel("ConsumptionTemplate"), - migrations.RunPython( - delete_consumption_template_content_type, - undelete_consumption_template_content_type, - ), - ] diff --git a/src/documents/migrations/1045_alter_customfieldinstance_value_monetary.py b/src/documents/migrations/1045_alter_customfieldinstance_value_monetary.py deleted file mode 100644 index 597fbb7f9..000000000 --- a/src/documents/migrations/1045_alter_customfieldinstance_value_monetary.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 4.2.10 on 2024-02-22 03:52 - -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1044_workflow_workflowaction_workflowtrigger_and_more"), - ] - - operations = [ - migrations.AlterField( - model_name="customfieldinstance", - name="value_monetary", - field=models.CharField(max_length=128, null=True), - ), - ] diff --git a/src/documents/migrations/1045_alter_customfieldinstance_value_monetary_squashed_1049_document_deleted_at_document_restored_at.py b/src/documents/migrations/1045_alter_customfieldinstance_value_monetary_squashed_1049_document_deleted_at_document_restored_at.py deleted file mode 100644 index 2987e4812..000000000 --- a/src/documents/migrations/1045_alter_customfieldinstance_value_monetary_squashed_1049_document_deleted_at_document_restored_at.py +++ /dev/null @@ -1,331 +0,0 @@ -# Generated by Django 4.2.13 on 2024-06-28 19:39 - -import django.core.validators -import django.db.models.deletion -from django.conf import settings -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - replaces = [ - ("documents", "1045_alter_customfieldinstance_value_monetary"), - ("documents", "1046_workflowaction_remove_all_correspondents_and_more"), - ("documents", "1047_savedview_display_mode_and_more"), - ("documents", "1048_alter_savedviewfilterrule_rule_type"), - ("documents", "1049_document_deleted_at_document_restored_at"), - ] - - dependencies = [ - ("documents", "1044_workflow_workflowaction_workflowtrigger_and_more"), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ("auth", "0012_alter_user_first_name_max_length"), - ] - - operations = [ - migrations.AlterField( - model_name="customfieldinstance", - name="value_monetary", - field=models.CharField(max_length=128, null=True), - ), - migrations.AddField( - model_name="workflowaction", - name="remove_all_correspondents", - field=models.BooleanField( - default=False, - verbose_name="remove all correspondents", - ), - ), - migrations.AddField( - model_name="workflowaction", - name="remove_all_custom_fields", - field=models.BooleanField( - default=False, - verbose_name="remove all custom fields", - ), - ), - migrations.AddField( - model_name="workflowaction", - name="remove_all_document_types", - field=models.BooleanField( - default=False, - verbose_name="remove all document types", - ), - ), - migrations.AddField( - model_name="workflowaction", - name="remove_all_owners", - field=models.BooleanField(default=False, verbose_name="remove all owners"), - ), - migrations.AddField( - model_name="workflowaction", - name="remove_all_permissions", - field=models.BooleanField( - default=False, - verbose_name="remove all permissions", - ), - ), - migrations.AddField( - model_name="workflowaction", - name="remove_all_storage_paths", - field=models.BooleanField( - default=False, - verbose_name="remove all storage paths", - ), - ), - migrations.AddField( - model_name="workflowaction", - name="remove_all_tags", - field=models.BooleanField(default=False, verbose_name="remove all tags"), - ), - migrations.AddField( - model_name="workflowaction", - name="remove_change_groups", - field=models.ManyToManyField( - blank=True, - related_name="+", - to="auth.group", - verbose_name="remove change permissions for these groups", - ), - ), - migrations.AddField( - model_name="workflowaction", - name="remove_change_users", - field=models.ManyToManyField( - blank=True, - related_name="+", - to=settings.AUTH_USER_MODEL, - verbose_name="remove change permissions for these users", - ), - ), - migrations.AddField( - model_name="workflowaction", - name="remove_correspondents", - field=models.ManyToManyField( - blank=True, - related_name="+", - to="documents.correspondent", - verbose_name="remove these correspondent(s)", - ), - ), - migrations.AddField( - model_name="workflowaction", - name="remove_custom_fields", - field=models.ManyToManyField( - blank=True, - related_name="+", - to="documents.customfield", - verbose_name="remove these custom fields", - ), - ), - migrations.AddField( - model_name="workflowaction", - name="remove_document_types", - field=models.ManyToManyField( - blank=True, - related_name="+", - to="documents.documenttype", - verbose_name="remove these document type(s)", - ), - ), - migrations.AddField( - model_name="workflowaction", - name="remove_owners", - field=models.ManyToManyField( - blank=True, - related_name="+", - to=settings.AUTH_USER_MODEL, - verbose_name="remove these owner(s)", - ), - ), - migrations.AddField( - model_name="workflowaction", - name="remove_storage_paths", - field=models.ManyToManyField( - blank=True, - related_name="+", - to="documents.storagepath", - verbose_name="remove these storage path(s)", - ), - ), - migrations.AddField( - model_name="workflowaction", - name="remove_tags", - field=models.ManyToManyField( - blank=True, - related_name="+", - to="documents.tag", - verbose_name="remove these tag(s)", - ), - ), - migrations.AddField( - model_name="workflowaction", - name="remove_view_groups", - field=models.ManyToManyField( - blank=True, - related_name="+", - to="auth.group", - verbose_name="remove view permissions for these groups", - ), - ), - migrations.AddField( - model_name="workflowaction", - name="remove_view_users", - field=models.ManyToManyField( - blank=True, - related_name="+", - to=settings.AUTH_USER_MODEL, - verbose_name="remove view permissions for these users", - ), - ), - migrations.AlterField( - model_name="workflowaction", - name="assign_correspondent", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="+", - to="documents.correspondent", - verbose_name="assign this correspondent", - ), - ), - migrations.AlterField( - model_name="workflowaction", - name="assign_document_type", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="+", - to="documents.documenttype", - verbose_name="assign this document type", - ), - ), - migrations.AlterField( - model_name="workflowaction", - name="assign_storage_path", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="+", - to="documents.storagepath", - verbose_name="assign this storage path", - ), - ), - migrations.AlterField( - model_name="workflowaction", - name="assign_tags", - field=models.ManyToManyField( - blank=True, - related_name="+", - to="documents.tag", - verbose_name="assign this tag", - ), - ), - migrations.AlterField( - model_name="workflowaction", - name="type", - field=models.PositiveIntegerField( - choices=[(1, "Assignment"), (2, "Removal")], - default=1, - verbose_name="Workflow Action Type", - ), - ), - migrations.AddField( - model_name="savedview", - name="display_mode", - field=models.CharField( - blank=True, - choices=[ - ("table", "Table"), - ("smallCards", "Small Cards"), - ("largeCards", "Large Cards"), - ], - max_length=128, - null=True, - verbose_name="View display mode", - ), - ), - migrations.AddField( - model_name="savedview", - name="page_size", - field=models.PositiveIntegerField( - blank=True, - null=True, - validators=[django.core.validators.MinValueValidator(1)], - verbose_name="View page size", - ), - ), - migrations.AddField( - model_name="savedview", - name="display_fields", - field=models.JSONField( - blank=True, - null=True, - verbose_name="Document display fields", - ), - ), - migrations.AlterField( - model_name="savedviewfilterrule", - name="rule_type", - field=models.PositiveIntegerField( - choices=[ - (0, "title contains"), - (1, "content contains"), - (2, "ASN is"), - (3, "correspondent is"), - (4, "document type is"), - (5, "is in inbox"), - (6, "has tag"), - (7, "has any tag"), - (8, "created before"), - (9, "created after"), - (10, "created year is"), - (11, "created month is"), - (12, "created day is"), - (13, "added before"), - (14, "added after"), - (15, "modified before"), - (16, "modified after"), - (17, "does not have tag"), - (18, "does not have ASN"), - (19, "title or content contains"), - (20, "fulltext query"), - (21, "more like this"), - (22, "has tags in"), - (23, "ASN greater than"), - (24, "ASN less than"), - (25, "storage path is"), - (26, "has correspondent in"), - (27, "does not have correspondent in"), - (28, "has document type in"), - (29, "does not have document type in"), - (30, "has storage path in"), - (31, "does not have storage path in"), - (32, "owner is"), - (33, "has owner in"), - (34, "does not have owner"), - (35, "does not have owner in"), - (36, "has custom field value"), - (37, "is shared by me"), - (38, "has custom fields"), - (39, "has custom field in"), - (40, "does not have custom field in"), - (41, "does not have custom field"), - ], - verbose_name="rule type", - ), - ), - migrations.AddField( - model_name="document", - name="deleted_at", - field=models.DateTimeField(blank=True, null=True), - ), - migrations.AddField( - model_name="document", - name="restored_at", - field=models.DateTimeField(blank=True, null=True), - ), - ] diff --git a/src/documents/migrations/1046_workflowaction_remove_all_correspondents_and_more.py b/src/documents/migrations/1046_workflowaction_remove_all_correspondents_and_more.py deleted file mode 100644 index 3ab010a3c..000000000 --- a/src/documents/migrations/1046_workflowaction_remove_all_correspondents_and_more.py +++ /dev/null @@ -1,222 +0,0 @@ -# Generated by Django 4.2.10 on 2024-02-21 21:19 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("auth", "0012_alter_user_first_name_max_length"), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ("documents", "1045_alter_customfieldinstance_value_monetary"), - ] - - operations = [ - migrations.AddField( - model_name="workflowaction", - name="remove_all_correspondents", - field=models.BooleanField( - default=False, - verbose_name="remove all correspondents", - ), - ), - migrations.AddField( - model_name="workflowaction", - name="remove_all_custom_fields", - field=models.BooleanField( - default=False, - verbose_name="remove all custom fields", - ), - ), - migrations.AddField( - model_name="workflowaction", - name="remove_all_document_types", - field=models.BooleanField( - default=False, - verbose_name="remove all document types", - ), - ), - migrations.AddField( - model_name="workflowaction", - name="remove_all_owners", - field=models.BooleanField(default=False, verbose_name="remove all owners"), - ), - migrations.AddField( - model_name="workflowaction", - name="remove_all_permissions", - field=models.BooleanField( - default=False, - verbose_name="remove all permissions", - ), - ), - migrations.AddField( - model_name="workflowaction", - name="remove_all_storage_paths", - field=models.BooleanField( - default=False, - verbose_name="remove all storage paths", - ), - ), - migrations.AddField( - model_name="workflowaction", - name="remove_all_tags", - field=models.BooleanField(default=False, verbose_name="remove all tags"), - ), - migrations.AddField( - model_name="workflowaction", - name="remove_change_groups", - field=models.ManyToManyField( - blank=True, - related_name="+", - to="auth.group", - verbose_name="remove change permissions for these groups", - ), - ), - migrations.AddField( - model_name="workflowaction", - name="remove_change_users", - field=models.ManyToManyField( - blank=True, - related_name="+", - to=settings.AUTH_USER_MODEL, - verbose_name="remove change permissions for these users", - ), - ), - migrations.AddField( - model_name="workflowaction", - name="remove_correspondents", - field=models.ManyToManyField( - blank=True, - related_name="+", - to="documents.correspondent", - verbose_name="remove these correspondent(s)", - ), - ), - migrations.AddField( - model_name="workflowaction", - name="remove_custom_fields", - field=models.ManyToManyField( - blank=True, - related_name="+", - to="documents.customfield", - verbose_name="remove these custom fields", - ), - ), - migrations.AddField( - model_name="workflowaction", - name="remove_document_types", - field=models.ManyToManyField( - blank=True, - related_name="+", - to="documents.documenttype", - verbose_name="remove these document type(s)", - ), - ), - migrations.AddField( - model_name="workflowaction", - name="remove_owners", - field=models.ManyToManyField( - blank=True, - related_name="+", - to=settings.AUTH_USER_MODEL, - verbose_name="remove these owner(s)", - ), - ), - migrations.AddField( - model_name="workflowaction", - name="remove_storage_paths", - field=models.ManyToManyField( - blank=True, - related_name="+", - to="documents.storagepath", - verbose_name="remove these storage path(s)", - ), - ), - migrations.AddField( - model_name="workflowaction", - name="remove_tags", - field=models.ManyToManyField( - blank=True, - related_name="+", - to="documents.tag", - verbose_name="remove these tag(s)", - ), - ), - migrations.AddField( - model_name="workflowaction", - name="remove_view_groups", - field=models.ManyToManyField( - blank=True, - related_name="+", - to="auth.group", - verbose_name="remove view permissions for these groups", - ), - ), - migrations.AddField( - model_name="workflowaction", - name="remove_view_users", - field=models.ManyToManyField( - blank=True, - related_name="+", - to=settings.AUTH_USER_MODEL, - verbose_name="remove view permissions for these users", - ), - ), - migrations.AlterField( - model_name="workflowaction", - name="assign_correspondent", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="+", - to="documents.correspondent", - verbose_name="assign this correspondent", - ), - ), - migrations.AlterField( - model_name="workflowaction", - name="assign_document_type", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="+", - to="documents.documenttype", - verbose_name="assign this document type", - ), - ), - migrations.AlterField( - model_name="workflowaction", - name="assign_storage_path", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="+", - to="documents.storagepath", - verbose_name="assign this storage path", - ), - ), - migrations.AlterField( - model_name="workflowaction", - name="assign_tags", - field=models.ManyToManyField( - blank=True, - related_name="+", - to="documents.tag", - verbose_name="assign this tag", - ), - ), - migrations.AlterField( - model_name="workflowaction", - name="type", - field=models.PositiveIntegerField( - choices=[(1, "Assignment"), (2, "Removal")], - default=1, - verbose_name="Workflow Action Type", - ), - ), - ] diff --git a/src/documents/migrations/1047_savedview_display_mode_and_more.py b/src/documents/migrations/1047_savedview_display_mode_and_more.py deleted file mode 100644 index 904f86bb1..000000000 --- a/src/documents/migrations/1047_savedview_display_mode_and_more.py +++ /dev/null @@ -1,48 +0,0 @@ -# Generated by Django 4.2.11 on 2024-04-16 18:35 - -import django.core.validators -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1046_workflowaction_remove_all_correspondents_and_more"), - ] - - operations = [ - migrations.AddField( - model_name="savedview", - name="display_mode", - field=models.CharField( - blank=True, - choices=[ - ("table", "Table"), - ("smallCards", "Small Cards"), - ("largeCards", "Large Cards"), - ], - max_length=128, - null=True, - verbose_name="View display mode", - ), - ), - migrations.AddField( - model_name="savedview", - name="page_size", - field=models.PositiveIntegerField( - blank=True, - null=True, - validators=[django.core.validators.MinValueValidator(1)], - verbose_name="View page size", - ), - ), - migrations.AddField( - model_name="savedview", - name="display_fields", - field=models.JSONField( - blank=True, - null=True, - verbose_name="Document display fields", - ), - ), - ] diff --git a/src/documents/migrations/1048_alter_savedviewfilterrule_rule_type.py b/src/documents/migrations/1048_alter_savedviewfilterrule_rule_type.py deleted file mode 100644 index 904ad242c..000000000 --- a/src/documents/migrations/1048_alter_savedviewfilterrule_rule_type.py +++ /dev/null @@ -1,64 +0,0 @@ -# Generated by Django 4.2.11 on 2024-04-24 04:58 - -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1047_savedview_display_mode_and_more"), - ] - - operations = [ - migrations.AlterField( - model_name="savedviewfilterrule", - name="rule_type", - field=models.PositiveIntegerField( - choices=[ - (0, "title contains"), - (1, "content contains"), - (2, "ASN is"), - (3, "correspondent is"), - (4, "document type is"), - (5, "is in inbox"), - (6, "has tag"), - (7, "has any tag"), - (8, "created before"), - (9, "created after"), - (10, "created year is"), - (11, "created month is"), - (12, "created day is"), - (13, "added before"), - (14, "added after"), - (15, "modified before"), - (16, "modified after"), - (17, "does not have tag"), - (18, "does not have ASN"), - (19, "title or content contains"), - (20, "fulltext query"), - (21, "more like this"), - (22, "has tags in"), - (23, "ASN greater than"), - (24, "ASN less than"), - (25, "storage path is"), - (26, "has correspondent in"), - (27, "does not have correspondent in"), - (28, "has document type in"), - (29, "does not have document type in"), - (30, "has storage path in"), - (31, "does not have storage path in"), - (32, "owner is"), - (33, "has owner in"), - (34, "does not have owner"), - (35, "does not have owner in"), - (36, "has custom field value"), - (37, "is shared by me"), - (38, "has custom fields"), - (39, "has custom field in"), - (40, "does not have custom field in"), - (41, "does not have custom field"), - ], - verbose_name="rule type", - ), - ), - ] diff --git a/src/documents/migrations/1049_document_deleted_at_document_restored_at.py b/src/documents/migrations/1049_document_deleted_at_document_restored_at.py deleted file mode 100644 index 39fb41353..000000000 --- a/src/documents/migrations/1049_document_deleted_at_document_restored_at.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 4.2.11 on 2024-04-23 07:56 - -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1048_alter_savedviewfilterrule_rule_type"), - ] - - operations = [ - migrations.AddField( - model_name="document", - name="deleted_at", - field=models.DateTimeField(blank=True, null=True), - ), - migrations.AddField( - model_name="document", - name="restored_at", - field=models.DateTimeField(blank=True, null=True), - ), - ] diff --git a/src/documents/migrations/1050_customfield_extra_data_and_more.py b/src/documents/migrations/1050_customfield_extra_data_and_more.py deleted file mode 100644 index 0c6a77ccc..000000000 --- a/src/documents/migrations/1050_customfield_extra_data_and_more.py +++ /dev/null @@ -1,48 +0,0 @@ -# Generated by Django 4.2.13 on 2024-07-04 01:02 - -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1049_document_deleted_at_document_restored_at"), - ] - - operations = [ - migrations.AddField( - model_name="customfield", - name="extra_data", - field=models.JSONField( - blank=True, - help_text="Extra data for the custom field, such as select options", - null=True, - verbose_name="extra data", - ), - ), - migrations.AddField( - model_name="customfieldinstance", - name="value_select", - field=models.PositiveSmallIntegerField(null=True), - ), - migrations.AlterField( - model_name="customfield", - name="data_type", - field=models.CharField( - choices=[ - ("string", "String"), - ("url", "URL"), - ("date", "Date"), - ("boolean", "Boolean"), - ("integer", "Integer"), - ("float", "Float"), - ("monetary", "Monetary"), - ("documentlink", "Document Link"), - ("select", "Select"), - ], - editable=False, - max_length=50, - verbose_name="data type", - ), - ), - ] diff --git a/src/documents/migrations/1051_alter_correspondent_owner_alter_document_owner_and_more.py b/src/documents/migrations/1051_alter_correspondent_owner_alter_document_owner_and_more.py deleted file mode 100644 index e8f0bb97c..000000000 --- a/src/documents/migrations/1051_alter_correspondent_owner_alter_document_owner_and_more.py +++ /dev/null @@ -1,88 +0,0 @@ -# Generated by Django 4.2.13 on 2024-07-09 16:39 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ("documents", "1050_customfield_extra_data_and_more"), - ] - - operations = [ - migrations.AlterField( - model_name="correspondent", - name="owner", - field=models.ForeignKey( - blank=True, - default=None, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to=settings.AUTH_USER_MODEL, - verbose_name="owner", - ), - ), - migrations.AlterField( - model_name="document", - name="owner", - field=models.ForeignKey( - blank=True, - default=None, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to=settings.AUTH_USER_MODEL, - verbose_name="owner", - ), - ), - migrations.AlterField( - model_name="documenttype", - name="owner", - field=models.ForeignKey( - blank=True, - default=None, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to=settings.AUTH_USER_MODEL, - verbose_name="owner", - ), - ), - migrations.AlterField( - model_name="savedview", - name="owner", - field=models.ForeignKey( - blank=True, - default=None, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to=settings.AUTH_USER_MODEL, - verbose_name="owner", - ), - ), - migrations.AlterField( - model_name="storagepath", - name="owner", - field=models.ForeignKey( - blank=True, - default=None, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to=settings.AUTH_USER_MODEL, - verbose_name="owner", - ), - ), - migrations.AlterField( - model_name="tag", - name="owner", - field=models.ForeignKey( - blank=True, - default=None, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to=settings.AUTH_USER_MODEL, - verbose_name="owner", - ), - ), - ] diff --git a/src/documents/migrations/1052_document_transaction_id.py b/src/documents/migrations/1052_document_transaction_id.py deleted file mode 100644 index 5eb8e2ef9..000000000 --- a/src/documents/migrations/1052_document_transaction_id.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 4.2.15 on 2024-08-20 02:41 - -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1051_alter_correspondent_owner_alter_document_owner_and_more"), - ] - - operations = [ - migrations.AddField( - model_name="document", - name="transaction_id", - field=models.UUIDField(blank=True, null=True), - ), - ] diff --git a/src/documents/migrations/1053_document_page_count.py b/src/documents/migrations/1053_document_page_count.py deleted file mode 100644 index 3a8bc5d79..000000000 --- a/src/documents/migrations/1053_document_page_count.py +++ /dev/null @@ -1,67 +0,0 @@ -# Generated by Django 5.1.1 on 2024-09-28 04:42 - -from pathlib import Path - -import pikepdf -from django.conf import settings -from django.core.validators import MinValueValidator -from django.db import migrations -from django.db import models -from django.utils.termcolors import colorize as colourise - - -def source_path(self): - if self.filename: - fname = str(self.filename) - - return Path(settings.ORIGINALS_DIR / fname).resolve() - - -def add_number_of_pages_to_page_count(apps, schema_editor): - Document = apps.get_model("documents", "Document") - - if not Document.objects.all().exists(): - return - - for doc in Document.objects.filter(mime_type="application/pdf"): - print( - " {} {} {}".format( - colourise("*", fg="green"), - colourise("Calculating number of pages for", fg="white"), - colourise(doc.filename, fg="cyan"), - ), - ) - - try: - with pikepdf.Pdf.open(source_path(doc)) as pdf: - if pdf.pages is not None: - doc.page_count = len(pdf.pages) - doc.save() - except Exception as e: # pragma: no cover - print(f"Error retrieving number of pages for {doc.filename}: {e}") - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1052_document_transaction_id"), - ] - - operations = [ - migrations.AddField( - model_name="document", - name="page_count", - field=models.PositiveIntegerField( - blank=False, - help_text="The number of pages of the document.", - null=True, - unique=False, - validators=[MinValueValidator(1)], - verbose_name="page count", - db_index=False, - ), - ), - migrations.RunPython( - add_number_of_pages_to_page_count, - migrations.RunPython.noop, - ), - ] diff --git a/src/documents/migrations/1054_customfieldinstance_value_monetary_amount_and_more.py b/src/documents/migrations/1054_customfieldinstance_value_monetary_amount_and_more.py deleted file mode 100644 index 92d45de33..000000000 --- a/src/documents/migrations/1054_customfieldinstance_value_monetary_amount_and_more.py +++ /dev/null @@ -1,95 +0,0 @@ -# Generated by Django 5.1.1 on 2024-09-29 16:26 - -import django.db.models.functions.comparison -import django.db.models.functions.text -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1053_document_page_count"), - ] - - operations = [ - migrations.AddField( - model_name="customfieldinstance", - name="value_monetary_amount", - field=models.GeneratedField( - db_persist=True, - expression=models.Case( - models.When( - then=django.db.models.functions.comparison.Cast( - django.db.models.functions.text.Substr("value_monetary", 1), - output_field=models.DecimalField( - decimal_places=2, - max_digits=65, - ), - ), - value_monetary__regex="^\\d+", - ), - default=django.db.models.functions.comparison.Cast( - django.db.models.functions.text.Substr("value_monetary", 4), - output_field=models.DecimalField( - decimal_places=2, - max_digits=65, - ), - ), - output_field=models.DecimalField(decimal_places=2, max_digits=65), - ), - output_field=models.DecimalField(decimal_places=2, max_digits=65), - ), - ), - migrations.AlterField( - model_name="savedviewfilterrule", - name="rule_type", - field=models.PositiveIntegerField( - choices=[ - (0, "title contains"), - (1, "content contains"), - (2, "ASN is"), - (3, "correspondent is"), - (4, "document type is"), - (5, "is in inbox"), - (6, "has tag"), - (7, "has any tag"), - (8, "created before"), - (9, "created after"), - (10, "created year is"), - (11, "created month is"), - (12, "created day is"), - (13, "added before"), - (14, "added after"), - (15, "modified before"), - (16, "modified after"), - (17, "does not have tag"), - (18, "does not have ASN"), - (19, "title or content contains"), - (20, "fulltext query"), - (21, "more like this"), - (22, "has tags in"), - (23, "ASN greater than"), - (24, "ASN less than"), - (25, "storage path is"), - (26, "has correspondent in"), - (27, "does not have correspondent in"), - (28, "has document type in"), - (29, "does not have document type in"), - (30, "has storage path in"), - (31, "does not have storage path in"), - (32, "owner is"), - (33, "has owner in"), - (34, "does not have owner"), - (35, "does not have owner in"), - (36, "has custom field value"), - (37, "is shared by me"), - (38, "has custom fields"), - (39, "has custom field in"), - (40, "does not have custom field in"), - (41, "does not have custom field"), - (42, "custom fields query"), - ], - verbose_name="rule type", - ), - ), - ] diff --git a/src/documents/migrations/1055_alter_storagepath_path.py b/src/documents/migrations/1055_alter_storagepath_path.py deleted file mode 100644 index 1421bf824..000000000 --- a/src/documents/migrations/1055_alter_storagepath_path.py +++ /dev/null @@ -1,36 +0,0 @@ -# Generated by Django 5.1.1 on 2024-10-03 14:47 - -from django.conf import settings -from django.db import migrations -from django.db import models -from django.db import transaction -from filelock import FileLock - -from documents.templating.utils import convert_format_str_to_template_format - - -def convert_from_format_to_template(apps, schema_editor): - StoragePath = apps.get_model("documents", "StoragePath") - - with transaction.atomic(), FileLock(settings.MEDIA_LOCK): - for storage_path in StoragePath.objects.all(): - storage_path.path = convert_format_str_to_template_format(storage_path.path) - storage_path.save() - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1054_customfieldinstance_value_monetary_amount_and_more"), - ] - - operations = [ - migrations.AlterField( - model_name="storagepath", - name="path", - field=models.TextField(verbose_name="path"), - ), - migrations.RunPython( - convert_from_format_to_template, - migrations.RunPython.noop, - ), - ] diff --git a/src/documents/migrations/1056_customfieldinstance_deleted_at_and_more.py b/src/documents/migrations/1056_customfieldinstance_deleted_at_and_more.py deleted file mode 100644 index eba1e4281..000000000 --- a/src/documents/migrations/1056_customfieldinstance_deleted_at_and_more.py +++ /dev/null @@ -1,58 +0,0 @@ -# Generated by Django 5.1.2 on 2024-10-28 01:55 - -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1055_alter_storagepath_path"), - ] - - operations = [ - migrations.AddField( - model_name="customfieldinstance", - name="deleted_at", - field=models.DateTimeField(blank=True, null=True), - ), - migrations.AddField( - model_name="customfieldinstance", - name="restored_at", - field=models.DateTimeField(blank=True, null=True), - ), - migrations.AddField( - model_name="customfieldinstance", - name="transaction_id", - field=models.UUIDField(blank=True, null=True), - ), - migrations.AddField( - model_name="note", - name="deleted_at", - field=models.DateTimeField(blank=True, null=True), - ), - migrations.AddField( - model_name="note", - name="restored_at", - field=models.DateTimeField(blank=True, null=True), - ), - migrations.AddField( - model_name="note", - name="transaction_id", - field=models.UUIDField(blank=True, null=True), - ), - migrations.AddField( - model_name="sharelink", - name="deleted_at", - field=models.DateTimeField(blank=True, null=True), - ), - migrations.AddField( - model_name="sharelink", - name="restored_at", - field=models.DateTimeField(blank=True, null=True), - ), - migrations.AddField( - model_name="sharelink", - name="transaction_id", - field=models.UUIDField(blank=True, null=True), - ), - ] diff --git a/src/documents/migrations/1057_paperlesstask_owner.py b/src/documents/migrations/1057_paperlesstask_owner.py deleted file mode 100644 index e9f108d3a..000000000 --- a/src/documents/migrations/1057_paperlesstask_owner.py +++ /dev/null @@ -1,28 +0,0 @@ -# Generated by Django 5.1.1 on 2024-11-04 21:56 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1056_customfieldinstance_deleted_at_and_more"), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.AddField( - model_name="paperlesstask", - name="owner", - field=models.ForeignKey( - blank=True, - default=None, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to=settings.AUTH_USER_MODEL, - verbose_name="owner", - ), - ), - ] diff --git a/src/documents/migrations/1058_workflowtrigger_schedule_date_custom_field_and_more.py b/src/documents/migrations/1058_workflowtrigger_schedule_date_custom_field_and_more.py deleted file mode 100644 index 05d38578a..000000000 --- a/src/documents/migrations/1058_workflowtrigger_schedule_date_custom_field_and_more.py +++ /dev/null @@ -1,143 +0,0 @@ -# Generated by Django 5.1.1 on 2024-11-05 05:19 - -import django.core.validators -import django.db.models.deletion -import django.utils.timezone -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1057_paperlesstask_owner"), - ] - - operations = [ - migrations.AddField( - model_name="workflowtrigger", - name="schedule_date_custom_field", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to="documents.customfield", - verbose_name="schedule date custom field", - ), - ), - migrations.AddField( - model_name="workflowtrigger", - name="schedule_date_field", - field=models.CharField( - choices=[ - ("added", "Added"), - ("created", "Created"), - ("modified", "Modified"), - ("custom_field", "Custom Field"), - ], - default="added", - help_text="The field to check for a schedule trigger.", - max_length=20, - verbose_name="schedule date field", - ), - ), - migrations.AddField( - model_name="workflowtrigger", - name="schedule_is_recurring", - field=models.BooleanField( - default=False, - help_text="If the schedule should be recurring.", - verbose_name="schedule is recurring", - ), - ), - migrations.AddField( - model_name="workflowtrigger", - name="schedule_offset_days", - field=models.PositiveIntegerField( - default=0, - help_text="The number of days to offset the schedule trigger by.", - verbose_name="schedule offset days", - ), - ), - migrations.AddField( - model_name="workflowtrigger", - name="schedule_recurring_interval_days", - field=models.PositiveIntegerField( - default=1, - help_text="The number of days between recurring schedule triggers.", - validators=[django.core.validators.MinValueValidator(1)], - verbose_name="schedule recurring delay in days", - ), - ), - migrations.AlterField( - model_name="workflowtrigger", - name="type", - field=models.PositiveIntegerField( - choices=[ - (1, "Consumption Started"), - (2, "Document Added"), - (3, "Document Updated"), - (4, "Scheduled"), - ], - default=1, - verbose_name="Workflow Trigger Type", - ), - ), - migrations.CreateModel( - name="WorkflowRun", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "type", - models.PositiveIntegerField( - choices=[ - (1, "Consumption Started"), - (2, "Document Added"), - (3, "Document Updated"), - (4, "Scheduled"), - ], - null=True, - verbose_name="workflow trigger type", - ), - ), - ( - "run_at", - models.DateTimeField( - db_index=True, - default=django.utils.timezone.now, - verbose_name="date run", - ), - ), - ( - "document", - models.ForeignKey( - null=True, - on_delete=django.db.models.deletion.CASCADE, - related_name="workflow_runs", - to="documents.document", - verbose_name="document", - ), - ), - ( - "workflow", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="runs", - to="documents.workflow", - verbose_name="workflow", - ), - ), - ], - options={ - "verbose_name": "workflow run", - "verbose_name_plural": "workflow runs", - }, - ), - ] diff --git a/src/documents/migrations/1059_workflowactionemail_workflowactionwebhook_and_more.py b/src/documents/migrations/1059_workflowactionemail_workflowactionwebhook_and_more.py deleted file mode 100644 index d94470285..000000000 --- a/src/documents/migrations/1059_workflowactionemail_workflowactionwebhook_and_more.py +++ /dev/null @@ -1,154 +0,0 @@ -# Generated by Django 5.1.3 on 2024-11-26 04:07 - -import django.db.models.deletion -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1058_workflowtrigger_schedule_date_custom_field_and_more"), - ] - - operations = [ - migrations.CreateModel( - name="WorkflowActionEmail", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "subject", - models.CharField( - help_text="The subject of the email, can include some placeholders, see documentation.", - max_length=256, - verbose_name="email subject", - ), - ), - ( - "body", - models.TextField( - help_text="The body (message) of the email, can include some placeholders, see documentation.", - verbose_name="email body", - ), - ), - ( - "to", - models.TextField( - help_text="The destination email addresses, comma separated.", - verbose_name="emails to", - ), - ), - ( - "include_document", - models.BooleanField( - default=False, - verbose_name="include document in email", - ), - ), - ], - ), - migrations.CreateModel( - name="WorkflowActionWebhook", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "url", - models.URLField( - help_text="The destination URL for the notification.", - verbose_name="webhook url", - ), - ), - ( - "use_params", - models.BooleanField(default=True, verbose_name="use parameters"), - ), - ( - "params", - models.JSONField( - blank=True, - help_text="The parameters to send with the webhook URL if body not used.", - null=True, - verbose_name="webhook parameters", - ), - ), - ( - "body", - models.TextField( - blank=True, - help_text="The body to send with the webhook URL if parameters not used.", - null=True, - verbose_name="webhook body", - ), - ), - ( - "headers", - models.JSONField( - blank=True, - help_text="The headers to send with the webhook URL.", - null=True, - verbose_name="webhook headers", - ), - ), - ( - "include_document", - models.BooleanField( - default=False, - verbose_name="include document in webhook", - ), - ), - ], - ), - migrations.AlterField( - model_name="workflowaction", - name="type", - field=models.PositiveIntegerField( - choices=[ - (1, "Assignment"), - (2, "Removal"), - (3, "Email"), - (4, "Webhook"), - ], - default=1, - verbose_name="Workflow Action Type", - ), - ), - migrations.AddField( - model_name="workflowaction", - name="email", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="action", - to="documents.workflowactionemail", - verbose_name="email", - ), - ), - migrations.AddField( - model_name="workflowaction", - name="webhook", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="action", - to="documents.workflowactionwebhook", - verbose_name="webhook", - ), - ), - ] diff --git a/src/documents/migrations/1060_alter_customfieldinstance_value_select.py b/src/documents/migrations/1060_alter_customfieldinstance_value_select.py deleted file mode 100644 index 21f3f8b41..000000000 --- a/src/documents/migrations/1060_alter_customfieldinstance_value_select.py +++ /dev/null @@ -1,79 +0,0 @@ -# Generated by Django 5.1.1 on 2024-11-13 05:14 - -from django.db import migrations -from django.db import models -from django.db import transaction -from django.utils.crypto import get_random_string - - -def migrate_customfield_selects(apps, schema_editor): - """ - Migrate the custom field selects from a simple list of strings to a list of dictionaries with - label and id. Then update all instances of the custom field to use the new format. - """ - CustomFieldInstance = apps.get_model("documents", "CustomFieldInstance") - CustomField = apps.get_model("documents", "CustomField") - - with transaction.atomic(): - for custom_field in CustomField.objects.filter( - data_type="select", - ): # CustomField.FieldDataType.SELECT - old_select_options = custom_field.extra_data["select_options"] - custom_field.extra_data["select_options"] = [ - {"id": get_random_string(16), "label": value} - for value in old_select_options - ] - custom_field.save() - - for instance in CustomFieldInstance.objects.filter(field=custom_field): - if instance.value_select: - instance.value_select = custom_field.extra_data["select_options"][ - int(instance.value_select) - ]["id"] - instance.save() - - -def reverse_migrate_customfield_selects(apps, schema_editor): - """ - Reverse the migration of the custom field selects from a list of dictionaries with label and id - to a simple list of strings. Then update all instances of the custom field to use the old format, - which is just the index of the selected option. - """ - CustomFieldInstance = apps.get_model("documents", "CustomFieldInstance") - CustomField = apps.get_model("documents", "CustomField") - - with transaction.atomic(): - for custom_field in CustomField.objects.all(): - if custom_field.data_type == "select": # CustomField.FieldDataType.SELECT - old_select_options = custom_field.extra_data["select_options"] - custom_field.extra_data["select_options"] = [ - option["label"] - for option in custom_field.extra_data["select_options"] - ] - custom_field.save() - - for instance in CustomFieldInstance.objects.filter(field=custom_field): - instance.value_select = next( - index - for index, option in enumerate(old_select_options) - if option.get("id") == instance.value_select - ) - instance.save() - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1059_workflowactionemail_workflowactionwebhook_and_more"), - ] - - operations = [ - migrations.AlterField( - model_name="customfieldinstance", - name="value_select", - field=models.CharField(max_length=16, null=True), - ), - migrations.RunPython( - migrate_customfield_selects, - reverse_migrate_customfield_selects, - ), - ] diff --git a/src/documents/migrations/1061_workflowactionwebhook_as_json.py b/src/documents/migrations/1061_workflowactionwebhook_as_json.py deleted file mode 100644 index f1945cfc1..000000000 --- a/src/documents/migrations/1061_workflowactionwebhook_as_json.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.1.4 on 2025-01-18 19:35 - -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1060_alter_customfieldinstance_value_select"), - ] - - operations = [ - migrations.AddField( - model_name="workflowactionwebhook", - name="as_json", - field=models.BooleanField(default=False, verbose_name="send as JSON"), - ), - ] diff --git a/src/documents/migrations/1062_alter_savedviewfilterrule_rule_type.py b/src/documents/migrations/1062_alter_savedviewfilterrule_rule_type.py deleted file mode 100644 index c5a6bb90e..000000000 --- a/src/documents/migrations/1062_alter_savedviewfilterrule_rule_type.py +++ /dev/null @@ -1,70 +0,0 @@ -# Generated by Django 5.1.4 on 2025-02-06 05:54 - -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1061_workflowactionwebhook_as_json"), - ] - - operations = [ - migrations.AlterField( - model_name="savedviewfilterrule", - name="rule_type", - field=models.PositiveIntegerField( - choices=[ - (0, "title contains"), - (1, "content contains"), - (2, "ASN is"), - (3, "correspondent is"), - (4, "document type is"), - (5, "is in inbox"), - (6, "has tag"), - (7, "has any tag"), - (8, "created before"), - (9, "created after"), - (10, "created year is"), - (11, "created month is"), - (12, "created day is"), - (13, "added before"), - (14, "added after"), - (15, "modified before"), - (16, "modified after"), - (17, "does not have tag"), - (18, "does not have ASN"), - (19, "title or content contains"), - (20, "fulltext query"), - (21, "more like this"), - (22, "has tags in"), - (23, "ASN greater than"), - (24, "ASN less than"), - (25, "storage path is"), - (26, "has correspondent in"), - (27, "does not have correspondent in"), - (28, "has document type in"), - (29, "does not have document type in"), - (30, "has storage path in"), - (31, "does not have storage path in"), - (32, "owner is"), - (33, "has owner in"), - (34, "does not have owner"), - (35, "does not have owner in"), - (36, "has custom field value"), - (37, "is shared by me"), - (38, "has custom fields"), - (39, "has custom field in"), - (40, "does not have custom field in"), - (41, "does not have custom field"), - (42, "custom fields query"), - (43, "created to"), - (44, "created from"), - (45, "added to"), - (46, "added from"), - (47, "mime type is"), - ], - verbose_name="rule type", - ), - ), - ] diff --git a/src/documents/migrations/1063_paperlesstask_type_alter_paperlesstask_task_name_and_more.py b/src/documents/migrations/1063_paperlesstask_type_alter_paperlesstask_task_name_and_more.py deleted file mode 100644 index aeedbd6a0..000000000 --- a/src/documents/migrations/1063_paperlesstask_type_alter_paperlesstask_task_name_and_more.py +++ /dev/null @@ -1,92 +0,0 @@ -# Generated by Django 5.1.6 on 2025-02-21 16:34 - -import multiselectfield.db.fields -from django.db import migrations -from django.db import models - - -# WebUI source was added, so all existing APIUpload sources should be updated to include WebUI -def update_workflow_sources(apps, schema_editor): - WorkflowTrigger = apps.get_model("documents", "WorkflowTrigger") - for trigger in WorkflowTrigger.objects.all(): - sources = list(trigger.sources) - if 2 in sources: - sources.append(4) - trigger.sources = sources - trigger.save() - - -def make_existing_tasks_consume_auto(apps, schema_editor): - PaperlessTask = apps.get_model("documents", "PaperlessTask") - PaperlessTask.objects.all().update(type="auto_task", task_name="consume_file") - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1062_alter_savedviewfilterrule_rule_type"), - ] - - operations = [ - migrations.AddField( - model_name="paperlesstask", - name="type", - field=models.CharField( - choices=[ - ("auto_task", "Auto Task"), - ("scheduled_task", "Scheduled Task"), - ("manual_task", "Manual Task"), - ], - default="auto_task", - help_text="The type of task that was run", - max_length=30, - verbose_name="Task Type", - ), - ), - migrations.AlterField( - model_name="paperlesstask", - name="task_name", - field=models.CharField( - choices=[ - ("consume_file", "Consume File"), - ("train_classifier", "Train Classifier"), - ("check_sanity", "Check Sanity"), - ("index_optimize", "Index Optimize"), - ], - help_text="Name of the task that was run", - max_length=255, - null=True, - verbose_name="Task Name", - ), - ), - migrations.RunPython( - code=make_existing_tasks_consume_auto, - reverse_code=migrations.RunPython.noop, - ), - migrations.AlterField( - model_name="workflowactionwebhook", - name="url", - field=models.CharField( - help_text="The destination URL for the notification.", - max_length=256, - verbose_name="webhook url", - ), - ), - migrations.AlterField( - model_name="workflowtrigger", - name="sources", - field=multiselectfield.db.fields.MultiSelectField( - choices=[ - (1, "Consume Folder"), - (2, "Api Upload"), - (3, "Mail Fetch"), - (4, "Web UI"), - ], - default="1,2,3,4", - max_length=7, - ), - ), - migrations.RunPython( - code=update_workflow_sources, - reverse_code=migrations.RunPython.noop, - ), - ] diff --git a/src/documents/migrations/1064_delete_log.py b/src/documents/migrations/1064_delete_log.py deleted file mode 100644 index ec0830a91..000000000 --- a/src/documents/migrations/1064_delete_log.py +++ /dev/null @@ -1,15 +0,0 @@ -# Generated by Django 5.1.6 on 2025-02-28 15:19 - -from django.db import migrations - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1063_paperlesstask_type_alter_paperlesstask_task_name_and_more"), - ] - - operations = [ - migrations.DeleteModel( - name="Log", - ), - ] diff --git a/src/documents/migrations/1065_workflowaction_assign_custom_fields_values.py b/src/documents/migrations/1065_workflowaction_assign_custom_fields_values.py deleted file mode 100644 index 35fae02be..000000000 --- a/src/documents/migrations/1065_workflowaction_assign_custom_fields_values.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by Django 5.1.6 on 2025-03-01 18:10 - -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1064_delete_log"), - ] - - operations = [ - migrations.AddField( - model_name="workflowaction", - name="assign_custom_fields_values", - field=models.JSONField( - blank=True, - help_text="Optional values to assign to the custom fields.", - null=True, - verbose_name="custom field values", - default=dict, - ), - ), - ] diff --git a/src/documents/migrations/1066_alter_workflowtrigger_schedule_offset_days.py b/src/documents/migrations/1066_alter_workflowtrigger_schedule_offset_days.py deleted file mode 100644 index eaf23ad64..000000000 --- a/src/documents/migrations/1066_alter_workflowtrigger_schedule_offset_days.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 5.1.7 on 2025-04-15 19:18 - -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1065_workflowaction_assign_custom_fields_values"), - ] - - operations = [ - migrations.AlterField( - model_name="workflowtrigger", - name="schedule_offset_days", - field=models.IntegerField( - default=0, - help_text="The number of days to offset the schedule trigger by.", - verbose_name="schedule offset days", - ), - ), - ] diff --git a/src/documents/migrations/1067_alter_document_created.py b/src/documents/migrations/1067_alter_document_created.py deleted file mode 100644 index 0f96bce3d..000000000 --- a/src/documents/migrations/1067_alter_document_created.py +++ /dev/null @@ -1,76 +0,0 @@ -# Generated by Django 5.1.7 on 2025-04-04 01:08 - - -import datetime - -from django.db import migrations -from django.db import models -from django.utils.timezone import localtime - - -def migrate_date(apps, schema_editor): - Document = apps.get_model("documents", "Document") - - # Batch to avoid loading all objects into memory at once, - # which would be problematic for large datasets. - batch_size = 500 - updates = [] - total_updated = 0 - total_checked = 0 - - for doc in Document.objects.only("id", "created").iterator(chunk_size=batch_size): - total_checked += 1 - if doc.created: - doc.created_date = localtime(doc.created).date() - updates.append(doc) - - if len(updates) >= batch_size: - Document.objects.bulk_update(updates, ["created_date"]) - total_updated += len(updates) - print( - f"[1067_alter_document_created] {total_updated} of {total_checked} processed...", - ) - updates.clear() - - if updates: - Document.objects.bulk_update(updates, ["created_date"]) - total_updated += len(updates) - print( - f"[1067_alter_document_created] {total_updated} of {total_checked} processed...", - ) - - if total_checked > 0: - print(f"[1067_alter_document_created] completed for {total_checked} documents.") - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1066_alter_workflowtrigger_schedule_offset_days"), - ] - - operations = [ - migrations.AddField( - model_name="document", - name="created_date", - field=models.DateField(null=True), - ), - migrations.RunPython(migrate_date, reverse_code=migrations.RunPython.noop), - migrations.RemoveField( - model_name="document", - name="created", - ), - migrations.RenameField( - model_name="document", - old_name="created_date", - new_name="created", - ), - migrations.AlterField( - model_name="document", - name="created", - field=models.DateField( - db_index=True, - default=datetime.datetime.today, - verbose_name="created", - ), - ), - ] diff --git a/src/documents/migrations/1068_alter_document_created.py b/src/documents/migrations/1068_alter_document_created.py deleted file mode 100644 index b673f6584..000000000 --- a/src/documents/migrations/1068_alter_document_created.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by Django 5.1.8 on 2025-05-23 05:50 - -import datetime - -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1067_alter_document_created"), - ] - - operations = [ - migrations.AlterField( - model_name="document", - name="created", - field=models.DateField( - db_index=True, - default=datetime.date.today, - verbose_name="created", - ), - ), - ] diff --git a/src/documents/migrations/1069_workflowtrigger_filter_has_storage_path_and_more.py b/src/documents/migrations/1069_workflowtrigger_filter_has_storage_path_and_more.py deleted file mode 100644 index 47db2fd91..000000000 --- a/src/documents/migrations/1069_workflowtrigger_filter_has_storage_path_and_more.py +++ /dev/null @@ -1,35 +0,0 @@ -# Generated by Django 5.2.6 on 2025-09-11 17:29 - -import django.db.models.deletion -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1068_alter_document_created"), - ] - - operations = [ - migrations.AddField( - model_name="workflowtrigger", - name="filter_has_storage_path", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to="documents.storagepath", - verbose_name="has this storage path", - ), - ), - migrations.AlterField( - model_name="workflowaction", - name="assign_title", - field=models.TextField( - blank=True, - help_text="Assign a document title, must be a Jinja2 template, see documentation.", - null=True, - verbose_name="assign title", - ), - ), - ] diff --git a/src/documents/migrations/1070_customfieldinstance_value_long_text_and_more.py b/src/documents/migrations/1070_customfieldinstance_value_long_text_and_more.py deleted file mode 100644 index 69c77d29a..000000000 --- a/src/documents/migrations/1070_customfieldinstance_value_long_text_and_more.py +++ /dev/null @@ -1,39 +0,0 @@ -# Generated by Django 5.2.6 on 2025-09-13 17:11 - -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1069_workflowtrigger_filter_has_storage_path_and_more"), - ] - - operations = [ - migrations.AddField( - model_name="customfieldinstance", - name="value_long_text", - field=models.TextField(null=True), - ), - migrations.AlterField( - model_name="customfield", - name="data_type", - field=models.CharField( - choices=[ - ("string", "String"), - ("url", "URL"), - ("date", "Date"), - ("boolean", "Boolean"), - ("integer", "Integer"), - ("float", "Float"), - ("monetary", "Monetary"), - ("documentlink", "Document Link"), - ("select", "Select"), - ("longtext", "Long Text"), - ], - editable=False, - max_length=50, - verbose_name="data type", - ), - ), - ] diff --git a/src/documents/migrations/1071_tag_tn_ancestors_count_tag_tn_ancestors_pks_and_more.py b/src/documents/migrations/1071_tag_tn_ancestors_count_tag_tn_ancestors_pks_and_more.py deleted file mode 100644 index 3e097620e..000000000 --- a/src/documents/migrations/1071_tag_tn_ancestors_count_tag_tn_ancestors_pks_and_more.py +++ /dev/null @@ -1,159 +0,0 @@ -# Generated by Django 5.2.6 on 2025-09-12 18:42 - -import django.core.validators -import django.db.models.deletion -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1070_customfieldinstance_value_long_text_and_more"), - ] - - operations = [ - migrations.AddField( - model_name="tag", - name="tn_ancestors_count", - field=models.PositiveIntegerField( - default=0, - editable=False, - verbose_name="Ancestors count", - ), - ), - migrations.AddField( - model_name="tag", - name="tn_ancestors_pks", - field=models.TextField( - blank=True, - default="", - editable=False, - verbose_name="Ancestors pks", - ), - ), - migrations.AddField( - model_name="tag", - name="tn_children_count", - field=models.PositiveIntegerField( - default=0, - editable=False, - verbose_name="Children count", - ), - ), - migrations.AddField( - model_name="tag", - name="tn_children_pks", - field=models.TextField( - blank=True, - default="", - editable=False, - verbose_name="Children pks", - ), - ), - migrations.AddField( - model_name="tag", - name="tn_depth", - field=models.PositiveIntegerField( - default=0, - editable=False, - validators=[ - django.core.validators.MinValueValidator(0), - django.core.validators.MaxValueValidator(10), - ], - verbose_name="Depth", - ), - ), - migrations.AddField( - model_name="tag", - name="tn_descendants_count", - field=models.PositiveIntegerField( - default=0, - editable=False, - verbose_name="Descendants count", - ), - ), - migrations.AddField( - model_name="tag", - name="tn_descendants_pks", - field=models.TextField( - blank=True, - default="", - editable=False, - verbose_name="Descendants pks", - ), - ), - migrations.AddField( - model_name="tag", - name="tn_index", - field=models.PositiveIntegerField( - default=0, - editable=False, - verbose_name="Index", - ), - ), - migrations.AddField( - model_name="tag", - name="tn_level", - field=models.PositiveIntegerField( - default=1, - editable=False, - validators=[ - django.core.validators.MinValueValidator(1), - django.core.validators.MaxValueValidator(10), - ], - verbose_name="Level", - ), - ), - migrations.AddField( - model_name="tag", - name="tn_order", - field=models.PositiveIntegerField( - default=0, - editable=False, - verbose_name="Order", - ), - ), - migrations.AddField( - model_name="tag", - name="tn_parent", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.CASCADE, - related_name="tn_children", - to="documents.tag", - verbose_name="Parent", - ), - ), - migrations.AddField( - model_name="tag", - name="tn_priority", - field=models.PositiveIntegerField( - default=0, - validators=[ - django.core.validators.MinValueValidator(0), - django.core.validators.MaxValueValidator(9999999999), - ], - verbose_name="Priority", - ), - ), - migrations.AddField( - model_name="tag", - name="tn_siblings_count", - field=models.PositiveIntegerField( - default=0, - editable=False, - verbose_name="Siblings count", - ), - ), - migrations.AddField( - model_name="tag", - name="tn_siblings_pks", - field=models.TextField( - blank=True, - default="", - editable=False, - verbose_name="Siblings pks", - ), - ), - ] diff --git a/src/documents/migrations/1072_workflowtrigger_filter_custom_field_query_and_more.py b/src/documents/migrations/1072_workflowtrigger_filter_custom_field_query_and_more.py deleted file mode 100644 index 1a22f6b4f..000000000 --- a/src/documents/migrations/1072_workflowtrigger_filter_custom_field_query_and_more.py +++ /dev/null @@ -1,73 +0,0 @@ -# Generated by Django 5.2.6 on 2025-10-07 18:52 - -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1071_tag_tn_ancestors_count_tag_tn_ancestors_pks_and_more"), - ] - - operations = [ - migrations.AddField( - model_name="workflowtrigger", - name="filter_custom_field_query", - field=models.TextField( - blank=True, - help_text="JSON-encoded custom field query expression.", - null=True, - verbose_name="filter custom field query", - ), - ), - migrations.AddField( - model_name="workflowtrigger", - name="filter_has_all_tags", - field=models.ManyToManyField( - blank=True, - related_name="workflowtriggers_has_all", - to="documents.tag", - verbose_name="has all of these tag(s)", - ), - ), - migrations.AddField( - model_name="workflowtrigger", - name="filter_has_not_correspondents", - field=models.ManyToManyField( - blank=True, - related_name="workflowtriggers_has_not_correspondent", - to="documents.correspondent", - verbose_name="does not have these correspondent(s)", - ), - ), - migrations.AddField( - model_name="workflowtrigger", - name="filter_has_not_document_types", - field=models.ManyToManyField( - blank=True, - related_name="workflowtriggers_has_not_document_type", - to="documents.documenttype", - verbose_name="does not have these document type(s)", - ), - ), - migrations.AddField( - model_name="workflowtrigger", - name="filter_has_not_storage_paths", - field=models.ManyToManyField( - blank=True, - related_name="workflowtriggers_has_not_storage_path", - to="documents.storagepath", - verbose_name="does not have these storage path(s)", - ), - ), - migrations.AddField( - model_name="workflowtrigger", - name="filter_has_not_tags", - field=models.ManyToManyField( - blank=True, - related_name="workflowtriggers_has_not", - to="documents.tag", - verbose_name="does not have these tag(s)", - ), - ), - ] diff --git a/src/documents/migrations/1073_migrate_workflow_title_jinja.py b/src/documents/migrations/1073_migrate_workflow_title_jinja.py deleted file mode 100644 index 9d80a277f..000000000 --- a/src/documents/migrations/1073_migrate_workflow_title_jinja.py +++ /dev/null @@ -1,63 +0,0 @@ -# Generated by Django 5.2.5 on 2025-08-27 22:02 -import logging - -from django.db import migrations -from django.db import models - -from documents.templating.utils import convert_format_str_to_template_format - -logger = logging.getLogger("paperless.migrations") - - -def convert_from_format_to_template(apps, schema_editor): - WorkflowAction = apps.get_model("documents", "WorkflowAction") - - batch_size = 500 - actions_to_update = [] - - queryset = ( - WorkflowAction.objects.filter(assign_title__isnull=False) - .exclude(assign_title="") - .only("id", "assign_title") - ) - - for action in queryset: - action.assign_title = convert_format_str_to_template_format( - action.assign_title, - ) - logger.debug( - "Converted WorkflowAction id %d title to template format: %s", - action.id, - action.assign_title, - ) - actions_to_update.append(action) - - if actions_to_update: - WorkflowAction.objects.bulk_update( - actions_to_update, - ["assign_title"], - batch_size=batch_size, - ) - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1072_workflowtrigger_filter_custom_field_query_and_more"), - ] - - operations = [ - migrations.AlterField( - model_name="workflowaction", - name="assign_title", - field=models.TextField( - blank=True, - help_text="Assign a document title, must be a Jinja2 template, see documentation.", - null=True, - verbose_name="assign title", - ), - ), - migrations.RunPython( - convert_from_format_to_template, - migrations.RunPython.noop, - ), - ] diff --git a/src/documents/migrations/1074_workflowrun_deleted_at_workflowrun_restored_at_and_more.py b/src/documents/migrations/1074_workflowrun_deleted_at_workflowrun_restored_at_and_more.py deleted file mode 100644 index 4381eabb1..000000000 --- a/src/documents/migrations/1074_workflowrun_deleted_at_workflowrun_restored_at_and_more.py +++ /dev/null @@ -1,28 +0,0 @@ -# Generated by Django 5.2.6 on 2025-10-27 15:11 - -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1073_migrate_workflow_title_jinja"), - ] - - operations = [ - migrations.AddField( - model_name="workflowrun", - name="deleted_at", - field=models.DateTimeField(blank=True, null=True), - ), - migrations.AddField( - model_name="workflowrun", - name="restored_at", - field=models.DateTimeField(blank=True, null=True), - ), - migrations.AddField( - model_name="workflowrun", - name="transaction_id", - field=models.UUIDField(blank=True, null=True), - ), - ] diff --git a/src/documents/migrations/1075_workflowaction_order.py b/src/documents/migrations/1075_workflowaction_order.py deleted file mode 100644 index f7101bf7e..000000000 --- a/src/documents/migrations/1075_workflowaction_order.py +++ /dev/null @@ -1,28 +0,0 @@ -# Generated by Django 5.2.7 on 2026-01-14 16:53 - -from django.db import migrations -from django.db import models -from django.db.models import F - - -def populate_action_order(apps, schema_editor): - WorkflowAction = apps.get_model("documents", "WorkflowAction") - WorkflowAction.objects.all().update(order=F("id")) - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1074_workflowrun_deleted_at_workflowrun_restored_at_and_more"), - ] - - operations = [ - migrations.AddField( - model_name="workflowaction", - name="order", - field=models.PositiveIntegerField(default=0, verbose_name="order"), - ), - migrations.RunPython( - populate_action_order, - reverse_code=migrations.RunPython.noop, - ), - ] diff --git a/src/documents/migrations/1076_alter_paperlesstask_task_name.py b/src/documents/migrations/1076_alter_paperlesstask_task_name.py deleted file mode 100644 index bb5406255..000000000 --- a/src/documents/migrations/1076_alter_paperlesstask_task_name.py +++ /dev/null @@ -1,30 +0,0 @@ -# Generated by Django 5.1.8 on 2025-04-30 02:38 - -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1075_workflowaction_order"), - ] - - operations = [ - migrations.AlterField( - model_name="paperlesstask", - name="task_name", - field=models.CharField( - choices=[ - ("consume_file", "Consume File"), - ("train_classifier", "Train Classifier"), - ("check_sanity", "Check Sanity"), - ("index_optimize", "Index Optimize"), - ("llmindex_update", "LLM Index Update"), - ], - help_text="Name of the task that was run", - max_length=255, - null=True, - verbose_name="Task Name", - ), - ), - ] diff --git a/src/documents/tests/test_management_superuser.py b/src/documents/tests/test_management_superuser.py index 01f03c8e1..343d5f568 100644 --- a/src/documents/tests/test_management_superuser.py +++ b/src/documents/tests/test_management_superuser.py @@ -33,8 +33,7 @@ class TestManageSuperUser(DirectoriesMixin, TestCase): # just the consumer user which is created # during migration, and AnonymousUser - self.assertEqual(User.objects.count(), 2) - self.assertTrue(User.objects.filter(username="consumer").exists()) + self.assertEqual(User.objects.count(), 1) self.assertEqual(User.objects.filter(is_superuser=True).count(), 0) self.assertEqual( out, @@ -54,7 +53,7 @@ class TestManageSuperUser(DirectoriesMixin, TestCase): # count is 3 as there's the consumer # user already created during migration, and AnonymousUser user: User = User.objects.get_by_natural_key("admin") - self.assertEqual(User.objects.count(), 3) + self.assertEqual(User.objects.count(), 2) self.assertTrue(user.is_superuser) self.assertEqual(user.email, "root@localhost") self.assertEqual(out, 'Created superuser "admin" with provided password.\n') @@ -71,7 +70,7 @@ class TestManageSuperUser(DirectoriesMixin, TestCase): out = self.call_command(environ={"PAPERLESS_ADMIN_PASSWORD": "123456"}) - self.assertEqual(User.objects.count(), 3) + self.assertEqual(User.objects.count(), 2) with self.assertRaises(User.DoesNotExist): User.objects.get_by_natural_key("admin") self.assertEqual( @@ -92,7 +91,7 @@ class TestManageSuperUser(DirectoriesMixin, TestCase): out = self.call_command(environ={"PAPERLESS_ADMIN_PASSWORD": "123456"}) - self.assertEqual(User.objects.count(), 3) + self.assertEqual(User.objects.count(), 2) user: User = User.objects.get_by_natural_key("admin") self.assertTrue(user.check_password("password")) self.assertEqual(out, "Did not create superuser, a user admin already exists\n") @@ -111,7 +110,7 @@ class TestManageSuperUser(DirectoriesMixin, TestCase): out = self.call_command(environ={"PAPERLESS_ADMIN_PASSWORD": "123456"}) - self.assertEqual(User.objects.count(), 3) + self.assertEqual(User.objects.count(), 2) user: User = User.objects.get_by_natural_key("admin") self.assertTrue(user.check_password("password")) self.assertFalse(user.is_superuser) @@ -150,7 +149,7 @@ class TestManageSuperUser(DirectoriesMixin, TestCase): ) user: User = User.objects.get_by_natural_key("admin") - self.assertEqual(User.objects.count(), 3) + self.assertEqual(User.objects.count(), 2) self.assertTrue(user.is_superuser) self.assertEqual(user.email, "hello@world.com") self.assertEqual(user.username, "admin") @@ -174,7 +173,7 @@ class TestManageSuperUser(DirectoriesMixin, TestCase): ) user: User = User.objects.get_by_natural_key("super") - self.assertEqual(User.objects.count(), 3) + self.assertEqual(User.objects.count(), 2) self.assertTrue(user.is_superuser) self.assertEqual(user.email, "hello@world.com") self.assertEqual(user.username, "super") diff --git a/src/paperless_mail/migrations/0001_initial.py b/src/paperless_mail/migrations/0001_initial.py index f7e717f0e..aab6a8239 100644 --- a/src/paperless_mail/migrations/0001_initial.py +++ b/src/paperless_mail/migrations/0001_initial.py @@ -1,6 +1,8 @@ -# Generated by Django 3.1.3 on 2020-11-15 22:54 +# Generated by Django 5.2.9 on 2026-01-20 18:46 import django.db.models.deletion +import django.utils.timezone +from django.conf import settings from django.db import migrations from django.db import models @@ -9,7 +11,8 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ("documents", "1002_auto_20201111_1105"), + ("documents", "0001_initial"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] operations = [ @@ -25,9 +28,23 @@ class Migration(migrations.Migration): verbose_name="ID", ), ), - ("name", models.CharField(max_length=256, unique=True)), - ("imap_server", models.CharField(max_length=256)), - ("imap_port", models.IntegerField(blank=True, null=True)), + ( + "name", + models.CharField(max_length=256, unique=True, verbose_name="name"), + ), + ( + "imap_server", + models.CharField(max_length=256, verbose_name="IMAP server"), + ), + ( + "imap_port", + models.IntegerField( + blank=True, + help_text="This is usually 143 for unencrypted and STARTTLS connections, and 993 for SSL connections.", + null=True, + verbose_name="IMAP port", + ), + ), ( "imap_security", models.PositiveIntegerField( @@ -37,11 +54,69 @@ class Migration(migrations.Migration): (3, "Use STARTTLS"), ], default=2, + verbose_name="IMAP security", + ), + ), + ("username", models.CharField(max_length=256, verbose_name="username")), + ("password", models.TextField(verbose_name="password")), + ( + "is_token", + models.BooleanField( + default=False, + verbose_name="Is token authentication", + ), + ), + ( + "character_set", + models.CharField( + default="UTF-8", + help_text="The character set to use when communicating with the mail server, such as 'UTF-8' or 'US-ASCII'.", + max_length=256, + verbose_name="character set", + ), + ), + ( + "account_type", + models.PositiveIntegerField( + choices=[(1, "IMAP"), (2, "Gmail OAuth"), (3, "Outlook OAuth")], + default=1, + verbose_name="account type", + ), + ), + ( + "refresh_token", + models.TextField( + blank=True, + help_text="The refresh token to use for token authentication e.g. with oauth2.", + null=True, + verbose_name="refresh token", + ), + ), + ( + "expiration", + models.DateTimeField( + blank=True, + help_text="The expiration date of the refresh token. ", + null=True, + verbose_name="expiration", + ), + ), + ( + "owner", + models.ForeignKey( + blank=True, + default=None, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to=settings.AUTH_USER_MODEL, + verbose_name="owner", ), ), - ("username", models.CharField(max_length=256)), - ("password", models.CharField(max_length=256)), ], + options={ + "verbose_name": "mail account", + "verbose_name_plural": "mail accounts", + }, ), migrations.CreateModel( name="MailRule", @@ -55,21 +130,126 @@ class Migration(migrations.Migration): verbose_name="ID", ), ), - ("name", models.CharField(max_length=256)), - ("folder", models.CharField(default="INBOX", max_length=256)), + ("name", models.CharField(max_length=256, verbose_name="name")), + ("order", models.IntegerField(default=0, verbose_name="order")), + ("enabled", models.BooleanField(default=True, verbose_name="enabled")), + ( + "folder", + models.CharField( + default="INBOX", + help_text="Subfolders must be separated by a delimiter, often a dot ('.') or slash ('/'), but it varies by mail server.", + max_length=256, + verbose_name="folder", + ), + ), ( "filter_from", - models.CharField(blank=True, max_length=256, null=True), + models.CharField( + blank=True, + max_length=256, + null=True, + verbose_name="filter from", + ), + ), + ( + "filter_to", + models.CharField( + blank=True, + max_length=256, + null=True, + verbose_name="filter to", + ), ), ( "filter_subject", - models.CharField(blank=True, max_length=256, null=True), + models.CharField( + blank=True, + max_length=256, + null=True, + verbose_name="filter subject", + ), ), ( "filter_body", - models.CharField(blank=True, max_length=256, null=True), + models.CharField( + blank=True, + max_length=256, + null=True, + verbose_name="filter body", + ), + ), + ( + "filter_attachment_filename_include", + models.CharField( + blank=True, + help_text="Only consume documents which entirely match this filename if specified. Wildcards such as *.pdf or *invoice* are allowed. Case insensitive.", + max_length=256, + null=True, + verbose_name="filter attachment filename inclusive", + ), + ), + ( + "filter_attachment_filename_exclude", + models.CharField( + blank=True, + help_text="Do not consume documents which entirely match this filename if specified. Wildcards such as *.pdf or *invoice* are allowed. Case insensitive.", + max_length=256, + null=True, + verbose_name="filter attachment filename exclusive", + ), + ), + ( + "maximum_age", + models.PositiveIntegerField( + default=30, + help_text="Specified in days.", + verbose_name="maximum age", + ), + ), + ( + "attachment_type", + models.PositiveIntegerField( + choices=[ + (1, "Only process attachments."), + (2, "Process all files, including 'inline' attachments."), + ], + default=1, + help_text="Inline attachments include embedded images, so it's best to combine this option with a filename filter.", + verbose_name="attachment type", + ), + ), + ( + "consumption_scope", + models.PositiveIntegerField( + choices=[ + (1, "Only process attachments."), + ( + 2, + "Process full Mail (with embedded attachments in file) as .eml", + ), + ( + 3, + "Process full Mail (with embedded attachments in file) as .eml + process attachments as separate documents", + ), + ], + default=1, + verbose_name="consumption scope", + ), + ), + ( + "pdf_layout", + models.PositiveIntegerField( + choices=[ + (0, "System default"), + (1, "Text, then HTML"), + (2, "HTML, then text"), + (3, "HTML only"), + (4, "Text only"), + ], + default=0, + verbose_name="pdf layout", + ), ), - ("maximum_age", models.PositiveIntegerField(default=30)), ( "action", models.PositiveIntegerField( @@ -78,18 +258,23 @@ class Migration(migrations.Migration): (2, "Move to specified folder"), (3, "Mark as read, don't process read mails"), (4, "Flag the mail, don't process flagged mails"), + ( + 5, + "Tag the mail with specified tag, don't process tagged mails", + ), ], default=3, - help_text="The action applied to the mail. This action is only performed when documents were consumed from the mail. Mails without attachments will remain entirely untouched.", + verbose_name="action", ), ), ( "action_parameter", models.CharField( blank=True, - help_text="Additional parameter for the action selected above, i.e., the target folder of the move to folder action.", + help_text="Additional parameter for the action selected above, i.e., the target folder of the move to folder action. Subfolders must be separated by dots.", max_length=256, null=True, + verbose_name="action parameter", ), ), ( @@ -98,8 +283,10 @@ class Migration(migrations.Migration): choices=[ (1, "Use subject as title"), (2, "Use attachment filename as title"), + (3, "Do not assign title from rule"), ], default=1, + verbose_name="assign title from", ), ), ( @@ -112,6 +299,14 @@ class Migration(migrations.Migration): (4, "Use correspondent selected below"), ], default=1, + verbose_name="assign correspondent from", + ), + ), + ( + "assign_owner_from_rule", + models.BooleanField( + default=True, + verbose_name="Assign the rule owner to documents", ), ), ( @@ -120,6 +315,7 @@ class Migration(migrations.Migration): on_delete=django.db.models.deletion.CASCADE, related_name="rules", to="paperless_mail.mailaccount", + verbose_name="account", ), ), ( @@ -129,6 +325,7 @@ class Migration(migrations.Migration): null=True, on_delete=django.db.models.deletion.SET_NULL, to="documents.correspondent", + verbose_name="assign this correspondent", ), ), ( @@ -138,17 +335,136 @@ class Migration(migrations.Migration): null=True, on_delete=django.db.models.deletion.SET_NULL, to="documents.documenttype", + verbose_name="assign this document type", ), ), ( - "assign_tag", + "assign_tags", + models.ManyToManyField( + blank=True, + to="documents.tag", + verbose_name="assign this tag", + ), + ), + ( + "owner", models.ForeignKey( blank=True, + default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, - to="documents.tag", + to=settings.AUTH_USER_MODEL, + verbose_name="owner", ), ), ], + options={ + "verbose_name": "mail rule", + "verbose_name_plural": "mail rules", + }, + ), + migrations.CreateModel( + name="ProcessedMail", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "folder", + models.CharField( + editable=False, + max_length=256, + verbose_name="folder", + ), + ), + ( + "uid", + models.CharField( + editable=False, + max_length=256, + verbose_name="uid", + ), + ), + ( + "subject", + models.CharField( + editable=False, + max_length=256, + verbose_name="subject", + ), + ), + ( + "received", + models.DateTimeField(editable=False, verbose_name="received"), + ), + ( + "processed", + models.DateTimeField( + default=django.utils.timezone.now, + editable=False, + verbose_name="processed", + ), + ), + ( + "status", + models.CharField( + editable=False, + max_length=256, + verbose_name="status", + ), + ), + ( + "error", + models.TextField( + blank=True, + editable=False, + null=True, + verbose_name="error", + ), + ), + ( + "owner", + models.ForeignKey( + blank=True, + default=None, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to=settings.AUTH_USER_MODEL, + verbose_name="owner", + ), + ), + ( + "rule", + models.ForeignKey( + editable=False, + on_delete=django.db.models.deletion.CASCADE, + to="paperless_mail.mailrule", + ), + ), + ], + options={ + "abstract": False, + }, + ), + migrations.AddConstraint( + model_name="mailrule", + constraint=models.UniqueConstraint( + fields=("name", "owner"), + name="paperless_mail_mailrule_unique_name_owner", + ), + ), + migrations.AddConstraint( + model_name="mailrule", + constraint=models.UniqueConstraint( + condition=models.Q(("owner__isnull", True)), + fields=("name",), + name="paperless_mail_mailrule_name_unique", + ), ), ] diff --git a/src/paperless_mail/migrations/0001_initial_squashed_0009_mailrule_assign_tags.py b/src/paperless_mail/migrations/0001_initial_squashed_0009_mailrule_assign_tags.py deleted file mode 100644 index aad22918f..000000000 --- a/src/paperless_mail/migrations/0001_initial_squashed_0009_mailrule_assign_tags.py +++ /dev/null @@ -1,477 +0,0 @@ -# Generated by Django 4.2.13 on 2024-06-28 17:46 - -import django.db.migrations.operations.special -import django.db.models.deletion -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - replaces = [ - ("paperless_mail", "0001_initial"), - ("paperless_mail", "0002_auto_20201117_1334"), - ("paperless_mail", "0003_auto_20201118_1940"), - ("paperless_mail", "0004_mailrule_order"), - ("paperless_mail", "0005_help_texts"), - ("paperless_mail", "0006_auto_20210101_2340"), - ("paperless_mail", "0007_auto_20210106_0138"), - ("paperless_mail", "0008_auto_20210516_0940"), - ("paperless_mail", "0009_mailrule_assign_tags"), - ] - - dependencies = [ - ("documents", "1002_auto_20201111_1105"), - ("documents", "1011_auto_20210101_2340"), - ] - - operations = [ - migrations.CreateModel( - name="MailAccount", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("name", models.CharField(max_length=256, unique=True)), - ("imap_server", models.CharField(max_length=256)), - ("imap_port", models.IntegerField(blank=True, null=True)), - ( - "imap_security", - models.PositiveIntegerField( - choices=[ - (1, "No encryption"), - (2, "Use SSL"), - (3, "Use STARTTLS"), - ], - default=2, - ), - ), - ("username", models.CharField(max_length=256)), - ("password", models.CharField(max_length=256)), - ], - ), - migrations.CreateModel( - name="MailRule", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("name", models.CharField(max_length=256)), - ("folder", models.CharField(default="INBOX", max_length=256)), - ( - "filter_from", - models.CharField(blank=True, max_length=256, null=True), - ), - ( - "filter_subject", - models.CharField(blank=True, max_length=256, null=True), - ), - ( - "filter_body", - models.CharField(blank=True, max_length=256, null=True), - ), - ("maximum_age", models.PositiveIntegerField(default=30)), - ( - "action", - models.PositiveIntegerField( - choices=[ - (1, "Delete"), - (2, "Move to specified folder"), - (3, "Mark as read, don't process read mails"), - (4, "Flag the mail, don't process flagged mails"), - ], - default=3, - help_text="The action applied to the mail. This action is only performed when documents were consumed from the mail. Mails without attachments will remain entirely untouched.", - ), - ), - ( - "action_parameter", - models.CharField( - blank=True, - help_text="Additional parameter for the action selected above, i.e., the target folder of the move to folder action.", - max_length=256, - null=True, - ), - ), - ( - "assign_title_from", - models.PositiveIntegerField( - choices=[ - (1, "Use subject as title"), - (2, "Use attachment filename as title"), - ], - default=1, - ), - ), - ( - "assign_correspondent_from", - models.PositiveIntegerField( - choices=[ - (1, "Do not assign a correspondent"), - (2, "Use mail address"), - (3, "Use name (or mail address if not available)"), - (4, "Use correspondent selected below"), - ], - default=1, - ), - ), - ( - "account", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="rules", - to="paperless_mail.mailaccount", - ), - ), - ( - "assign_correspondent", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to="documents.correspondent", - ), - ), - ( - "assign_document_type", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to="documents.documenttype", - ), - ), - ( - "assign_tag", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to="documents.tag", - ), - ), - ], - ), - migrations.RunPython( - code=django.db.migrations.operations.special.RunPython.noop, - reverse_code=django.db.migrations.operations.special.RunPython.noop, - ), - migrations.AlterField( - model_name="mailaccount", - name="imap_port", - field=models.IntegerField( - blank=True, - help_text="This is usually 143 for unencrypted and STARTTLS connections, and 993 for SSL connections.", - null=True, - ), - ), - migrations.AlterField( - model_name="mailrule", - name="name", - field=models.CharField(max_length=256, unique=True), - ), - migrations.AddField( - model_name="mailrule", - name="order", - field=models.IntegerField(default=0), - ), - migrations.AlterField( - model_name="mailrule", - name="action", - field=models.PositiveIntegerField( - choices=[ - (3, "Mark as read, don't process read mails"), - (4, "Flag the mail, don't process flagged mails"), - (2, "Move to specified folder"), - (1, "Delete"), - ], - default=3, - ), - ), - migrations.AlterField( - model_name="mailrule", - name="maximum_age", - field=models.PositiveIntegerField( - default=30, - help_text="Specified in days.", - ), - ), - migrations.AlterModelOptions( - name="mailaccount", - options={ - "verbose_name": "mail account", - "verbose_name_plural": "mail accounts", - }, - ), - migrations.AlterModelOptions( - name="mailrule", - options={"verbose_name": "mail rule", "verbose_name_plural": "mail rules"}, - ), - migrations.AlterField( - model_name="mailaccount", - name="imap_port", - field=models.IntegerField( - blank=True, - help_text="This is usually 143 for unencrypted and STARTTLS connections, and 993 for SSL connections.", - null=True, - verbose_name="IMAP port", - ), - ), - migrations.AlterField( - model_name="mailaccount", - name="imap_security", - field=models.PositiveIntegerField( - choices=[(1, "No encryption"), (2, "Use SSL"), (3, "Use STARTTLS")], - default=2, - verbose_name="IMAP security", - ), - ), - migrations.AlterField( - model_name="mailaccount", - name="imap_server", - field=models.CharField(max_length=256, verbose_name="IMAP server"), - ), - migrations.AlterField( - model_name="mailaccount", - name="name", - field=models.CharField(max_length=256, unique=True, verbose_name="name"), - ), - migrations.AlterField( - model_name="mailaccount", - name="password", - field=models.CharField(max_length=256, verbose_name="password"), - ), - migrations.AlterField( - model_name="mailaccount", - name="username", - field=models.CharField(max_length=256, verbose_name="username"), - ), - migrations.AlterField( - model_name="mailrule", - name="account", - field=models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="rules", - to="paperless_mail.mailaccount", - verbose_name="account", - ), - ), - migrations.AlterField( - model_name="mailrule", - name="action", - field=models.PositiveIntegerField( - choices=[ - (3, "Mark as read, don't process read mails"), - (4, "Flag the mail, don't process flagged mails"), - (2, "Move to specified folder"), - (1, "Delete"), - ], - default=3, - verbose_name="action", - ), - ), - migrations.AlterField( - model_name="mailrule", - name="action_parameter", - field=models.CharField( - blank=True, - help_text="Additional parameter for the action selected above, i.e., the target folder of the move to folder action.", - max_length=256, - null=True, - verbose_name="action parameter", - ), - ), - migrations.AlterField( - model_name="mailrule", - name="assign_correspondent", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to="documents.correspondent", - verbose_name="assign this correspondent", - ), - ), - migrations.AlterField( - model_name="mailrule", - name="assign_correspondent_from", - field=models.PositiveIntegerField( - choices=[ - (1, "Do not assign a correspondent"), - (2, "Use mail address"), - (3, "Use name (or mail address if not available)"), - (4, "Use correspondent selected below"), - ], - default=1, - verbose_name="assign correspondent from", - ), - ), - migrations.AlterField( - model_name="mailrule", - name="assign_document_type", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to="documents.documenttype", - verbose_name="assign this document type", - ), - ), - migrations.AlterField( - model_name="mailrule", - name="assign_tag", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to="documents.tag", - verbose_name="assign this tag", - ), - ), - migrations.AlterField( - model_name="mailrule", - name="assign_title_from", - field=models.PositiveIntegerField( - choices=[ - (1, "Use subject as title"), - (2, "Use attachment filename as title"), - ], - default=1, - verbose_name="assign title from", - ), - ), - migrations.AlterField( - model_name="mailrule", - name="filter_body", - field=models.CharField( - blank=True, - max_length=256, - null=True, - verbose_name="filter body", - ), - ), - migrations.AlterField( - model_name="mailrule", - name="filter_from", - field=models.CharField( - blank=True, - max_length=256, - null=True, - verbose_name="filter from", - ), - ), - migrations.AlterField( - model_name="mailrule", - name="filter_subject", - field=models.CharField( - blank=True, - max_length=256, - null=True, - verbose_name="filter subject", - ), - ), - migrations.AlterField( - model_name="mailrule", - name="folder", - field=models.CharField( - default="INBOX", - max_length=256, - verbose_name="folder", - ), - ), - migrations.AlterField( - model_name="mailrule", - name="maximum_age", - field=models.PositiveIntegerField( - default=30, - help_text="Specified in days.", - verbose_name="maximum age", - ), - ), - migrations.AlterField( - model_name="mailrule", - name="name", - field=models.CharField(max_length=256, unique=True, verbose_name="name"), - ), - migrations.AlterField( - model_name="mailrule", - name="order", - field=models.IntegerField(default=0, verbose_name="order"), - ), - migrations.AddField( - model_name="mailrule", - name="attachment_type", - field=models.PositiveIntegerField( - choices=[ - (1, "Only process attachments."), - (2, "Process all files, including 'inline' attachments."), - ], - default=1, - help_text="Inline attachments include embedded images, so it's best to combine this option with a filename filter.", - verbose_name="attachment type", - ), - ), - migrations.AddField( - model_name="mailrule", - name="filter_attachment_filename", - field=models.CharField( - blank=True, - help_text="Only consume documents which entirely match this filename if specified. Wildcards such as *.pdf or *invoice* are allowed. Case insensitive.", - max_length=256, - null=True, - verbose_name="filter attachment filename", - ), - ), - migrations.AddField( - model_name="mailaccount", - name="character_set", - field=models.CharField( - default="UTF-8", - help_text="The character set to use when communicating with the mail server, such as 'UTF-8' or 'US-ASCII'.", - max_length=256, - verbose_name="character set", - ), - ), - migrations.AlterField( - model_name="mailrule", - name="action_parameter", - field=models.CharField( - blank=True, - help_text="Additional parameter for the action selected above, i.e., the target folder of the move to folder action. Subfolders must be separated by dots.", - max_length=256, - null=True, - verbose_name="action parameter", - ), - ), - migrations.AlterField( - model_name="mailrule", - name="folder", - field=models.CharField( - default="INBOX", - help_text="Subfolders must be separated by dots.", - max_length=256, - verbose_name="folder", - ), - ), - migrations.AddField( - model_name="mailrule", - name="assign_tags", - field=models.ManyToManyField( - blank=True, - related_name="mail_rules_multi", - to="documents.tag", - verbose_name="assign this tag", - ), - ), - ] diff --git a/src/paperless_mail/migrations/0002_auto_20201117_1334.py b/src/paperless_mail/migrations/0002_auto_20201117_1334.py deleted file mode 100644 index 1f4df3f6d..000000000 --- a/src/paperless_mail/migrations/0002_auto_20201117_1334.py +++ /dev/null @@ -1,12 +0,0 @@ -# Generated by Django 3.1.3 on 2020-11-17 13:34 - -from django.db import migrations -from django.db.migrations import RunPython - - -class Migration(migrations.Migration): - dependencies = [ - ("paperless_mail", "0001_initial"), - ] - - operations = [RunPython(migrations.RunPython.noop, migrations.RunPython.noop)] diff --git a/src/paperless_mail/migrations/0003_auto_20201118_1940.py b/src/paperless_mail/migrations/0003_auto_20201118_1940.py deleted file mode 100644 index a1263db05..000000000 --- a/src/paperless_mail/migrations/0003_auto_20201118_1940.py +++ /dev/null @@ -1,27 +0,0 @@ -# Generated by Django 3.1.3 on 2020-11-18 19:40 - -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("paperless_mail", "0002_auto_20201117_1334"), - ] - - operations = [ - migrations.AlterField( - model_name="mailaccount", - name="imap_port", - field=models.IntegerField( - blank=True, - help_text="This is usually 143 for unencrypted and STARTTLS connections, and 993 for SSL connections.", - null=True, - ), - ), - migrations.AlterField( - model_name="mailrule", - name="name", - field=models.CharField(max_length=256, unique=True), - ), - ] diff --git a/src/paperless_mail/migrations/0004_mailrule_order.py b/src/paperless_mail/migrations/0004_mailrule_order.py deleted file mode 100644 index 4ffc0a6e5..000000000 --- a/src/paperless_mail/migrations/0004_mailrule_order.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 3.1.3 on 2020-11-21 21:51 - -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("paperless_mail", "0003_auto_20201118_1940"), - ] - - operations = [ - migrations.AddField( - model_name="mailrule", - name="order", - field=models.IntegerField(default=0), - ), - ] diff --git a/src/paperless_mail/migrations/0005_help_texts.py b/src/paperless_mail/migrations/0005_help_texts.py deleted file mode 100644 index 8e49238e9..000000000 --- a/src/paperless_mail/migrations/0005_help_texts.py +++ /dev/null @@ -1,34 +0,0 @@ -# Generated by Django 3.1.3 on 2020-11-22 10:36 - -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("paperless_mail", "0004_mailrule_order"), - ] - - operations = [ - migrations.AlterField( - model_name="mailrule", - name="action", - field=models.PositiveIntegerField( - choices=[ - (3, "Mark as read, don't process read mails"), - (4, "Flag the mail, don't process flagged mails"), - (2, "Move to specified folder"), - (1, "Delete"), - ], - default=3, - ), - ), - migrations.AlterField( - model_name="mailrule", - name="maximum_age", - field=models.PositiveIntegerField( - default=30, - help_text="Specified in days.", - ), - ), - ] diff --git a/src/paperless_mail/migrations/0006_auto_20210101_2340.py b/src/paperless_mail/migrations/0006_auto_20210101_2340.py deleted file mode 100644 index 2c2ff9fa8..000000000 --- a/src/paperless_mail/migrations/0006_auto_20210101_2340.py +++ /dev/null @@ -1,217 +0,0 @@ -# Generated by Django 3.1.4 on 2021-01-01 23:40 - -import django.db.models.deletion -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1011_auto_20210101_2340"), - ("paperless_mail", "0005_help_texts"), - ] - - operations = [ - migrations.AlterModelOptions( - name="mailaccount", - options={ - "verbose_name": "mail account", - "verbose_name_plural": "mail accounts", - }, - ), - migrations.AlterModelOptions( - name="mailrule", - options={"verbose_name": "mail rule", "verbose_name_plural": "mail rules"}, - ), - migrations.AlterField( - model_name="mailaccount", - name="imap_port", - field=models.IntegerField( - blank=True, - help_text="This is usually 143 for unencrypted and STARTTLS connections, and 993 for SSL connections.", - null=True, - verbose_name="IMAP port", - ), - ), - migrations.AlterField( - model_name="mailaccount", - name="imap_security", - field=models.PositiveIntegerField( - choices=[(1, "No encryption"), (2, "Use SSL"), (3, "Use STARTTLS")], - default=2, - verbose_name="IMAP security", - ), - ), - migrations.AlterField( - model_name="mailaccount", - name="imap_server", - field=models.CharField(max_length=256, verbose_name="IMAP server"), - ), - migrations.AlterField( - model_name="mailaccount", - name="name", - field=models.CharField(max_length=256, unique=True, verbose_name="name"), - ), - migrations.AlterField( - model_name="mailaccount", - name="password", - field=models.CharField(max_length=256, verbose_name="password"), - ), - migrations.AlterField( - model_name="mailaccount", - name="username", - field=models.CharField(max_length=256, verbose_name="username"), - ), - migrations.AlterField( - model_name="mailrule", - name="account", - field=models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="rules", - to="paperless_mail.mailaccount", - verbose_name="account", - ), - ), - migrations.AlterField( - model_name="mailrule", - name="action", - field=models.PositiveIntegerField( - choices=[ - (3, "Mark as read, don't process read mails"), - (4, "Flag the mail, don't process flagged mails"), - (2, "Move to specified folder"), - (1, "Delete"), - ], - default=3, - verbose_name="action", - ), - ), - migrations.AlterField( - model_name="mailrule", - name="action_parameter", - field=models.CharField( - blank=True, - help_text="Additional parameter for the action selected above, i.e., the target folder of the move to folder action.", - max_length=256, - null=True, - verbose_name="action parameter", - ), - ), - migrations.AlterField( - model_name="mailrule", - name="assign_correspondent", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to="documents.correspondent", - verbose_name="assign this correspondent", - ), - ), - migrations.AlterField( - model_name="mailrule", - name="assign_correspondent_from", - field=models.PositiveIntegerField( - choices=[ - (1, "Do not assign a correspondent"), - (2, "Use mail address"), - (3, "Use name (or mail address if not available)"), - (4, "Use correspondent selected below"), - ], - default=1, - verbose_name="assign correspondent from", - ), - ), - migrations.AlterField( - model_name="mailrule", - name="assign_document_type", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to="documents.documenttype", - verbose_name="assign this document type", - ), - ), - migrations.AlterField( - model_name="mailrule", - name="assign_tag", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to="documents.tag", - verbose_name="assign this tag", - ), - ), - migrations.AlterField( - model_name="mailrule", - name="assign_title_from", - field=models.PositiveIntegerField( - choices=[ - (1, "Use subject as title"), - (2, "Use attachment filename as title"), - ], - default=1, - verbose_name="assign title from", - ), - ), - migrations.AlterField( - model_name="mailrule", - name="filter_body", - field=models.CharField( - blank=True, - max_length=256, - null=True, - verbose_name="filter body", - ), - ), - migrations.AlterField( - model_name="mailrule", - name="filter_from", - field=models.CharField( - blank=True, - max_length=256, - null=True, - verbose_name="filter from", - ), - ), - migrations.AlterField( - model_name="mailrule", - name="filter_subject", - field=models.CharField( - blank=True, - max_length=256, - null=True, - verbose_name="filter subject", - ), - ), - migrations.AlterField( - model_name="mailrule", - name="folder", - field=models.CharField( - default="INBOX", - max_length=256, - verbose_name="folder", - ), - ), - migrations.AlterField( - model_name="mailrule", - name="maximum_age", - field=models.PositiveIntegerField( - default=30, - help_text="Specified in days.", - verbose_name="maximum age", - ), - ), - migrations.AlterField( - model_name="mailrule", - name="name", - field=models.CharField(max_length=256, unique=True, verbose_name="name"), - ), - migrations.AlterField( - model_name="mailrule", - name="order", - field=models.IntegerField(default=0, verbose_name="order"), - ), - ] diff --git a/src/paperless_mail/migrations/0007_auto_20210106_0138.py b/src/paperless_mail/migrations/0007_auto_20210106_0138.py deleted file mode 100644 index c51a4aebe..000000000 --- a/src/paperless_mail/migrations/0007_auto_20210106_0138.py +++ /dev/null @@ -1,37 +0,0 @@ -# Generated by Django 3.1.5 on 2021-01-06 01:38 - -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("paperless_mail", "0006_auto_20210101_2340"), - ] - - operations = [ - migrations.AddField( - model_name="mailrule", - name="attachment_type", - field=models.PositiveIntegerField( - choices=[ - (1, "Only process attachments."), - (2, "Process all files, including 'inline' attachments."), - ], - default=1, - help_text="Inline attachments include embedded images, so it's best to combine this option with a filename filter.", - verbose_name="attachment type", - ), - ), - migrations.AddField( - model_name="mailrule", - name="filter_attachment_filename", - field=models.CharField( - blank=True, - help_text="Only consume documents which entirely match this filename if specified. Wildcards such as *.pdf or *invoice* are allowed. Case insensitive.", - max_length=256, - null=True, - verbose_name="filter attachment filename", - ), - ), - ] diff --git a/src/paperless_mail/migrations/0008_auto_20210516_0940.py b/src/paperless_mail/migrations/0008_auto_20210516_0940.py deleted file mode 100644 index b2fc062dd..000000000 --- a/src/paperless_mail/migrations/0008_auto_20210516_0940.py +++ /dev/null @@ -1,44 +0,0 @@ -# Generated by Django 3.2.3 on 2021-05-16 09:40 - -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("paperless_mail", "0007_auto_20210106_0138"), - ] - - operations = [ - migrations.AddField( - model_name="mailaccount", - name="character_set", - field=models.CharField( - default="UTF-8", - help_text="The character set to use when communicating with the mail server, such as 'UTF-8' or 'US-ASCII'.", - max_length=256, - verbose_name="character set", - ), - ), - migrations.AlterField( - model_name="mailrule", - name="action_parameter", - field=models.CharField( - blank=True, - help_text="Additional parameter for the action selected above, i.e., the target folder of the move to folder action. Subfolders must be separated by dots.", - max_length=256, - null=True, - verbose_name="action parameter", - ), - ), - migrations.AlterField( - model_name="mailrule", - name="folder", - field=models.CharField( - default="INBOX", - help_text="Subfolders must be separated by dots.", - max_length=256, - verbose_name="folder", - ), - ), - ] diff --git a/src/paperless_mail/migrations/0009_alter_mailrule_action_alter_mailrule_folder.py b/src/paperless_mail/migrations/0009_alter_mailrule_action_alter_mailrule_folder.py deleted file mode 100644 index 47fdaff12..000000000 --- a/src/paperless_mail/migrations/0009_alter_mailrule_action_alter_mailrule_folder.py +++ /dev/null @@ -1,37 +0,0 @@ -# Generated by Django 4.0.3 on 2022-03-28 17:40 - -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("paperless_mail", "0008_auto_20210516_0940"), - ] - - operations = [ - migrations.AlterField( - model_name="mailrule", - name="action", - field=models.PositiveIntegerField( - choices=[ - (1, "Mark as read, don't process read mails"), - (2, "Flag the mail, don't process flagged mails"), - (3, "Move to specified folder"), - (4, "Delete"), - ], - default=3, - verbose_name="action", - ), - ), - migrations.AlterField( - model_name="mailrule", - name="folder", - field=models.CharField( - default="INBOX", - help_text="Subfolders must be separated by a delimiter, often a dot ('.') or slash ('/'), but it varies by mail server.", - max_length=256, - verbose_name="folder", - ), - ), - ] diff --git a/src/paperless_mail/migrations/0009_mailrule_assign_tags.py b/src/paperless_mail/migrations/0009_mailrule_assign_tags.py deleted file mode 100644 index fbe359814..000000000 --- a/src/paperless_mail/migrations/0009_mailrule_assign_tags.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 3.2.12 on 2022-03-11 15:00 - -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("paperless_mail", "0008_auto_20210516_0940"), - ] - - operations = [ - migrations.AddField( - model_name="mailrule", - name="assign_tags", - field=models.ManyToManyField( - blank=True, - related_name="mail_rules_multi", - to="documents.Tag", - verbose_name="assign this tag", - ), - ), - ] diff --git a/src/paperless_mail/migrations/0010_auto_20220311_1602.py b/src/paperless_mail/migrations/0010_auto_20220311_1602.py deleted file mode 100644 index 0511608ca..000000000 --- a/src/paperless_mail/migrations/0010_auto_20220311_1602.py +++ /dev/null @@ -1,39 +0,0 @@ -# Generated by Django 3.2.12 on 2022-03-11 15:02 - -from django.db import migrations - - -def migrate_tag_to_tags(apps, schema_editor): - # Manual data migration, see - # https://docs.djangoproject.com/en/3.2/topics/migrations/#data-migrations - # - # Copy the assign_tag property to the new assign_tags set if it exists. - MailRule = apps.get_model("paperless_mail", "MailRule") - for mail_rule in MailRule.objects.all(): - if mail_rule.assign_tag: - mail_rule.assign_tags.add(mail_rule.assign_tag) - mail_rule.save() - - -def migrate_tags_to_tag(apps, schema_editor): - # Manual data migration, see - # https://docs.djangoproject.com/en/3.2/topics/migrations/#data-migrations - # - # Copy the unique value in the assign_tags set to the old assign_tag property. - # Do nothing if the tag is not unique. - MailRule = apps.get_model("paperless_mail", "MailRule") - for mail_rule in MailRule.objects.all(): - tags = mail_rule.assign_tags.all() - if len(tags) == 1: - mail_rule.assign_tag = tags[0] - mail_rule.save() - - -class Migration(migrations.Migration): - dependencies = [ - ("paperless_mail", "0009_mailrule_assign_tags"), - ] - - operations = [ - migrations.RunPython(migrate_tag_to_tags, migrate_tags_to_tag), - ] diff --git a/src/paperless_mail/migrations/0011_remove_mailrule_assign_tag.py b/src/paperless_mail/migrations/0011_remove_mailrule_assign_tag.py deleted file mode 100644 index 16cec8710..000000000 --- a/src/paperless_mail/migrations/0011_remove_mailrule_assign_tag.py +++ /dev/null @@ -1,16 +0,0 @@ -# Generated by Django 3.2.12 on 2022-03-11 15:18 - -from django.db import migrations - - -class Migration(migrations.Migration): - dependencies = [ - ("paperless_mail", "0010_auto_20220311_1602"), - ] - - operations = [ - migrations.RemoveField( - model_name="mailrule", - name="assign_tag", - ), - ] diff --git a/src/paperless_mail/migrations/0011_remove_mailrule_assign_tag_squashed_0024_alter_mailrule_name_and_more.py b/src/paperless_mail/migrations/0011_remove_mailrule_assign_tag_squashed_0024_alter_mailrule_name_and_more.py deleted file mode 100644 index c48ebf33b..000000000 --- a/src/paperless_mail/migrations/0011_remove_mailrule_assign_tag_squashed_0024_alter_mailrule_name_and_more.py +++ /dev/null @@ -1,321 +0,0 @@ -# Generated by Django 4.2.13 on 2024-06-28 17:47 - -import django.db.models.deletion -import django.utils.timezone -from django.conf import settings -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - replaces = [ - ("paperless_mail", "0011_remove_mailrule_assign_tag"), - ("paperless_mail", "0012_alter_mailrule_assign_tags"), - ("paperless_mail", "0009_alter_mailrule_action_alter_mailrule_folder"), - ("paperless_mail", "0013_merge_20220412_1051"), - ("paperless_mail", "0014_alter_mailrule_action"), - ("paperless_mail", "0015_alter_mailrule_action"), - ("paperless_mail", "0016_mailrule_consumption_scope"), - ("paperless_mail", "0017_mailaccount_owner_mailrule_owner"), - ("paperless_mail", "0018_processedmail"), - ("paperless_mail", "0019_mailrule_filter_to"), - ("paperless_mail", "0020_mailaccount_is_token"), - ("paperless_mail", "0021_alter_mailaccount_password"), - ("paperless_mail", "0022_mailrule_assign_owner_from_rule_and_more"), - ("paperless_mail", "0023_remove_mailrule_filter_attachment_filename_and_more"), - ("paperless_mail", "0024_alter_mailrule_name_and_more"), - ] - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ("paperless_mail", "0010_auto_20220311_1602"), - ] - - operations = [ - migrations.RemoveField( - model_name="mailrule", - name="assign_tag", - ), - migrations.AlterField( - model_name="mailrule", - name="assign_tags", - field=models.ManyToManyField( - blank=True, - to="documents.tag", - verbose_name="assign this tag", - ), - ), - migrations.AlterField( - model_name="mailrule", - name="action", - field=models.PositiveIntegerField( - choices=[ - (1, "Mark as read, don't process read mails"), - (2, "Flag the mail, don't process flagged mails"), - (3, "Move to specified folder"), - (4, "Delete"), - ], - default=3, - verbose_name="action", - ), - ), - migrations.AlterField( - model_name="mailrule", - name="folder", - field=models.CharField( - default="INBOX", - help_text="Subfolders must be separated by a delimiter, often a dot ('.') or slash ('/'), but it varies by mail server.", - max_length=256, - verbose_name="folder", - ), - ), - migrations.AlterField( - model_name="mailrule", - name="action", - field=models.PositiveIntegerField( - choices=[ - (1, "Delete"), - (2, "Move to specified folder"), - (3, "Mark as read, don't process read mails"), - (4, "Flag the mail, don't process flagged mails"), - ], - default=3, - verbose_name="action", - ), - ), - migrations.AlterField( - model_name="mailrule", - name="action", - field=models.PositiveIntegerField( - choices=[ - (1, "Delete"), - (2, "Move to specified folder"), - (3, "Mark as read, don't process read mails"), - (4, "Flag the mail, don't process flagged mails"), - (5, "Tag the mail with specified tag, don't process tagged mails"), - ], - default=3, - verbose_name="action", - ), - ), - migrations.AddField( - model_name="mailrule", - name="consumption_scope", - field=models.PositiveIntegerField( - choices=[ - (1, "Only process attachments."), - ( - 2, - "Process full Mail (with embedded attachments in file) as .eml", - ), - ( - 3, - "Process full Mail (with embedded attachments in file) as .eml + process attachments as separate documents", - ), - ], - default=1, - verbose_name="consumption scope", - ), - ), - migrations.AddField( - model_name="mailaccount", - name="owner", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to=settings.AUTH_USER_MODEL, - verbose_name="owner", - ), - ), - migrations.AddField( - model_name="mailrule", - name="owner", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to=settings.AUTH_USER_MODEL, - verbose_name="owner", - ), - ), - migrations.CreateModel( - name="ProcessedMail", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "folder", - models.CharField( - editable=False, - max_length=256, - verbose_name="folder", - ), - ), - ( - "uid", - models.CharField( - editable=False, - max_length=256, - verbose_name="uid", - ), - ), - ( - "subject", - models.CharField( - editable=False, - max_length=256, - verbose_name="subject", - ), - ), - ( - "received", - models.DateTimeField(editable=False, verbose_name="received"), - ), - ( - "processed", - models.DateTimeField( - default=django.utils.timezone.now, - editable=False, - verbose_name="processed", - ), - ), - ( - "status", - models.CharField( - editable=False, - max_length=256, - verbose_name="status", - ), - ), - ( - "error", - models.TextField( - blank=True, - editable=False, - null=True, - verbose_name="error", - ), - ), - ( - "owner", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to=settings.AUTH_USER_MODEL, - verbose_name="owner", - ), - ), - ( - "rule", - models.ForeignKey( - editable=False, - on_delete=django.db.models.deletion.CASCADE, - to="paperless_mail.mailrule", - ), - ), - ], - options={ - "abstract": False, - }, - ), - migrations.AddField( - model_name="mailrule", - name="filter_to", - field=models.CharField( - blank=True, - max_length=256, - null=True, - verbose_name="filter to", - ), - ), - migrations.AddField( - model_name="mailaccount", - name="is_token", - field=models.BooleanField( - default=False, - verbose_name="Is token authentication", - ), - ), - migrations.AlterField( - model_name="mailaccount", - name="password", - field=models.CharField(max_length=2048, verbose_name="password"), - ), - migrations.AddField( - model_name="mailrule", - name="assign_owner_from_rule", - field=models.BooleanField( - default=True, - verbose_name="Assign the rule owner to documents", - ), - ), - migrations.AlterField( - model_name="mailrule", - name="assign_title_from", - field=models.PositiveIntegerField( - choices=[ - (1, "Use subject as title"), - (2, "Use attachment filename as title"), - (3, "Do not assign title from rule"), - ], - default=1, - verbose_name="assign title from", - ), - ), - migrations.RenameField( - model_name="mailrule", - old_name="filter_attachment_filename", - new_name="filter_attachment_filename_include", - ), - migrations.AddField( - model_name="mailrule", - name="filter_attachment_filename_exclude", - field=models.CharField( - blank=True, - help_text="Do not consume documents which entirely match this filename if specified. Wildcards such as *.pdf or *invoice* are allowed. Case insensitive.", - max_length=256, - null=True, - verbose_name="filter attachment filename exclusive", - ), - ), - migrations.AlterField( - model_name="mailrule", - name="filter_attachment_filename_include", - field=models.CharField( - blank=True, - help_text="Only consume documents which entirely match this filename if specified. Wildcards such as *.pdf or *invoice* are allowed. Case insensitive.", - max_length=256, - null=True, - verbose_name="filter attachment filename inclusive", - ), - ), - migrations.AlterField( - model_name="mailrule", - name="name", - field=models.CharField(max_length=256, verbose_name="name"), - ), - migrations.AddConstraint( - model_name="mailrule", - constraint=models.UniqueConstraint( - fields=("name", "owner"), - name="paperless_mail_mailrule_unique_name_owner", - ), - ), - migrations.AddConstraint( - model_name="mailrule", - constraint=models.UniqueConstraint( - condition=models.Q(("owner__isnull", True)), - fields=("name",), - name="paperless_mail_mailrule_name_unique", - ), - ), - ] diff --git a/src/paperless_mail/migrations/0012_alter_mailrule_assign_tags.py b/src/paperless_mail/migrations/0012_alter_mailrule_assign_tags.py deleted file mode 100644 index 83ece3bba..000000000 --- a/src/paperless_mail/migrations/0012_alter_mailrule_assign_tags.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 3.2.12 on 2022-03-11 16:21 - -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("paperless_mail", "0011_remove_mailrule_assign_tag"), - ] - - operations = [ - migrations.AlterField( - model_name="mailrule", - name="assign_tags", - field=models.ManyToManyField( - blank=True, - to="documents.Tag", - verbose_name="assign this tag", - ), - ), - ] diff --git a/src/paperless_mail/migrations/0013_merge_20220412_1051.py b/src/paperless_mail/migrations/0013_merge_20220412_1051.py deleted file mode 100644 index 0310fd083..000000000 --- a/src/paperless_mail/migrations/0013_merge_20220412_1051.py +++ /dev/null @@ -1,12 +0,0 @@ -# Generated by Django 4.0.4 on 2022-04-12 08:51 - -from django.db import migrations - - -class Migration(migrations.Migration): - dependencies = [ - ("paperless_mail", "0009_alter_mailrule_action_alter_mailrule_folder"), - ("paperless_mail", "0012_alter_mailrule_assign_tags"), - ] - - operations = [] diff --git a/src/paperless_mail/migrations/0014_alter_mailrule_action.py b/src/paperless_mail/migrations/0014_alter_mailrule_action.py deleted file mode 100644 index 6be3ddf69..000000000 --- a/src/paperless_mail/migrations/0014_alter_mailrule_action.py +++ /dev/null @@ -1,27 +0,0 @@ -# Generated by Django 4.0.4 on 2022-04-18 22:57 - -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("paperless_mail", "0013_merge_20220412_1051"), - ] - - operations = [ - migrations.AlterField( - model_name="mailrule", - name="action", - field=models.PositiveIntegerField( - choices=[ - (1, "Delete"), - (2, "Move to specified folder"), - (3, "Mark as read, don't process read mails"), - (4, "Flag the mail, don't process flagged mails"), - ], - default=3, - verbose_name="action", - ), - ), - ] diff --git a/src/paperless_mail/migrations/0015_alter_mailrule_action.py b/src/paperless_mail/migrations/0015_alter_mailrule_action.py deleted file mode 100644 index 80de9b2b1..000000000 --- a/src/paperless_mail/migrations/0015_alter_mailrule_action.py +++ /dev/null @@ -1,28 +0,0 @@ -# Generated by Django 4.0.4 on 2022-05-29 13:21 - -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("paperless_mail", "0014_alter_mailrule_action"), - ] - - operations = [ - migrations.AlterField( - model_name="mailrule", - name="action", - field=models.PositiveIntegerField( - choices=[ - (1, "Delete"), - (2, "Move to specified folder"), - (3, "Mark as read, don't process read mails"), - (4, "Flag the mail, don't process flagged mails"), - (5, "Tag the mail with specified tag, don't process tagged mails"), - ], - default=3, - verbose_name="action", - ), - ), - ] diff --git a/src/paperless_mail/migrations/0016_mailrule_consumption_scope.py b/src/paperless_mail/migrations/0016_mailrule_consumption_scope.py deleted file mode 100644 index d4a0ba590..000000000 --- a/src/paperless_mail/migrations/0016_mailrule_consumption_scope.py +++ /dev/null @@ -1,32 +0,0 @@ -# Generated by Django 4.0.4 on 2022-07-11 22:02 - -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("paperless_mail", "0015_alter_mailrule_action"), - ] - - operations = [ - migrations.AddField( - model_name="mailrule", - name="consumption_scope", - field=models.PositiveIntegerField( - choices=[ - (1, "Only process attachments."), - ( - 2, - "Process full Mail (with embedded attachments in file) as .eml", - ), - ( - 3, - "Process full Mail (with embedded attachments in file) as .eml + process attachments as separate documents", - ), - ], - default=1, - verbose_name="consumption scope", - ), - ), - ] diff --git a/src/paperless_mail/migrations/0017_mailaccount_owner_mailrule_owner.py b/src/paperless_mail/migrations/0017_mailaccount_owner_mailrule_owner.py deleted file mode 100644 index 98cfef014..000000000 --- a/src/paperless_mail/migrations/0017_mailaccount_owner_mailrule_owner.py +++ /dev/null @@ -1,38 +0,0 @@ -# Generated by Django 4.1.3 on 2022-12-06 04:48 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ("paperless_mail", "0016_mailrule_consumption_scope"), - ] - - operations = [ - migrations.AddField( - model_name="mailaccount", - name="owner", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to=settings.AUTH_USER_MODEL, - verbose_name="owner", - ), - ), - migrations.AddField( - model_name="mailrule", - name="owner", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to=settings.AUTH_USER_MODEL, - verbose_name="owner", - ), - ), - ] diff --git a/src/paperless_mail/migrations/0018_processedmail.py b/src/paperless_mail/migrations/0018_processedmail.py deleted file mode 100644 index 3307f7579..000000000 --- a/src/paperless_mail/migrations/0018_processedmail.py +++ /dev/null @@ -1,105 +0,0 @@ -# Generated by Django 4.1.5 on 2023-03-03 18:38 - -import django.db.models.deletion -import django.utils.timezone -from django.conf import settings -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ("paperless_mail", "0017_mailaccount_owner_mailrule_owner"), - ] - - operations = [ - migrations.CreateModel( - name="ProcessedMail", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "folder", - models.CharField( - editable=False, - max_length=256, - verbose_name="folder", - ), - ), - ( - "uid", - models.CharField( - editable=False, - max_length=256, - verbose_name="uid", - ), - ), - ( - "subject", - models.CharField( - editable=False, - max_length=256, - verbose_name="subject", - ), - ), - ( - "received", - models.DateTimeField(editable=False, verbose_name="received"), - ), - ( - "processed", - models.DateTimeField( - default=django.utils.timezone.now, - editable=False, - verbose_name="processed", - ), - ), - ( - "status", - models.CharField( - editable=False, - max_length=256, - verbose_name="status", - ), - ), - ( - "error", - models.TextField( - blank=True, - editable=False, - null=True, - verbose_name="error", - ), - ), - ( - "owner", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to=settings.AUTH_USER_MODEL, - verbose_name="owner", - ), - ), - ( - "rule", - models.ForeignKey( - editable=False, - on_delete=django.db.models.deletion.CASCADE, - to="paperless_mail.mailrule", - ), - ), - ], - options={ - "abstract": False, - }, - ), - ] diff --git a/src/paperless_mail/migrations/0019_mailrule_filter_to.py b/src/paperless_mail/migrations/0019_mailrule_filter_to.py deleted file mode 100644 index 8951be290..000000000 --- a/src/paperless_mail/migrations/0019_mailrule_filter_to.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 4.1.7 on 2023-03-11 21:08 - -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("paperless_mail", "0018_processedmail"), - ] - - operations = [ - migrations.AddField( - model_name="mailrule", - name="filter_to", - field=models.CharField( - blank=True, - max_length=256, - null=True, - verbose_name="filter to", - ), - ), - ] diff --git a/src/paperless_mail/migrations/0020_mailaccount_is_token.py b/src/paperless_mail/migrations/0020_mailaccount_is_token.py deleted file mode 100644 index 81ce50a19..000000000 --- a/src/paperless_mail/migrations/0020_mailaccount_is_token.py +++ /dev/null @@ -1,21 +0,0 @@ -# Generated by Django 4.1.7 on 2023-03-22 17:51 - -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("paperless_mail", "0019_mailrule_filter_to"), - ] - - operations = [ - migrations.AddField( - model_name="mailaccount", - name="is_token", - field=models.BooleanField( - default=False, - verbose_name="Is token authentication", - ), - ), - ] diff --git a/src/paperless_mail/migrations/0021_alter_mailaccount_password.py b/src/paperless_mail/migrations/0021_alter_mailaccount_password.py deleted file mode 100644 index 0c012b98b..000000000 --- a/src/paperless_mail/migrations/0021_alter_mailaccount_password.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 4.1.7 on 2023-04-20 15:03 - -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("paperless_mail", "0020_mailaccount_is_token"), - ] - - operations = [ - migrations.AlterField( - model_name="mailaccount", - name="password", - field=models.CharField(max_length=2048, verbose_name="password"), - ), - ] diff --git a/src/paperless_mail/migrations/0022_mailrule_assign_owner_from_rule_and_more.py b/src/paperless_mail/migrations/0022_mailrule_assign_owner_from_rule_and_more.py deleted file mode 100644 index f2c59a5bf..000000000 --- a/src/paperless_mail/migrations/0022_mailrule_assign_owner_from_rule_and_more.py +++ /dev/null @@ -1,34 +0,0 @@ -# Generated by Django 4.1.11 on 2023-09-18 18:50 - -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("paperless_mail", "0021_alter_mailaccount_password"), - ] - - operations = [ - migrations.AddField( - model_name="mailrule", - name="assign_owner_from_rule", - field=models.BooleanField( - default=True, - verbose_name="Assign the rule owner to documents", - ), - ), - migrations.AlterField( - model_name="mailrule", - name="assign_title_from", - field=models.PositiveIntegerField( - choices=[ - (1, "Use subject as title"), - (2, "Use attachment filename as title"), - (3, "Do not assign title from rule"), - ], - default=1, - verbose_name="assign title from", - ), - ), - ] diff --git a/src/paperless_mail/migrations/0023_remove_mailrule_filter_attachment_filename_and_more.py b/src/paperless_mail/migrations/0023_remove_mailrule_filter_attachment_filename_and_more.py deleted file mode 100644 index 1a1eac790..000000000 --- a/src/paperless_mail/migrations/0023_remove_mailrule_filter_attachment_filename_and_more.py +++ /dev/null @@ -1,40 +0,0 @@ -# Generated by Django 4.2.7 on 2023-12-04 03:06 - -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("paperless_mail", "0022_mailrule_assign_owner_from_rule_and_more"), - ] - - operations = [ - migrations.RenameField( - model_name="mailrule", - old_name="filter_attachment_filename", - new_name="filter_attachment_filename_include", - ), - migrations.AddField( - model_name="mailrule", - name="filter_attachment_filename_exclude", - field=models.CharField( - blank=True, - help_text="Do not consume documents which entirely match this filename if specified. Wildcards such as *.pdf or *invoice* are allowed. Case insensitive.", - max_length=256, - null=True, - verbose_name="filter attachment filename exclusive", - ), - ), - migrations.AlterField( - model_name="mailrule", - name="filter_attachment_filename_include", - field=models.CharField( - blank=True, - help_text="Only consume documents which entirely match this filename if specified. Wildcards such as *.pdf or *invoice* are allowed. Case insensitive.", - max_length=256, - null=True, - verbose_name="filter attachment filename inclusive", - ), - ), - ] diff --git a/src/paperless_mail/migrations/0024_alter_mailrule_name_and_more.py b/src/paperless_mail/migrations/0024_alter_mailrule_name_and_more.py deleted file mode 100644 index c2840d0e4..000000000 --- a/src/paperless_mail/migrations/0024_alter_mailrule_name_and_more.py +++ /dev/null @@ -1,33 +0,0 @@ -# Generated by Django 4.2.11 on 2024-06-05 16:51 - -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("paperless_mail", "0023_remove_mailrule_filter_attachment_filename_and_more"), - ] - - operations = [ - migrations.AlterField( - model_name="mailrule", - name="name", - field=models.CharField(max_length=256, verbose_name="name"), - ), - migrations.AddConstraint( - model_name="mailrule", - constraint=models.UniqueConstraint( - fields=("name", "owner"), - name="paperless_mail_mailrule_unique_name_owner", - ), - ), - migrations.AddConstraint( - model_name="mailrule", - constraint=models.UniqueConstraint( - condition=models.Q(("owner__isnull", True)), - fields=("name",), - name="paperless_mail_mailrule_name_unique", - ), - ), - ] diff --git a/src/paperless_mail/migrations/0025_alter_mailaccount_owner_alter_mailrule_owner_and_more.py b/src/paperless_mail/migrations/0025_alter_mailaccount_owner_alter_mailrule_owner_and_more.py deleted file mode 100644 index 308ebdf15..000000000 --- a/src/paperless_mail/migrations/0025_alter_mailaccount_owner_alter_mailrule_owner_and_more.py +++ /dev/null @@ -1,52 +0,0 @@ -# Generated by Django 4.2.13 on 2024-07-09 16:39 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ("paperless_mail", "0024_alter_mailrule_name_and_more"), - ] - - operations = [ - migrations.AlterField( - model_name="mailaccount", - name="owner", - field=models.ForeignKey( - blank=True, - default=None, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to=settings.AUTH_USER_MODEL, - verbose_name="owner", - ), - ), - migrations.AlterField( - model_name="mailrule", - name="owner", - field=models.ForeignKey( - blank=True, - default=None, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to=settings.AUTH_USER_MODEL, - verbose_name="owner", - ), - ), - migrations.AlterField( - model_name="processedmail", - name="owner", - field=models.ForeignKey( - blank=True, - default=None, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to=settings.AUTH_USER_MODEL, - verbose_name="owner", - ), - ), - ] diff --git a/src/paperless_mail/migrations/0026_mailrule_enabled.py b/src/paperless_mail/migrations/0026_mailrule_enabled.py deleted file mode 100644 index c10ee698c..000000000 --- a/src/paperless_mail/migrations/0026_mailrule_enabled.py +++ /dev/null @@ -1,21 +0,0 @@ -# Generated by Django 5.1.1 on 2024-09-30 15:17 - -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ( - "paperless_mail", - "0025_alter_mailaccount_owner_alter_mailrule_owner_and_more", - ), - ] - - operations = [ - migrations.AddField( - model_name="mailrule", - name="enabled", - field=models.BooleanField(default=True, verbose_name="enabled"), - ), - ] diff --git a/src/paperless_mail/migrations/0027_mailaccount_expiration_mailaccount_account_type_and_more.py b/src/paperless_mail/migrations/0027_mailaccount_expiration_mailaccount_account_type_and_more.py deleted file mode 100644 index 3fb1e6af2..000000000 --- a/src/paperless_mail/migrations/0027_mailaccount_expiration_mailaccount_account_type_and_more.py +++ /dev/null @@ -1,48 +0,0 @@ -# Generated by Django 5.1.1 on 2024-10-05 17:12 - -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("paperless_mail", "0026_mailrule_enabled"), - ] - - operations = [ - migrations.AlterField( - model_name="mailaccount", - name="password", - field=models.CharField(max_length=3072, verbose_name="password"), - ), - migrations.AddField( - model_name="mailaccount", - name="expiration", - field=models.DateTimeField( - blank=True, - help_text="The expiration date of the refresh token. ", - null=True, - verbose_name="expiration", - ), - ), - migrations.AddField( - model_name="mailaccount", - name="account_type", - field=models.PositiveIntegerField( - choices=[(1, "IMAP"), (2, "Gmail OAuth"), (3, "Outlook OAuth")], - default=1, - verbose_name="account type", - ), - ), - migrations.AddField( - model_name="mailaccount", - name="refresh_token", - field=models.CharField( - blank=True, - help_text="The refresh token to use for token authentication e.g. with oauth2.", - max_length=3072, - null=True, - verbose_name="refresh token", - ), - ), - ] diff --git a/src/paperless_mail/migrations/0028_alter_mailaccount_password_and_more.py b/src/paperless_mail/migrations/0028_alter_mailaccount_password_and_more.py deleted file mode 100644 index 2a0279651..000000000 --- a/src/paperless_mail/migrations/0028_alter_mailaccount_password_and_more.py +++ /dev/null @@ -1,31 +0,0 @@ -# Generated by Django 5.1.1 on 2024-10-30 04:31 - -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ( - "paperless_mail", - "0027_mailaccount_expiration_mailaccount_account_type_and_more", - ), - ] - - operations = [ - migrations.AlterField( - model_name="mailaccount", - name="password", - field=models.TextField(verbose_name="password"), - ), - migrations.AlterField( - model_name="mailaccount", - name="refresh_token", - field=models.TextField( - blank=True, - help_text="The refresh token to use for token authentication e.g. with oauth2.", - null=True, - verbose_name="refresh token", - ), - ), - ] diff --git a/src/paperless_mail/migrations/0029_mailrule_pdf_layout.py b/src/paperless_mail/migrations/0029_mailrule_pdf_layout.py deleted file mode 100644 index fe7a93b71..000000000 --- a/src/paperless_mail/migrations/0029_mailrule_pdf_layout.py +++ /dev/null @@ -1,28 +0,0 @@ -# Generated by Django 5.1.3 on 2024-11-24 12:39 - -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("paperless_mail", "0028_alter_mailaccount_password_and_more"), - ] - - operations = [ - migrations.AddField( - model_name="mailrule", - name="pdf_layout", - field=models.PositiveIntegerField( - choices=[ - (0, "System default"), - (1, "Text, then HTML"), - (2, "HTML, then text"), - (3, "HTML only"), - (4, "Text only"), - ], - default=0, - verbose_name="pdf layout", - ), - ), - ]