]> git.ipfire.org Git - thirdparty/paperless-ngx.git/commitdiff
Fix: handle created change with api version increment, use created only on frontend...
authorshamoon <4887959+shamoon@users.noreply.github.com>
Mon, 19 May 2025 16:38:01 +0000 (09:38 -0700)
committerGitHub <noreply@github.com>
Mon, 19 May 2025 16:38:01 +0000 (09:38 -0700)
13 files changed:
docs/api.md
src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html
src-ui/src/app/components/document-detail/document-detail.component.html
src-ui/src/app/components/document-detail/document-detail.component.ts
src-ui/src/app/components/document-list/document-card-large/document-card-large.component.html
src-ui/src/app/components/document-list/document-card-small/document-card-small.component.html
src-ui/src/app/components/document-list/document-list.component.html
src-ui/src/app/data/document.ts
src-ui/src/app/services/rest/document.service.ts
src-ui/src/environments/environment.prod.ts
src/documents/serialisers.py
src/documents/tests/test_api_documents.py
src/paperless/settings.py

index 44c9a4bc42f16d300f1592eace5474f52dfd8963..ac2789f8b47da871dfeb870899c82fed9040a3d6 100644 (file)
@@ -418,3 +418,9 @@ Initial API version.
 
 -   The user field of document notes now returns a simplified user object
     rather than just the user ID.
+
+#### Version 9
+
+-   The document `created` field is now a date, not a datetime. The
+    `created_date` field is considered deprecated and will be removed in a
+    future version.
index 305584a643895fa8ef2c3b7a3ddeb4ca1ce6a02e..53fa86dd36ecb8f574070dce275af32b6d7f95c7 100644 (file)
@@ -43,7 +43,7 @@
                       <a routerLink="/documents/{{doc.id}}" class="btn-link text-dark text-decoration-none py-2 py-md-3" title="Open document" i18n-title>{{doc.added | customDate}}</a>
                     }
                     @case (DisplayField.CREATED) {
-                      <a routerLink="/documents/{{doc.id}}" class="btn-link text-dark text-decoration-none py-2 py-md-3" title="Open document" i18n-title>{{doc.created_date | customDate}}</a>
+                      <a routerLink="/documents/{{doc.id}}" class="btn-link text-dark text-decoration-none py-2 py-md-3" title="Open document" i18n-title>{{doc.created | customDate}}</a>
                     }
                     @case (DisplayField.TITLE) {
                       <a routerLink="/documents/{{doc.id}}" title="Open document" i18n-title class="btn-link text-dark text-decoration-none py-2 py-md-3">{{doc.title | documentTitle}}</a>
index 19d6faabbe75f8a94f355ff6d1b0cab7e081fa76..0672463354e1654817e0d39dcafc51eef54d7497 100644 (file)
             <div>
               <pngx-input-text #inputTitle i18n-title title="Title" formControlName="title" [horizontal]="true" (keyup)="titleKeyUp($event)" [error]="error?.title"></pngx-input-text>
               <pngx-input-number i18n-title title="Archive serial number" [error]="error?.archive_serial_number" [horizontal]="true" formControlName='archive_serial_number'></pngx-input-number>
-              <pngx-input-date i18n-title title="Date created" formControlName="created_date" [suggestions]="suggestions?.dates" [showFilter]="true" [horizontal]="true" (filterDocuments)="filterDocuments($event)"
-              [error]="error?.created_date"></pngx-input-date>
+              <pngx-input-date i18n-title title="Date created" formControlName="created" [suggestions]="suggestions?.dates" [showFilter]="true" [horizontal]="true" (filterDocuments)="filterDocuments($event)"
+              [error]="error?.created"></pngx-input-date>
               <pngx-input-select [items]="correspondents" i18n-title title="Correspondent" formControlName="correspondent" [allowNull]="true" [showFilter]="true" [horizontal]="true" (filterDocuments)="filterDocuments($event, DataType.Correspondent)"
               (createNew)="createCorrespondent($event)" [hideAddButton]="createDisabled(DataType.Correspondent)" [suggestions]="suggestions?.correspondents" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Correspondent }"></pngx-input-select>
               <pngx-input-select [items]="documentTypes" i18n-title title="Document type" formControlName="document_type" [allowNull]="true" [showFilter]="true" [horizontal]="true" (filterDocuments)="filterDocuments($event, DataType.DocumentType)"
index dac5f5de0d0e0ad2b4f15978e8e5fc2b1207aa73..7c3e576552b56520cc06771fbd292e561bd119df 100644 (file)
@@ -208,7 +208,7 @@ export class DocumentDetailComponent
   documentForm: FormGroup = new FormGroup({
     title: new FormControl(''),
     content: new FormControl(''),
-    created_date: new FormControl(),
+    created: new FormControl(),
     correspondent: new FormControl(),
     document_type: new FormControl(),
     storage_path: new FormControl(),
@@ -490,7 +490,7 @@ export class DocumentDetailComponent
           this.store = new BehaviorSubject({
             title: doc.title,
             content: doc.content,
-            created_date: doc.created_date,
+            created: doc.created,
             correspondent: doc.correspondent,
             document_type: doc.document_type,
             storage_path: doc.storage_path,
index 7ba59d7a0ec79f8ea02004a2e6450b5433883caa..ab9436f0918a097ec47f2abcff332afb6c7625b4 100644 (file)
               @if (displayFields.includes(DisplayField.CREATED) || displayFields.includes(DisplayField.ADDED)) {
                 <ng-template #dateTooltip>
                   <div class="d-flex flex-column text-light">
-                    <span i18n>Created: {{ document.created_date | customDate }}</span>
+                    <span i18n>Created: {{ document.created | customDate }}</span>
                     <span i18n>Added: {{ document.added | customDate }}</span>
                     <span i18n>Modified: {{ document.modified | customDate }}</span>
                   </div>
                 </ng-template>
                 @if (displayFields.includes(DisplayField.CREATED)) {
                   <div class="list-group-item bg-light text-dark p-1 border-0 d-flex align-items-center" [ngbTooltip]="dateTooltip">
-                    <i-bs width=".9em" height=".9em" class="me-2 text-muted" name="calendar-event"></i-bs><small>{{document.created_date | customDate:'mediumDate'}}</small>
+                    <i-bs width=".9em" height=".9em" class="me-2 text-muted" name="calendar-event"></i-bs><small>{{document.created | customDate:'mediumDate'}}</small>
                   </div>
                 }
                 @if (displayFields.includes(DisplayField.ADDED)) {
index a166acd946d661b91f2b582dd2590b7bfb1b2252..662bb9babfe68710aacf43c2d8a5e90fcf087157 100644 (file)
             <div class="list-group-item bg-transparent p-0 border-0 d-flex flex-wrap-reverse justify-content-between">
               <ng-template #dateTooltip>
                 <div class="d-flex flex-column text-light">
-                  <span i18n>Created: {{ document.created_date | customDate }}</span>
+                  <span i18n>Created: {{ document.created | customDate }}</span>
                   <span i18n>Added: {{ document.added | customDate }}</span>
                   <span i18n>Modified: {{ document.modified | customDate }}</span>
                 </div>
               </ng-template>
               <div class="ps-0 p-1" placement="top" [ngbTooltip]="dateTooltip">
                 <i-bs width="1em" height="1em" class="me-2 text-muted" name="calendar-event"></i-bs>
-                <small>{{document.created_date | customDate:'mediumDate'}}</small>
+                <small>{{document.created | customDate:'mediumDate'}}</small>
               </div>
             </div>
           }
index e5080509883c50c288c72718262c36948d4adcb1..c58d1ede10bc4dee8044c3a2c08594fccfa9b05e 100644 (file)
                 }
                 @if (activeDisplayFields.includes(DisplayField.CREATED)) {
                   <td>
-                    {{d.created_date | customDate}}
+                    {{d.created | customDate}}
                   </td>
                 }
                 @if (activeDisplayFields.includes(DisplayField.ADDED)) {
index e5f00148edc5e7eca3549a13f7966d82ff82ed55..5c23a86002062752c2f55e82893898daa4c70f23 100644 (file)
@@ -130,9 +130,6 @@ export interface Document extends ObjectWithPermissions {
   // UTC
   created?: Date
 
-  // localized date
-  created_date?: Date
-
   modified?: Date
 
   added?: Date
index 9cdb862802e6321a9e4684b0a01dd99be96fe0c7..1f67a358bddd6cdfb3a0f196dda7478f276bf58e 100644 (file)
@@ -190,8 +190,6 @@ export class DocumentService extends AbstractPaperlessService<Document> {
   }
 
   patch(o: Document): Observable<Document> {
-    // we want to only set created_date
-    delete o.created
     o.remove_inbox_tags = !!this.settingsService.get(
       SETTINGS_KEYS.DOCUMENT_EDITING_REMOVE_INBOX_TAGS
     )
index 2bc42f4e96f63adb863d5134fb1c2ed0f1eed5d6..a05991d9eba2573f00ebe66894dfb10e46f5c062 100644 (file)
@@ -3,7 +3,7 @@ const base_url = new URL(document.baseURI)
 export const environment = {
   production: true,
   apiBaseUrl: document.baseURI + 'api/',
-  apiVersion: '8', // match src/paperless/settings.py
+  apiVersion: '9', // match src/paperless/settings.py
   appTitle: 'Paperless-ngx',
   version: '2.15.3',
   webSocketHost: window.location.host,
index 06277554444ca9f5845f8ff4c4d16069ac0714c7..9782932bcfbe612277b12fa8799b815dc860457b 100644 (file)
@@ -21,6 +21,7 @@ from django.utils.crypto import get_random_string
 from django.utils.text import slugify
 from django.utils.translation import gettext as _
 from drf_spectacular.utils import extend_schema_field
+from drf_spectacular.utils import extend_schema_serializer
 from drf_writable_nested.serializers import NestedUpdateMixin
 from guardian.core import ObjectPermissionChecker
 from guardian.shortcuts import get_users_with_perms
@@ -891,6 +892,9 @@ class NotesSerializer(serializers.ModelSerializer):
         return ret
 
 
+@extend_schema_serializer(
+    deprecate_fields=["created_date"],
+)
 class DocumentSerializer(
     OwnedObjectSerializer,
     NestedUpdateMixin,
@@ -943,6 +947,22 @@ class DocumentSerializer(
         doc = super().to_representation(instance)
         if self.truncate_content and "content" in self.fields:
             doc["content"] = doc.get("content")[0:550]
+
+        request = self.context.get("request")
+        api_version = int(
+            request.version if request else settings.REST_FRAMEWORK["DEFAULT_VERSION"],
+        )
+
+        if api_version < 9:
+            # provide created as a datetime for backwards compatibility
+            from django.utils import timezone
+
+            doc["created"] = timezone.make_aware(
+                datetime.combine(
+                    instance.created,
+                    datetime.min.time(),
+                ),
+            ).isoformat()
         return doc
 
     def validate(self, attrs):
@@ -968,6 +988,9 @@ class DocumentSerializer(
             instance.created = validated_data.get("created_date")
             instance.save()
         if "created_date" in validated_data:
+            logger.warning(
+                "created_date is deprecated, use created instead",
+            )
             validated_data.pop("created_date")
         if instance.custom_fields.count() > 0 and "custom_fields" in validated_data:
             incoming_custom_fields = [
index c63ffdb57c22bfb71ffc3fbec7aba88ce53af6e0..bb77f581825d05c68dc2190fd9239d7776bcab1c 100644 (file)
@@ -171,6 +171,38 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase):
         results = response.data["results"]
         self.assertEqual(len(results[0]), 0)
 
+    def test_document_legacy_created_format(self):
+        """
+        GIVEN:
+            - Existing document
+        WHEN:
+            - Document is requested with api version ≥ 9
+            - Document is requested with api version < 9
+        THEN:
+            - Document created field is returned as date
+            - Document created field is returned as datetime
+        """
+        doc = Document.objects.create(
+            title="none",
+            checksum="123",
+            mime_type="application/pdf",
+            created=date(2023, 1, 1),
+        )
+
+        response = self.client.get(
+            f"/api/documents/{doc.pk}/",
+            headers={"Accept": "application/json; version=8"},
+        )
+        self.assertEqual(response.status_code, status.HTTP_200_OK)
+        self.assertRegex(response.data["created"], r"^2023-01-01T00:00:00.*$")
+
+        response = self.client.get(
+            f"/api/documents/{doc.pk}/",
+            headers={"Accept": "application/json; version=9"},
+        )
+        self.assertEqual(response.status_code, status.HTTP_200_OK)
+        self.assertEqual(response.data["created"], "2023-01-01")
+
     def test_document_update_with_created_date(self):
         """
         GIVEN:
index 3e6e76f112992d83ba98acadc59b687fac89bc5b..1eaf9392020965def9cb49aa28ebc732034be36c 100644 (file)
@@ -342,10 +342,10 @@ REST_FRAMEWORK = {
         "rest_framework.authentication.SessionAuthentication",
     ],
     "DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.AcceptHeaderVersioning",
-    "DEFAULT_VERSION": "8",  # match src-ui/src/environments/environment.prod.ts
+    "DEFAULT_VERSION": "9",  # match src-ui/src/environments/environment.prod.ts
     # Make sure these are ordered and that the most recent version appears
     # last. See api.md#api-versioning when adding new versions.
-    "ALLOWED_VERSIONS": ["1", "2", "3", "4", "5", "6", "7", "8"],
+    "ALLOWED_VERSIONS": ["1", "2", "3", "4", "5", "6", "7", "8", "9"],
     # DRF Spectacular default schema
     "DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
 }