- 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.
<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>
<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)"
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(),
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,
@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)) {
<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>
}
}
@if (activeDisplayFields.includes(DisplayField.CREATED)) {
<td>
- {{d.created_date | customDate}}
+ {{d.created | customDate}}
</td>
}
@if (activeDisplayFields.includes(DisplayField.ADDED)) {
// UTC
created?: Date
- // localized date
- created_date?: Date
-
modified?: Date
added?: Date
}
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
)
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,
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
return ret
+@extend_schema_serializer(
+ deprecate_fields=["created_date"],
+)
class DocumentSerializer(
OwnedObjectSerializer,
NestedUpdateMixin,
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):
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 = [
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:
"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",
}