mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-10-30 03:56:23 -05:00 
			
		
		
		
	Compare commits
	
		
			5 Commits
		
	
	
		
			117dfb83fe
			...
			feature-in
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | f20b224ef5 | ||
|   | 8344a6e0e3 | ||
|   | 10a8a5da19 | ||
|   | d190b50032 | ||
|   | fda29f51c3 | 
| @@ -1,4 +1,9 @@ | |||||||
| <pngx-page-header [(title)]="title"> | <pngx-page-header [(title)]="title"> | ||||||
|  |   @if (document?.in_process) { | ||||||
|  |     <span class="badge bg-danger text-dark ms-2 d-flex align-items-center"> | ||||||
|  |       <div class="spinner-border spinner-border-sm me-1" role="status"></div><span i18n>Processing...</span> | ||||||
|  |     </span> | ||||||
|  |   } | ||||||
|   @if (archiveContentRenderType === ContentRenderType.PDF && !useNativePdfViewer) { |   @if (archiveContentRenderType === ContentRenderType.PDF && !useNativePdfViewer) { | ||||||
|     @if (previewNumPages) { |     @if (previewNumPages) { | ||||||
|       <div class="input-group input-group-sm d-none d-md-flex"> |       <div class="input-group input-group-sm d-none d-md-flex"> | ||||||
| @@ -50,7 +55,7 @@ | |||||||
|       <div class="d-none d-sm-inline"> <ng-container i18n>Actions</ng-container></div> |       <div class="d-none d-sm-inline"> <ng-container i18n>Actions</ng-container></div> | ||||||
|     </button> |     </button> | ||||||
|     <div ngbDropdownMenu aria-labelledby="actionsDropdown" class="shadow"> |     <div ngbDropdownMenu aria-labelledby="actionsDropdown" class="shadow"> | ||||||
|       <button ngbDropdownItem (click)="reprocess()" [disabled]="!userCanEdit || !userIsOwner"> |       <button ngbDropdownItem (click)="reprocess()" [disabled]="!userCanEdit || !userIsOwner || document?.in_process"> | ||||||
|         <i-bs width="1em" height="1em" name="arrow-counterclockwise"></i-bs> <span i18n>Reprocess</span> |         <i-bs width="1em" height="1em" name="arrow-counterclockwise"></i-bs> <span i18n>Reprocess</span> | ||||||
|       </button> |       </button> | ||||||
|  |  | ||||||
| @@ -58,7 +63,7 @@ | |||||||
|         <i-bs width="1em" height="1em" name="diagram-3"></i-bs> <span i18n>More like this</span> |         <i-bs width="1em" height="1em" name="diagram-3"></i-bs> <span i18n>More like this</span> | ||||||
|       </button> |       </button> | ||||||
|  |  | ||||||
|       <button ngbDropdownItem (click)="editPdf()" [disabled]="!userIsOwner || !userCanEdit || originalContentRenderType !== ContentRenderType.PDF"> |       <button ngbDropdownItem (click)="editPdf()" [disabled]="!userIsOwner || !userCanEdit || originalContentRenderType !== ContentRenderType.PDF || document?.in_process"> | ||||||
|         <i-bs name="pencil"></i-bs> <ng-container i18n>PDF Editor</ng-container> |         <i-bs name="pencil"></i-bs> <ng-container i18n>PDF Editor</ng-container> | ||||||
|       </button> |       </button> | ||||||
|     </div> |     </div> | ||||||
| @@ -90,7 +95,6 @@ | |||||||
|       } |       } | ||||||
|     </div> |     </div> | ||||||
|   </div> |   </div> | ||||||
|  |  | ||||||
| </pngx-page-header> | </pngx-page-header> | ||||||
|  |  | ||||||
| <div class="row"> | <div class="row"> | ||||||
|   | |||||||
| @@ -15,8 +15,13 @@ | |||||||
|       } |       } | ||||||
|  |  | ||||||
|     </div> |     </div> | ||||||
|     <div class="col col-md-10"> |   <div class="col col-md-10"> | ||||||
|       <div class="card-body"> |       <div class="card-body"> | ||||||
|  |         @if (document?.in_process) { | ||||||
|  |           <span class="badge bg-secondary text-light mb-2"> | ||||||
|  |             <div class="spinner-border spinner-border-sm me-1" role="status"></div><span i18n>Processing...</span> | ||||||
|  |           </span> | ||||||
|  |         } | ||||||
|         <div class="d-flex justify-content-between align-items-center"> |         <div class="d-flex justify-content-between align-items-center"> | ||||||
|           <h5 class="card-title w-100"> |           <h5 class="card-title w-100"> | ||||||
|             @if (document) { |             @if (document) { | ||||||
|   | |||||||
| @@ -37,6 +37,11 @@ | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     <div class="card-body bg-light p-2"> |     <div class="card-body bg-light p-2"> | ||||||
|  |       @if (document?.in_process) { | ||||||
|  |         <span class="badge bg-secondary text-light mb-2"> | ||||||
|  |           <div class="spinner-border spinner-border-sm me-1" role="status"></div><span i18n>Processing...</span> | ||||||
|  |         </span> | ||||||
|  |       } | ||||||
|       <p class="card-text"> |       <p class="card-text"> | ||||||
|         @if (document) { |         @if (document) { | ||||||
|           @if (displayFields.includes(DisplayField.CORRESPONDENT) && document.correspondent) { |           @if (displayFields.includes(DisplayField.CORRESPONDENT) && document.correspondent) { | ||||||
|   | |||||||
| @@ -301,6 +301,11 @@ | |||||||
|                 } |                 } | ||||||
|                 @if (activeDisplayFields.includes(DisplayField.TITLE) || activeDisplayFields.includes(DisplayField.TAGS)) { |                 @if (activeDisplayFields.includes(DisplayField.TITLE) || activeDisplayFields.includes(DisplayField.TAGS)) { | ||||||
|                   <td width="30%"> |                   <td width="30%"> | ||||||
|  |                     @if (d.in_process) { | ||||||
|  |                       <span class="badge bg-secondary text-light me-1"> | ||||||
|  |                         <div class="spinner-border spinner-border-sm me-1" role="status"></div><span i18n>Processing...</span> | ||||||
|  |                       </span> | ||||||
|  |                     } | ||||||
|                     @if (activeDisplayFields.includes(DisplayField.TITLE)) { |                     @if (activeDisplayFields.includes(DisplayField.TITLE)) { | ||||||
|                       <div class="d-inline-block" (mouseleave)="popupPreview.close()"> |                       <div class="d-inline-block" (mouseleave)="popupPreview.close()"> | ||||||
|                         <a routerLink="/documents/{{d.id}}" title="Edit document" i18n-title style="overflow-wrap: anywhere;">{{d.title | documentTitle}}</a> |                         <a routerLink="/documents/{{d.id}}" title="Edit document" i18n-title style="overflow-wrap: anywhere;">{{d.title | documentTitle}}</a> | ||||||
|   | |||||||
| @@ -159,6 +159,8 @@ export interface Document extends ObjectWithPermissions { | |||||||
|  |  | ||||||
|   page_count?: number |   page_count?: number | ||||||
|  |  | ||||||
|  |   in_process?: boolean | ||||||
|  |  | ||||||
|   // Frontend only |   // Frontend only | ||||||
|   __changedFields?: string[] |   __changedFields?: string[] | ||||||
| } | } | ||||||
|   | |||||||
| @@ -283,6 +283,7 @@ def rotate(doc_ids: list[int], degrees: int) -> Literal["OK"]: | |||||||
|         f"Attempting to rotate {len(doc_ids)} documents by {degrees} degrees.", |         f"Attempting to rotate {len(doc_ids)} documents by {degrees} degrees.", | ||||||
|     ) |     ) | ||||||
|     qs = Document.objects.filter(id__in=doc_ids) |     qs = Document.objects.filter(id__in=doc_ids) | ||||||
|  |     Document.objects.filter(pk__in=doc_ids).update(in_process=True) | ||||||
|     affected_docs: list[int] = [] |     affected_docs: list[int] = [] | ||||||
|     import pikepdf |     import pikepdf | ||||||
|  |  | ||||||
| @@ -309,7 +310,9 @@ def rotate(doc_ids: list[int], degrees: int) -> Literal["OK"]: | |||||||
|                     f"Rotated document {doc.id} by {degrees} degrees", |                     f"Rotated document {doc.id} by {degrees} degrees", | ||||||
|                 ) |                 ) | ||||||
|                 affected_docs.append(doc.id) |                 affected_docs.append(doc.id) | ||||||
|  |                 Document.objects.filter(pk__in=doc_ids).update(in_process=False) | ||||||
|         except Exception as e: |         except Exception as e: | ||||||
|  |             Document.objects.filter(pk__in=doc_ids).update(in_process=False) | ||||||
|             logger.exception(f"Error rotating document {doc.id}: {e}") |             logger.exception(f"Error rotating document {doc.id}: {e}") | ||||||
|  |  | ||||||
|     if len(affected_docs) > 0: |     if len(affected_docs) > 0: | ||||||
| @@ -474,6 +477,7 @@ def delete_pages(doc_ids: list[int], pages: list[int]) -> Literal["OK"]: | |||||||
|         f"Attempting to delete pages {pages} from {len(doc_ids)} documents", |         f"Attempting to delete pages {pages} from {len(doc_ids)} documents", | ||||||
|     ) |     ) | ||||||
|     doc = Document.objects.get(id=doc_ids[0]) |     doc = Document.objects.get(id=doc_ids[0]) | ||||||
|  |     Document.objects.filter(pk=doc.id).update(in_process=True) | ||||||
|     pages = sorted(pages)  # sort pages to avoid index issues |     pages = sorted(pages)  # sort pages to avoid index issues | ||||||
|     import pikepdf |     import pikepdf | ||||||
|  |  | ||||||
| @@ -492,6 +496,7 @@ def delete_pages(doc_ids: list[int], pages: list[int]) -> Literal["OK"]: | |||||||
|             update_document_content_maybe_archive_file.delay(document_id=doc.id) |             update_document_content_maybe_archive_file.delay(document_id=doc.id) | ||||||
|             logger.info(f"Deleted pages {pages} from document {doc.id}") |             logger.info(f"Deleted pages {pages} from document {doc.id}") | ||||||
|     except Exception as e: |     except Exception as e: | ||||||
|  |         Document.objects.filter(pk=doc.id).update(in_process=False) | ||||||
|         logger.exception(f"Error deleting pages from document {doc.id}: {e}") |         logger.exception(f"Error deleting pages from document {doc.id}: {e}") | ||||||
|  |  | ||||||
|     return "OK" |     return "OK" | ||||||
| @@ -518,6 +523,7 @@ def edit_pdf( | |||||||
|         f"Editing PDF of document {doc_ids[0]} with {len(operations)} operations", |         f"Editing PDF of document {doc_ids[0]} with {len(operations)} operations", | ||||||
|     ) |     ) | ||||||
|     doc = Document.objects.get(id=doc_ids[0]) |     doc = Document.objects.get(id=doc_ids[0]) | ||||||
|  |     Document.objects.filter(pk=doc.id).update(in_process=True) | ||||||
|     import pikepdf |     import pikepdf | ||||||
|  |  | ||||||
|     pdf_docs: list[pikepdf.Pdf] = [] |     pdf_docs: list[pikepdf.Pdf] = [] | ||||||
| @@ -587,6 +593,7 @@ def edit_pdf( | |||||||
|  |  | ||||||
|     except Exception as e: |     except Exception as e: | ||||||
|         logger.exception(f"Error editing document {doc.id}: {e}") |         logger.exception(f"Error editing document {doc.id}: {e}") | ||||||
|  |         Document.objects.filter(pk=doc.id).update(in_process=False) | ||||||
|         raise ValueError( |         raise ValueError( | ||||||
|             f"An error occurred while editing the document: {e}", |             f"An error occurred while editing the document: {e}", | ||||||
|         ) from e |         ) from e | ||||||
|   | |||||||
							
								
								
									
										23
									
								
								src/documents/migrations/1069_document_in_process.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/documents/migrations/1069_document_in_process.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | |||||||
|  | # Generated by Django 5.2.5 on 2025-08-26 07:54 | ||||||
|  |  | ||||||
|  | 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="document", | ||||||
|  |             name="in_process", | ||||||
|  |             field=models.BooleanField( | ||||||
|  |                 db_index=True, | ||||||
|  |                 default=False, | ||||||
|  |                 help_text="Whether the document is currently being processed.", | ||||||
|  |                 verbose_name="in process", | ||||||
|  |             ), | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
| @@ -289,6 +289,13 @@ class Document(SoftDeleteModel, ModelWithOwner): | |||||||
|         ), |         ), | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  |     in_process = models.BooleanField( | ||||||
|  |         _("in process"), | ||||||
|  |         default=False, | ||||||
|  |         db_index=True, | ||||||
|  |         help_text=_("Whether the document is currently being processed."), | ||||||
|  |     ) | ||||||
|  |  | ||||||
|     class Meta: |     class Meta: | ||||||
|         ordering = ("-created",) |         ordering = ("-created",) | ||||||
|         verbose_name = _("document") |         verbose_name = _("document") | ||||||
|   | |||||||
| @@ -935,6 +935,8 @@ class DocumentSerializer( | |||||||
|         required=False, |         required=False, | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  |     in_process = serializers.BooleanField(read_only=True) | ||||||
|  |  | ||||||
|     def get_page_count(self, obj) -> int | None: |     def get_page_count(self, obj) -> int | None: | ||||||
|         return obj.page_count |         return obj.page_count | ||||||
|  |  | ||||||
| @@ -1103,6 +1105,7 @@ class DocumentSerializer( | |||||||
|             "remove_inbox_tags", |             "remove_inbox_tags", | ||||||
|             "page_count", |             "page_count", | ||||||
|             "mime_type", |             "mime_type", | ||||||
|  |             "in_process", | ||||||
|         ) |         ) | ||||||
|         list_serializer_class = OwnedObjectListSerializer |         list_serializer_class = OwnedObjectListSerializer | ||||||
|  |  | ||||||
|   | |||||||
| @@ -250,6 +250,7 @@ def update_document_content_maybe_archive_file(document_id): | |||||||
|     it exists. |     it exists. | ||||||
|     """ |     """ | ||||||
|     document = Document.objects.get(id=document_id) |     document = Document.objects.get(id=document_id) | ||||||
|  |     Document.objects.filter(pk=document_id).update(in_process=True) | ||||||
|  |  | ||||||
|     mime_type = document.mime_type |     mime_type = document.mime_type | ||||||
|  |  | ||||||
| @@ -349,6 +350,7 @@ def update_document_content_maybe_archive_file(document_id): | |||||||
|         ) |         ) | ||||||
|     finally: |     finally: | ||||||
|         parser.cleanup() |         parser.cleanup() | ||||||
|  |         Document.objects.filter(pk=document_id).update(in_process=False) | ||||||
|  |  | ||||||
|  |  | ||||||
| @shared_task | @shared_task | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user