ID_KWARGS = ["in", "exact"]
INT_KWARGS = ["exact", "gt", "gte", "lt", "lte", "isnull"]
DATE_KWARGS = [
+ "year",
+ "month",
+ "day",
+ "gt",
+ "gte",
+ "lt",
+ "lte",
+]
+DATETIME_KWARGS = [
"year",
"month",
"day",
mime_type = MimeTypeFilter()
+ # Backwards compatibility
+ created__date__gt = Filter(
+ field_name="created",
+ label="Created after",
+ lookup_expr="gt",
+ )
+
+ created__date__lt = Filter(
+ field_name="created",
+ label="Created before",
+ lookup_expr="lt",
+ )
+
class Meta:
model = Document
fields = {
"content": CHAR_KWARGS,
"archive_serial_number": INT_KWARGS,
"created": DATE_KWARGS,
- "added": DATE_KWARGS,
- "modified": DATE_KWARGS,
+ "added": DATETIME_KWARGS,
+ "modified": DATETIME_KWARGS,
"original_filename": CHAR_KWARGS,
"checksum": CHAR_KWARGS,
"correspondent": ["isnull"],
class Meta:
model = ShareLink
fields = {
- "created": DATE_KWARGS,
- "expiration": DATE_KWARGS,
+ "created": DATETIME_KWARGS,
+ "expiration": DATETIME_KWARGS,
}
from collections import Counter
from contextlib import contextmanager
from datetime import datetime
+from datetime import time
from datetime import timezone
from shutil import rmtree
from typing import TYPE_CHECKING
type=doc.document_type.name if doc.document_type else None,
type_id=doc.document_type.id if doc.document_type else None,
has_type=doc.document_type is not None,
- created=doc.created,
+ created=datetime.combine(doc.created, time.min),
added=doc.added,
asn=asn,
modified=doc.modified,
--- /dev/null
+# 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.db.models.functions import TruncDate
+
+
+def migrate_date(apps, schema_editor):
+ Document = apps.get_model("documents", "Document")
+ queryset = Document.objects.annotate(
+ truncated_created=TruncDate("created"),
+ ).values("id", "truncated_created")
+
+ # Batch to avoid loading all objects into memory at once,
+ # which would be problematic for large datasets.
+ batch_size = 500
+ updates = []
+ for item in queryset.iterator(chunk_size=batch_size):
+ updates.append(
+ Document(id=item["id"], created_date=item["truncated_created"]),
+ )
+ if len(updates) >= batch_size:
+ Document.objects.bulk_update(updates, ["created_date"])
+ updates.clear()
+ if updates:
+ Document.objects.bulk_update(updates, ["created_date"])
+
+
+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",
+ ),
+ ),
+ ]
),
)
- created = models.DateTimeField(_("created"), default=timezone.now, db_index=True)
+ created = models.DateField(
+ _("created"),
+ default=datetime.datetime.today,
+ db_index=True,
+ )
modified = models.DateTimeField(
_("modified"),
verbose_name_plural = _("documents")
def __str__(self) -> str:
- # Convert UTC database time to local time
- created = datetime.date.isoformat(timezone.localdate(self.created))
+ created = self.created.isoformat()
res = f"{created}"
@property
def created_date(self):
- return timezone.localdate(self.created)
+ return self.created
class SavedView(ModelWithOwner):
from __future__ import annotations
-import datetime
import logging
import math
import re
-import zoneinfo
+from datetime import datetime
from decimal import Decimal
from typing import TYPE_CHECKING
class CorrespondentSerializer(MatchingModelSerializer, OwnedObjectSerializer):
- last_correspondence = serializers.DateTimeField(read_only=True, required=False)
+ last_correspondence = serializers.DateField(read_only=True, required=False)
class Meta:
model = Correspondent
def update(self, instance: Document, validated_data):
if "created_date" in validated_data and "created" not in validated_data:
- new_datetime = datetime.datetime.combine(
- validated_data.get("created_date"),
- datetime.time(0, 0, 0, 0, zoneinfo.ZoneInfo(settings.TIME_ZONE)),
- )
- instance.created = new_datetime
+ instance.created = validated_data.get("created_date")
instance.save()
if "created_date" in validated_data:
validated_data.pop("created_date")
else:
return None
+ def validate_created(self, created):
+ # support datetime format for created for backwards compatibility
+ if isinstance(created, datetime):
+ return created.date()
+
class BulkDownloadSerializer(DocumentListSerializer):
content = serializers.ChoiceField(
timezone.localtime(document.added),
document.original_filename or "",
document.filename or "",
- timezone.localtime(document.created),
+ document.created,
)
except Exception:
logger.exception(
filename = document.original_filename or ""
current_filename = document.filename or ""
added = timezone.localtime(document.added)
- created = timezone.localtime(document.created)
+ created = document.created
else:
title = overrides.title if overrides.title else str(document.original_file)
doc_url = ""
filename = document.original_file if document.original_file else ""
current_filename = filename
added = timezone.localtime(timezone.now())
- created = timezone.localtime(overrides.created)
+ created = overrides.created
subject = (
parse_w_workflow_placeholders(
filename = document.original_filename or ""
current_filename = document.filename or ""
added = timezone.localtime(document.added)
- created = timezone.localtime(document.created)
+ created = document.created
else:
title = overrides.title if overrides.title else str(document.original_file)
doc_url = ""
filename = document.original_file if document.original_file else ""
current_filename = filename
added = timezone.localtime(timezone.now())
- created = timezone.localtime(overrides.created)
+ created = overrides.created
try:
data = {}
Given a Document, localizes the creation date and builds a context dictionary with some common, shorthand
formatted values from it
"""
- local_created = timezone.localdate(document.created)
-
return {
- "created": local_created.isoformat(),
- "created_year": local_created.strftime("%Y"),
- "created_year_short": local_created.strftime("%y"),
- "created_month": local_created.strftime("%m"),
- "created_month_name": local_created.strftime("%B"),
- "created_month_name_short": local_created.strftime("%b"),
- "created_day": local_created.strftime("%d"),
+ "created": document.created.isoformat(),
+ "created_year": document.created.strftime("%Y"),
+ "created_year_short": document.created.strftime("%y"),
+ "created_month": document.created.strftime("%m"),
+ "created_month_name": document.created.strftime("%B"),
+ "created_month_name_short": document.created.strftime("%b"),
+ "created_day": document.created.strftime("%d"),
}
+from datetime import date
from datetime import datetime
from pathlib import Path
local_added: datetime,
original_filename: str,
filename: str,
- created: datetime | None = None,
+ created: date | None = None,
doc_title: str | None = None,
doc_url: str | None = None,
) -> str:
results = response.data["results"]
self.assertEqual(len(results[0]), 0)
+ def test_document_update_with_created_date(self):
+ """
+ GIVEN:
+ - Existing document
+ WHEN:
+ - Document is updated with created_date and not created
+ THEN:
+ - Document created field is updated
+ """
+ doc = Document.objects.create(
+ title="none",
+ checksum="123",
+ mime_type="application/pdf",
+ created=date(2023, 1, 1),
+ )
+
+ created_date = date(2023, 2, 1)
+ self.client.patch(
+ f"/api/documents/{doc.pk}/",
+ {"created_date": created_date},
+ format="json",
+ )
+
+ doc.refresh_from_db()
+ self.assertEqual(doc.created_date, created_date)
+
def test_document_actions(self):
_, filename = tempfile.mkstemp(dir=self.dirs.originals_dir)
_, overrides = self.get_last_consume_delay_call_args()
- self.assertEqual(overrides.created, created)
+ self.assertEqual(overrides.created, created.date())
def test_upload_with_asn(self):
self.consume_file_mock.return_value = celery.result.AsyncResult(
from django.contrib.auth.models import Permission
from django.contrib.auth.models import User
-from django.utils import timezone
from rest_framework import status
from rest_framework.test import APITestCase
Document.objects.create(
mime_type="application/pdf",
correspondent=self.c1,
- created=timezone.make_aware(datetime.datetime(2022, 1, 1)),
+ created=datetime.date(2022, 1, 1),
checksum="123",
)
Document.objects.create(
mime_type="application/pdf",
correspondent=self.c1,
- created=timezone.make_aware(datetime.datetime(2022, 1, 2)),
+ created=datetime.date(2022, 1, 2),
checksum="456",
)
self.client.force_authenticate(user1)
response = self.client.get(
- "/api/documents/",
+ "/api/documents/?ordering=-id",
format="json",
)
d3.tags.add(t2)
d4 = Document.objects.create(
checksum="4",
- created=timezone.make_aware(datetime.datetime(2020, 7, 13)),
+ created=datetime.date(2020, 7, 13),
content="test",
original_filename="doc4.pdf",
)
+from datetime import date
+
from django.contrib.auth.models import Permission
from django.contrib.auth.models import User
from django.core.cache import cache
checksum="checksum",
mime_type="application/pdf",
owner=self.user,
+ created=date(2023, 1, 1),
)
document_u1.delete()
document_not_owned = Document.objects.create(
content="content2",
checksum="checksum2",
mime_type="application/pdf",
+ created=date(2023, 1, 2),
)
document_not_owned.delete()
user2 = User.objects.create_user(username="user2")
import shutil
+from datetime import date
from pathlib import Path
from unittest import mock
self.dt2 = DocumentType.objects.create(name="dt2")
self.t1 = Tag.objects.create(name="t1")
self.t2 = Tag.objects.create(name="t2")
- self.doc1 = Document.objects.create(checksum="A", title="A")
+ self.doc1 = Document.objects.create(
+ checksum="A",
+ title="A",
+ created=date(2023, 1, 1),
+ )
self.doc2 = Document.objects.create(
checksum="B",
title="B",
correspondent=self.c1,
document_type=self.dt1,
+ created=date(2023, 1, 2),
)
self.doc3 = Document.objects.create(
checksum="C",
title="C",
correspondent=self.c2,
document_type=self.dt2,
+ created=date(2023, 1, 3),
)
self.doc4 = Document.objects.create(checksum="D", title="D")
self.doc5 = Document.objects.create(checksum="E", title="E")
filename=sample2,
mime_type="application/pdf",
page_count=8,
+ created=date(2023, 1, 2),
)
self.doc2.archive_filename = sample2_archive
self.doc2.save()
title="D",
filename=img_doc,
mime_type="image/jpeg",
+ created=date(2023, 1, 3),
)
self.img_doc.archive_filename = img_doc_archive
self.img_doc.save()
import shutil
import stat
import tempfile
-import zoneinfo
from pathlib import Path
from unittest import mock
from unittest.mock import MagicMock
-from dateutil import tz
from django.conf import settings
from django.contrib.auth.models import Group
from django.contrib.auth.models import User
self._assert_first_last_send_progress()
- # Convert UTC time from DB to local time
- document_date_local = timezone.localtime(document.created)
-
- self.assertEqual(
- document_date_local.tzinfo,
- zoneinfo.ZoneInfo("America/Chicago"),
- )
- self.assertEqual(document_date_local.tzinfo, rough_create_date_local.tzinfo)
- self.assertEqual(document_date_local.year, rough_create_date_local.year)
- self.assertEqual(document_date_local.month, rough_create_date_local.month)
- self.assertEqual(document_date_local.day, rough_create_date_local.day)
- self.assertEqual(document_date_local.hour, rough_create_date_local.hour)
- self.assertEqual(document_date_local.minute, rough_create_date_local.minute)
- # Skipping seconds and more precise
+ self.assertEqual(document.created.year, rough_create_date_local.year)
+ self.assertEqual(document.created.month, rough_create_date_local.month)
+ self.assertEqual(document.created.day, rough_create_date_local.day)
@override_settings(FILENAME_FORMAT=None)
def testDeleteMacFiles(self):
self.assertEqual(
document.created,
- datetime.datetime(1996, 2, 20, tzinfo=tz.gettz(settings.TIME_ZONE)),
+ datetime.date(1996, 2, 20),
)
@override_settings(FILENAME_DATE_ORDER="YMD")
self.assertEqual(
document.created,
- datetime.datetime(2022, 2, 1, tzinfo=tz.gettz(settings.TIME_ZONE)),
+ datetime.date(2022, 2, 1),
)
def test_consume_date_filename_date_use_content(self):
self.assertEqual(
document.created,
- datetime.datetime(1996, 2, 20, tzinfo=tz.gettz(settings.TIME_ZONE)),
+ datetime.date(1996, 2, 20),
)
@override_settings(
self.assertEqual(
document.created,
- datetime.datetime(1997, 2, 20, tzinfo=tz.gettz(settings.TIME_ZONE)),
+ datetime.date(1997, 2, 20),
)
import shutil
import tempfile
-import zoneinfo
+from datetime import date
from pathlib import Path
from unittest import mock
from django.test import TestCase
from django.test import override_settings
-from django.utils import timezone
from documents.models import Correspondent
from documents.models import Document
doc = Document(
mime_type="application/pdf",
title="test",
- created=timezone.datetime(2020, 12, 25, tzinfo=zoneinfo.ZoneInfo("UTC")),
+ created=date(2020, 12, 25),
)
self.assertEqual(doc.get_public_filename(), "2020-12-25 test.pdf")
- @override_settings(
- TIME_ZONE="Europe/Berlin",
- )
- def test_file_name_with_timezone(self):
- # See https://docs.djangoproject.com/en/4.0/ref/utils/#django.utils.timezone.now
- # The default for created is an aware datetime in UTC
- # This does that, just manually, with a fixed date
- local_create_date = timezone.datetime(
- 2020,
- 12,
- 25,
- tzinfo=zoneinfo.ZoneInfo("Europe/Berlin"),
- )
-
- utc_create_date = local_create_date.astimezone(zoneinfo.ZoneInfo("UTC"))
-
- doc = Document(
- mime_type="application/pdf",
- title="test",
- created=utc_create_date,
- )
-
- # Ensure the create date would cause an off by 1 if not properly created above
- self.assertEqual(utc_create_date.date().day, 24)
- self.assertEqual(doc.get_public_filename(), "2020-12-25 test.pdf")
-
- local_create_date = timezone.datetime(
- 2020,
- 1,
- 1,
- tzinfo=zoneinfo.ZoneInfo("Europe/Berlin"),
- )
-
- utc_create_date = local_create_date.astimezone(zoneinfo.ZoneInfo("UTC"))
-
- doc = Document(
- mime_type="application/pdf",
- title="test",
- created=utc_create_date,
- )
-
- # Ensure the create date would cause an off by 1 in the year if not properly created above
- self.assertEqual(utc_create_date.date().year, 2019)
- self.assertEqual(doc.get_public_filename(), "2020-01-01 test.pdf")
-
def test_file_name_jpg(self):
doc = Document(
mime_type="image/jpeg",
title="test",
- created=timezone.datetime(2020, 12, 25, tzinfo=zoneinfo.ZoneInfo("UTC")),
+ created=date(2020, 12, 25),
)
self.assertEqual(doc.get_public_filename(), "2020-12-25 test.jpg")
doc = Document(
mime_type="application/zip",
title="test",
- created=timezone.datetime(2020, 12, 25, tzinfo=zoneinfo.ZoneInfo("UTC")),
+ created=date(2020, 12, 25),
)
self.assertEqual(doc.get_public_filename(), "2020-12-25 test.zip")
doc = Document(
mime_type="image/jpegasd",
title="test",
- created=timezone.datetime(2020, 12, 25, tzinfo=zoneinfo.ZoneInfo("UTC")),
+ created=date(2020, 12, 25),
)
self.assertEqual(doc.get_public_filename(), "2020-12-25 test")
self.assertEqual(generate_filename(doc1), "2020-03-06.pdf")
- doc1.created = timezone.make_aware(datetime.datetime(2020, 11, 16, 1, 1, 1))
+ doc1.created = datetime.date(2020, 11, 16)
self.assertEqual(generate_filename(doc1), "2020-11-16.pdf")
def test_date(self):
doc = Document.objects.create(
title="does not matter",
- created=timezone.make_aware(datetime.datetime(2020, 5, 21, 7, 36, 51, 153)),
+ created=datetime.date(2020, 5, 21),
mime_type="application/pdf",
pk=2,
checksum="2",
"""
doc = Document.objects.create(
title="does not matter",
- created=timezone.make_aware(datetime.datetime(2020, 6, 25, 7, 36, 51, 153)),
+ created=datetime.date(2020, 6, 25),
mime_type="application/pdf",
pk=2,
checksum="2",
"""
doc = Document.objects.create(
title="does not matter",
- created=timezone.make_aware(datetime.datetime(2020, 6, 25, 7, 36, 51, 153)),
+ created=datetime.date(2020, 6, 25),
mime_type="application/pdf",
pk=2,
checksum="2",
)
doc = Document.objects.create(
title="does not matter",
- created=timezone.make_aware(datetime.datetime(2020, 6, 25, 7, 36, 51, 153)),
+ created=datetime.date(2020, 6, 25),
mime_type="application/pdf",
pk=2,
checksum="2",
"""
doc_a = Document.objects.create(
title="does not matter",
- created=timezone.make_aware(datetime.datetime(2020, 6, 25, 7, 36, 51, 153)),
+ created=datetime.date(2020, 6, 25),
mime_type="application/pdf",
pk=2,
checksum="2",
)
doc_b = Document.objects.create(
title="does not matter",
- created=timezone.make_aware(datetime.datetime(2020, 7, 25, 7, 36, 51, 153)),
+ created=datetime.date(2020, 7, 25),
mime_type="application/pdf",
pk=5,
checksum="abcde",
"""
doc_a = Document.objects.create(
title="does not matter",
- created=timezone.make_aware(datetime.datetime(2020, 6, 25, 7, 36, 51, 153)),
+ created=datetime.date(2020, 6, 25),
mime_type="application/pdf",
pk=2,
checksum="2",
)
doc_b = Document.objects.create(
title="does not matter",
- created=timezone.make_aware(datetime.datetime(2020, 7, 25, 7, 36, 51, 153)),
+ created=datetime.date(2020, 7, 25),
mime_type="application/pdf",
pk=5,
checksum="abcde",
def test_short_names_created(self):
doc = Document.objects.create(
title="The Title",
- created=timezone.make_aware(
- datetime.datetime(1989, 12, 21, 7, 36, 51, 153),
- ),
+ created=datetime.date(1989, 12, 2),
mime_type="application/pdf",
pk=2,
checksum="2",
doc_a = Document.objects.create(
title="Does Matter",
- created=timezone.make_aware(datetime.datetime(2020, 6, 25, 7, 36, 51, 153)),
+ created=datetime.date(2020, 6, 25),
added=timezone.make_aware(datetime.datetime(2024, 10, 1, 7, 36, 51, 153)),
mime_type="application/pdf",
pk=2,
"""
doc_a = Document.objects.create(
title="Does Matter",
- created=timezone.make_aware(datetime.datetime(2020, 6, 25, 7, 36, 51, 153)),
+ created=datetime.date(2020, 6, 25),
added=timezone.make_aware(datetime.datetime(2024, 10, 1, 7, 36, 51, 153)),
mime_type="application/pdf",
pk=2,
"""
doc_a = Document.objects.create(
title="Does Matter",
- created=timezone.make_aware(datetime.datetime(2020, 6, 25, 7, 36, 51, 153)),
+ created=datetime.date(2020, 6, 25),
added=timezone.make_aware(datetime.datetime(2024, 10, 1, 7, 36, 51, 153)),
mime_type="application/pdf",
pk=2,
"""
doc_a = Document.objects.create(
title="Some Title",
- created=timezone.make_aware(datetime.datetime(2020, 6, 25, 7, 36, 51, 153)),
+ created=datetime.date(2020, 6, 25),
added=timezone.make_aware(datetime.datetime(2024, 10, 1, 7, 36, 51, 153)),
mime_type="application/pdf",
pk=2,
"""
doc_a = Document.objects.create(
title="Some Title",
- created=timezone.make_aware(datetime.datetime(2020, 6, 25, 7, 36, 51, 153)),
+ created=datetime.date(2020, 6, 25),
added=timezone.make_aware(datetime.datetime(2024, 10, 1, 7, 36, 51, 153)),
mime_type="application/pdf",
pk=2,
"""
doc = Document.objects.create(
title="Some Title! With @ Special # Characters",
- created=timezone.make_aware(datetime.datetime(2020, 6, 25, 7, 36, 51, 153)),
+ created=datetime.date(2020, 6, 25),
added=timezone.make_aware(datetime.datetime(2024, 10, 1, 7, 36, 51, 153)),
mime_type="application/pdf",
pk=2,
--- /dev/null
+from datetime import datetime
+from datetime import timedelta
+
+from documents.tests.utils import DirectoriesMixin
+from documents.tests.utils import TestMigrations
+
+
+class TestMigrateDocumentCreated(DirectoriesMixin, TestMigrations):
+ migrate_from = "1066_alter_workflowtrigger_schedule_offset_days"
+ migrate_to = "1067_alter_document_created"
+
+ def setUpBeforeMigration(self, apps):
+ # create 600 documents
+ for i in range(600):
+ Document = apps.get_model("documents", "Document")
+ Document.objects.create(
+ title=f"test{i}",
+ mime_type="application/pdf",
+ filename=f"file{i}.pdf",
+ created=datetime(
+ 2023,
+ 10,
+ 1,
+ 12,
+ 0,
+ 0,
+ )
+ + timedelta(days=i),
+ checksum=i,
+ )
+
+ def testDocumentCreatedMigrated(self):
+ Document = self.apps.get_model("documents", "Document")
+
+ doc = Document.objects.get(id=1)
+ self.assertEqual(doc.created, datetime(2023, 10, 1, 12, 0, 0).date())