Feature: number of pages of document in documents list

This commit is contained in:
s0llvan 2024-09-21 18:18:19 +00:00
parent 609fa9a212
commit 865856b06d
19 changed files with 318 additions and 58 deletions

View File

@ -1046,11 +1046,11 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/document.ts</context>
<context context-type="linenumber">62</context>
<context context-type="linenumber">63</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/document.ts</context>
<context context-type="linenumber">95</context>
<context context-type="linenumber">100</context>
</context-group>
</trans-unit>
<trans-unit id="293524471897878391" datatype="html">
@ -1954,11 +1954,11 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/document.ts</context>
<context context-type="linenumber">38</context>
<context context-type="linenumber">39</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/document.ts</context>
<context context-type="linenumber">92</context>
<context context-type="linenumber">97</context>
</context-group>
</trans-unit>
<trans-unit id="5968132631442328843" datatype="html">
@ -2414,7 +2414,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
<context context-type="linenumber">120</context>
<context context-type="linenumber">128</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/custom-fields/custom-fields.component.html</context>
@ -2760,7 +2760,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/document.ts</context>
<context context-type="linenumber">46</context>
<context context-type="linenumber">47</context>
</context-group>
</trans-unit>
<trans-unit id="4369111787961525769" datatype="html">
@ -2972,7 +2972,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
<context context-type="linenumber">131</context>
<context context-type="linenumber">139</context>
</context-group>
</trans-unit>
<trans-unit id="searchResults.noResults" datatype="html">
@ -3361,11 +3361,11 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/document.ts</context>
<context context-type="linenumber">42</context>
<context context-type="linenumber">43</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/document.ts</context>
<context context-type="linenumber">93</context>
<context context-type="linenumber">98</context>
</context-group>
</trans-unit>
<trans-unit id="4873149362496451858" datatype="html">
@ -5514,7 +5514,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
<context context-type="linenumber">277</context>
<context context-type="linenumber">286</context>
</context-group>
</trans-unit>
<trans-unit id="78870852467682010" datatype="html">
@ -5529,7 +5529,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
<context context-type="linenumber">312</context>
<context context-type="linenumber">321</context>
</context-group>
</trans-unit>
<trans-unit id="157572966557284263" datatype="html">
@ -5544,7 +5544,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
<context context-type="linenumber">319</context>
<context context-type="linenumber">328</context>
</context-group>
</trans-unit>
<trans-unit id="8911158217491828773" datatype="html">
@ -5842,11 +5842,11 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/document.ts</context>
<context context-type="linenumber">34</context>
<context context-type="linenumber">35</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/document.ts</context>
<context context-type="linenumber">90</context>
<context context-type="linenumber">95</context>
</context-group>
</trans-unit>
<trans-unit id="1379170675585571971" datatype="html">
@ -5883,11 +5883,11 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/document.ts</context>
<context context-type="linenumber">50</context>
<context context-type="linenumber">51</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/document.ts</context>
<context context-type="linenumber">89</context>
<context context-type="linenumber">94</context>
</context-group>
</trans-unit>
<trans-unit id="5066119607229701477" datatype="html">
@ -5910,11 +5910,11 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/document.ts</context>
<context context-type="linenumber">54</context>
<context context-type="linenumber">55</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/document.ts</context>
<context context-type="linenumber">91</context>
<context context-type="linenumber">96</context>
</context-group>
</trans-unit>
<trans-unit id="2091353339965748767" datatype="html">
@ -5937,7 +5937,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/document.ts</context>
<context context-type="linenumber">58</context>
<context context-type="linenumber">59</context>
</context-group>
</trans-unit>
<trans-unit id="6205355627445317276" datatype="html">
@ -6730,7 +6730,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
<context context-type="linenumber">288</context>
<context context-type="linenumber">297</context>
</context-group>
</trans-unit>
<trans-unit id="106713086593101376" datatype="html">
@ -6785,19 +6785,30 @@
<context context-type="linenumber">82,83</context>
</context-group>
</trans-unit>
<trans-unit id="197162226430950645" datatype="html">
<source>{VAR_PLURAL, plural, =1 {1 page} other {<x id="INTERPOLATION"/> pages}}</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-card-large/document-card-large.component.html</context>
<context context-type="linenumber">117</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
<context context-type="linenumber">95</context>
</context-group>
</trans-unit>
<trans-unit id="5739581984228459958" datatype="html">
<source>Shared</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-card-large/document-card-large.component.html</context>
<context context-type="linenumber">121</context>
<context context-type="linenumber">127</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
<context context-type="linenumber">106</context>
<context context-type="linenumber">114</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/document.ts</context>
<context context-type="linenumber">70</context>
<context context-type="linenumber">71</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/pipes/username.pipe.ts</context>
@ -6808,7 +6819,7 @@
<source>Score:</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-card-large/document-card-large.component.html</context>
<context context-type="linenumber">126</context>
<context context-type="linenumber">132</context>
</context-group>
</trans-unit>
<trans-unit id="3661756380991326939" datatype="html">
@ -6947,11 +6958,11 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/document.ts</context>
<context context-type="linenumber">74</context>
<context context-type="linenumber">75</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/document.ts</context>
<context context-type="linenumber">88</context>
<context context-type="linenumber">93</context>
</context-group>
</trans-unit>
<trans-unit id="6954625430271090777" datatype="html">
@ -6983,11 +6994,11 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/document.ts</context>
<context context-type="linenumber">66</context>
<context context-type="linenumber">67</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/document.ts</context>
<context context-type="linenumber">96</context>
<context context-type="linenumber">101</context>
</context-group>
</trans-unit>
<trans-unit id="3557446856808034218" datatype="html">
@ -7025,25 +7036,51 @@
<context context-type="linenumber">243</context>
</context-group>
</trans-unit>
<trans-unit id="4874754501044009042" datatype="html">
<source>Sort by number of pages</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
<context context-type="linenumber">252</context>
</context-group>
</trans-unit>
<trans-unit id="3817498941817715969" datatype="html">
<source>Pages</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
<context context-type="linenumber">256</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/document.ts</context>
<context context-type="linenumber">79</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/document.ts</context>
<context context-type="linenumber">102</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/paperless-config.ts</context>
<context context-type="linenumber">90</context>
</context-group>
</trans-unit>
<trans-unit id="329406837759048287" datatype="html">
<source> Shared </source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
<context context-type="linenumber">250,252</context>
<context context-type="linenumber">259,261</context>
</context-group>
</trans-unit>
<trans-unit id="2179847500064178686" datatype="html">
<source>Edit document</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
<context context-type="linenumber">284</context>
<context context-type="linenumber">293</context>
</context-group>
</trans-unit>
<trans-unit id="2807800733729323332" datatype="html">
<source>Yes</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
<context context-type="linenumber">335</context>
<context context-type="linenumber">349</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/pipes/yes-no.pipe.ts</context>
@ -7054,7 +7091,7 @@
<source>No</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
<context context-type="linenumber">335</context>
<context context-type="linenumber">349</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/pipes/yes-no.pipe.ts</context>
@ -7988,14 +8025,14 @@
<source>Modified</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/document.ts</context>
<context context-type="linenumber">94</context>
<context context-type="linenumber">99</context>
</context-group>
</trans-unit>
<trans-unit id="4460262093225954455" datatype="html">
<source>Search score</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/document.ts</context>
<context context-type="linenumber">102</context>
<context context-type="linenumber">108</context>
</context-group>
<note priority="1" from="description">Score is a value returned by the full text search engine and specifies how well a result matches the given query</note>
</trans-unit>
@ -8111,13 +8148,6 @@
<context context-type="linenumber">83</context>
</context-group>
</trans-unit>
<trans-unit id="3817498941817715969" datatype="html">
<source>Pages</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/paperless-config.ts</context>
<context context-type="linenumber">90</context>
</context-group>
</trans-unit>
<trans-unit id="1713271461473302108" datatype="html">
<source>Mode</source>
<context-group purpose="location">

View File

@ -65,6 +65,7 @@ const savedView: SavedView = {
DisplayField.CORRESPONDENT,
DisplayField.DOCUMENT_TYPE,
DisplayField.STORAGE_PATH,
DisplayField.PAGES_COUNT,
`${DisplayField.CUSTOM_FIELD}11` as any,
`${DisplayField.CUSTOM_FIELD}15` as any,
],
@ -344,6 +345,7 @@ describe('SavedViewWidgetComponent', () => {
expect(component.getColumnTitle(DisplayField.STORAGE_PATH)).toEqual(
'Storage path'
)
expect(component.getColumnTitle(DisplayField.PAGES_COUNT)).toEqual('Pages')
})
it('should get correct column title for custom field', () => {

View File

@ -111,6 +111,12 @@
</div>
}
}
@if (displayFields.includes(DisplayField.PAGES_COUNT) && document.pages_count) {
<div class="list-group-item bg-light text-dark p-1 border-0 d-flex align-items-center">
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="files"></i-bs>
<small i18n>{document.pages_count, plural, =1 {1 page} other {{{document.pages_count}} pages}}</small>
</div>
}
@if (displayFields.includes(DisplayField.OWNER) && document.owner && document.owner !== settingsService.currentUser.id) {
<div class="list-group-item bg-light text-dark p-1 border-0 d-flex align-items-center">
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="person-fill-lock"></i-bs><small>{{document.owner | username}}</small>

View File

@ -31,6 +31,7 @@ const doc = {
correspondent: 8,
document_type: 10,
storage_path: null,
pages_count: 8,
notes: [
{
id: 11,
@ -80,6 +81,7 @@ describe('DocumentCardLargeComponent', () => {
it('should display a document', () => {
expect(fixture.nativeElement.textContent).toContain('Document 10')
expect(fixture.nativeElement.textContent).toContain('Cupcake ipsum')
expect(fixture.nativeElement.textContent).toContain('8 pages')
})
it('should show preview on mouseover after delay to preload content', fakeAsync(() => {

View File

@ -73,20 +73,28 @@
</div>
</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 | 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.added | customDate:'mediumDate'}}</small>
@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 | 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.added | customDate:'mediumDate'}}</small>
</div>
</div>
}
@if (displayFields.includes(DisplayField.PAGES_COUNT) && document.pages_count) {
<div class="list-group-item bg-transparent p-0 border-0 d-flex flex-wrap-reverse justify-content-between">
<div class="ps-0 p-1" placement="top">
<i-bs width="1em" height="1em" class="me-2 text-muted" name="files"></i-bs>
<small i18n>{document.pages_count, plural, =1 {1 page} other {{{document.pages_count}} pages}}</small>
</div>
</div>
</div>
}
@if (displayFields.includes(DisplayField.ASN) && document.archive_serial_number | isNumber) {
<div class="ps-0 p-1">

View File

@ -34,6 +34,7 @@ const doc = {
correspondent: 8,
document_type: 10,
storage_path: null,
pages_count: 12,
notes: [
{
id: 11,
@ -91,6 +92,10 @@ describe('DocumentCardSmallComponent', () => {
fixture.detectChanges()
})
it('should display a document', () => {
expect(fixture.nativeElement.textContent).toContain('12 pages')
})
it('should display a document, limit tags to 5', () => {
expect(fixture.nativeElement.textContent).toContain('Document 10')
expect(

View File

@ -246,6 +246,15 @@
(sort)="onSort($event)"
i18n>Added</th>
}
@if (activeDisplayFields.includes(DisplayField.PAGES_COUNT)) {
<th class="cursor-pointer"
pngxSortable="pages_count"
title="Sort by number of pages" i18n-title
[currentSortField]="list.sortField"
[currentSortReverse]="list.sortReverse"
(sort)="onSort($event)"
i18n>Pages</th>
}
@if (activeDisplayFields.includes(DisplayField.SHARED)) {
<th i18n>
Shared
@ -330,6 +339,11 @@
{{d.added | customDate}}
</td>
}
@if (activeDisplayFields.includes(DisplayField.PAGES_COUNT)) {
<td>
{{ d.pages_count }}
</td>
}
@if (activeDisplayFields.includes(DisplayField.SHARED)) {
<td>
@if (d.is_shared_by_requester) { <ng-container i18n>Yes</ng-container> } @else { <ng-container i18n>No</ng-container> }

View File

@ -602,7 +602,7 @@ describe('DocumentListComponent', () => {
expect(
fixture.debugElement.queryAll(By.directive(SortableDirective))
).toHaveLength(9)
).toHaveLength(10)
expect(component.notesEnabled).toBeTruthy()
settingsService.set(SETTINGS_KEYS.NOTES_ENABLED, false)
@ -610,14 +610,14 @@ describe('DocumentListComponent', () => {
expect(component.notesEnabled).toBeFalsy()
expect(
fixture.debugElement.queryAll(By.directive(SortableDirective))
).toHaveLength(8)
).toHaveLength(9)
// insufficient perms
jest.spyOn(permissionService, 'currentUserCan').mockReturnValue(false)
fixture.detectChanges()
expect(
fixture.debugElement.queryAll(By.directive(SortableDirective))
).toHaveLength(4)
).toHaveLength(5)
})
it('should support toggle on document objects', () => {

View File

@ -26,6 +26,7 @@ export enum DisplayField {
OWNER = 'owner',
SHARED = 'shared',
ASN = 'asn',
PAGES_COUNT = 'pagescount',
}
export const DEFAULT_DISPLAY_FIELDS = [
@ -73,6 +74,10 @@ export const DEFAULT_DISPLAY_FIELDS = [
id: DisplayField.ASN,
name: $localize`ASN`,
},
{
id: DisplayField.PAGES_COUNT,
name: $localize`Pages`,
},
]
export const DEFAULT_DASHBOARD_VIEW_PAGE_SIZE = 10
@ -94,6 +99,7 @@ export const DOCUMENT_SORT_FIELDS = [
{ field: 'modified', name: $localize`Modified` },
{ field: 'num_notes', name: $localize`Notes` },
{ field: 'owner', name: $localize`Owner` },
{ field: 'pages_count', name: $localize`Pages` },
]
export const DOCUMENT_SORT_FIELDS_FULLTEXT = [
@ -164,4 +170,6 @@ export interface Document extends ObjectWithPermissions {
// write-only field
remove_inbox_tags?: boolean
pages_count?: number
}

View File

@ -345,6 +345,7 @@ export class SettingsService {
DisplayField.CREATED,
DisplayField.ADDED,
DisplayField.ASN,
DisplayField.PAGES_COUNT,
DisplayField.SHARED,
].includes(field.id)
) {

View File

@ -586,6 +586,7 @@ class ConsumerPlugin(
date = None
thumbnail = None
archive_path = None
pages_count = None
try:
self._send_progress(
@ -621,6 +622,7 @@ class ConsumerPlugin(
)
date = parse_date(self.filename, text)
archive_path = document_parser.get_archive_path()
pages_count = document_parser.get_pages_count(self.working_copy, mime_type)
except ParseError as e:
document_parser.cleanup()
@ -662,7 +664,12 @@ class ConsumerPlugin(
try:
with transaction.atomic():
# store the document.
document = self._store(text=text, date=date, mime_type=mime_type)
document = self._store(
text=text,
date=date,
pages_count=pages_count,
mime_type=mime_type,
)
# If we get here, it was successful. Proceed with post-consume
# hooks. If they fail, nothing will get changed.
@ -790,6 +797,7 @@ class ConsumerPlugin(
self,
text: str,
date: Optional[datetime.datetime],
pages_count: int,
mime_type: str,
) -> Document:
# If someone gave us the original filename, use it instead of doc.
@ -835,6 +843,7 @@ class ConsumerPlugin(
created=create_date,
modified=create_date,
storage_type=storage_type,
pages_count=pages_count,
original_filename=self.filename,
)

View File

@ -80,6 +80,7 @@ def get_schema():
has_owner=BOOLEAN(),
viewer_id=KEYWORD(commas=True),
checksum=TEXT(),
pages_count=NUMERIC(sortable=True),
original_filename=TEXT(sortable=True),
is_shared=BOOLEAN(),
)
@ -181,6 +182,7 @@ def update_document(writer: AsyncWriter, doc: Document):
has_owner=doc.owner is not None,
viewer_id=viewer_ids if viewer_ids else None,
checksum=doc.checksum,
pages_count=doc.pages_count,
original_filename=doc.original_filename,
is_shared=len(viewer_ids) > 0,
)
@ -247,6 +249,7 @@ class DelayedQuery:
"archive_serial_number": "asn",
"num_notes": "num_notes",
"owner": "owner",
"pages_count": "pages_count",
}
if field.startswith("-"):

View File

@ -0,0 +1,109 @@
# Generated by Django 4.2.16 on 2024-09-21 15:44
import datetime
from pathlib import Path
import pikepdf
from django.conf import settings
from django.db import migrations
from django.db import models
from django.utils import timezone
from django.utils.termcolors import colorize as colourise
from documents.parsers import get_default_file_extension
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.mime_type = doc.mime_type
self.filename = doc.filename
self.created = doc.created
def __str__(self) -> str:
# Convert UTC database time to local time
created = datetime.date.isoformat(timezone.localdate(self.created))
res = f"{created}"
if self.correspondent:
res += f" {self.correspondent}"
if self.title:
res += f" {self.title}"
return res
@property
def file_type(self):
return get_default_file_extension(self.mime_type)
@property
def source_path(self) -> Path:
if self.filename:
fname = str(self.filename)
return (settings.ORIGINALS_DIR / Path(fname)).resolve()
def add_number_of_pages_to_pages_count(apps, schema_editor):
documentModel = apps.get_model("documents", "Document")
if not documentModel.objects.all().exists():
return
for doc in documentModel.objects.filter(mime_type="application/pdf"):
document = Document(doc)
print(
" {} {} {}".format(
colourise("*", fg="green"),
colourise("Calculating number of pages for", fg="white"),
colourise(document.filename, fg="cyan"),
),
)
pdf = pikepdf.open(document.source_path)
if pdf.pages is not None:
doc.pages_count = len(pdf.pages)
doc.save()
def remove_number_of_pages_to_pages_count(apps, schema_editor):
documentModel = apps.get_model("documents", "Document")
if not documentModel.objects.all().exists():
return
for document in documentModel.objects.filter(mime_type="application/pdf"):
document.pages_count = 0
document.save()
class Migration(migrations.Migration):
dependencies = [
("documents", "1052_document_transaction_id"),
]
operations = [
migrations.AddField(
model_name="document",
name="pages_count",
field=models.PositiveIntegerField(
blank=False,
null=True,
unique=False,
db_index=False,
),
),
migrations.RunPython(
add_number_of_pages_to_pages_count,
remove_number_of_pages_to_pages_count,
),
]

View File

@ -205,6 +205,18 @@ class Document(SoftDeleteModel, ModelWithOwner):
help_text=_("The checksum of the archived document."),
)
pages_count = models.PositiveIntegerField(
_("pages count"),
blank=False,
null=True,
unique=False,
db_index=False,
validators=[MinValueValidator(1)],
help_text=_(
"The number of pages of the document.",
),
)
created = models.DateTimeField(_("created"), default=timezone.now, db_index=True)
modified = models.DateTimeField(
@ -414,6 +426,7 @@ class SavedView(ModelWithOwner):
OWNER = ("owner", _("Owner"))
SHARED = ("shared", _("Shared"))
ASN = ("asn", _("ASN"))
PAGES_COUNT = ("pagescount", _("Pages"))
CUSTOM_FIELD = ("custom_field_%d", ("Custom Field"))
name = models.CharField(_("name"), max_length=128)

View File

@ -367,6 +367,9 @@ class DocumentParser(LoggingMixin):
def extract_metadata(self, document_path, mime_type):
return []
def get_pages_count(self, document_path, mime_type):
return None
def parse(self, document_path, mime_type, file_name=None):
raise NotImplementedError

View File

@ -759,6 +759,7 @@ class DocumentSerializer(
original_file_name = SerializerMethodField()
archived_file_name = SerializerMethodField()
created_date = serializers.DateField(required=False)
pages_count = SerializerMethodField()
custom_fields = CustomFieldInstanceSerializer(
many=True,
@ -779,6 +780,9 @@ class DocumentSerializer(
required=False,
)
def get_pages_count(self, obj):
return obj.pages_count
def get_original_file_name(self, obj):
return obj.original_filename
@ -894,6 +898,7 @@ class DocumentSerializer(
"notes",
"custom_fields",
"remove_inbox_tags",
"pages_count",
)
list_serializer_class = OwnedObjectListSerializer

View File

@ -361,6 +361,7 @@ class DocumentViewSet(
"archive_serial_number",
"num_notes",
"owner",
"pages_count",
)
def get_queryset(self):
@ -444,6 +445,24 @@ class DocumentViewSet(
logger.warning(f"No parser for {mime_type}")
return []
def get_pages_count(self, file, mime_type):
if not os.path.isfile(file):
return None
parser_class = get_parser_class_for_mime_type(mime_type)
if parser_class:
parser = parser_class(progress_callback=None, logging_group=None)
try:
return parser.get_pages_count(file)
except Exception: # pragma: no cover
logger.exception(f"Issue getting pages count for {file}")
# TODO: cover GPG errors, remove later.
return []
else: # pragma: no cover
logger.warning(f"No parser for {mime_type}")
return []
def get_filesize(self, filename):
if os.path.isfile(filename):
return os.stat(filename).st_size

View File

@ -41,6 +41,15 @@ class RasterisedDocumentParser(DocumentParser):
"""
return OcrConfig()
def get_pages_count(self, document_path, mime_type):
pages_count = None
if mime_type == "application/pdf":
import pikepdf
pdf = pikepdf.open(document_path)
pages_count = len(pdf.pages)
return pages_count
def extract_metadata(self, document_path, mime_type):
result = []
if mime_type == "application/pdf":

View File

@ -57,6 +57,20 @@ class TestParser(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
self.assertContainsStrings(text.strip(), ["This is a test document."])
def test_get_pages_count(self):
parser = RasterisedDocumentParser(uuid.uuid4())
pages_count = parser.get_pages_count(
os.path.join(self.SAMPLE_FILES, "simple-digital.pdf"),
"application/pdf",
)
self.assertEqual(pages_count, 1)
pages_count = parser.get_pages_count(
os.path.join(self.SAMPLE_FILES, "multi-page-mixed.pdf"),
"application/pdf",
)
self.assertEqual(pages_count, 6)
def test_thumbnail(self):
parser = RasterisedDocumentParser(uuid.uuid4())
thumb = parser.get_thumbnail(