From c9e33a340100e3f08abe21fa173a5fbee112fbee Mon Sep 17 00:00:00 2001 From: Martin Richtarsky Date: Mon, 30 Sep 2024 16:56:24 +0200 Subject: [PATCH 1/5] Documentation: update development docker build steps (#7806) --- docs/development.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/development.md b/docs/development.md index bc9ef4c2b..fc6976e23 100644 --- a/docs/development.md +++ b/docs/development.md @@ -360,10 +360,10 @@ If you want to build the documentation locally, this is how you do it: The docker image is primarily built by the GitHub actions workflow, but it can be faster when developing to build and tag an image locally. -Building the image works as with any image: +Make sure you have the `docker-buildx` package installed. Building the image works as with any image: ``` -docker build --file Dockerfile --tag paperless:local --progress simple . +docker build --file Dockerfile --tag paperless:local . ``` ## Extending Paperless-ngx From b9c1ba8a1d11fa86d48d9223b3b568bab08296ac Mon Sep 17 00:00:00 2001 From: Trenton H <797416+stumpylog@users.noreply.github.com> Date: Mon, 30 Sep 2024 18:58:07 -0700 Subject: [PATCH 2/5] Documentation: Mention the Redis licensing and its forks (#7817) * Mention the Redis licensing and its forks * Note date --------- Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com> --- docs/faq.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/faq.md b/docs/faq.md index 5d59e29ad..64cf082a1 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -132,3 +132,11 @@ Multiple options for ASGI servers exist: - `daphne` as a standalone server, which is the reference implementation for ASGI. - `uvicorn` as a standalone server + +## _What about the Redis licensing change and using one of the open source forks_? + +Currently (October 2024), forks of Redis such as Valkey or Redirect are not officially supported by our upstream +libraries, so using one of these to replace Redis is not officially supported. + +However, they do claim to be compatible with the Redis protocol and will likely work, but we will +not be updating from using Redis as the broker officially just yet. From 991c9b0ca4ad36a00ae832fb8b8d657b34df761a Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Mon, 30 Sep 2024 19:42:19 -0700 Subject: [PATCH 3/5] Enhancement: disable-able mail rules, add toggle to overview (#7810) --- src-ui/messages.xlf | 185 +++++++++++------- .../global-search.component.scss | 4 - .../mail-rule-edit-dialog.component.html | 11 +- .../mail-rule-edit-dialog.component.spec.ts | 2 + .../mail-rule-edit-dialog.component.ts | 1 + .../document-list.component.scss | 4 - .../manage/mail/mail.component.html | 11 +- .../manage/mail/mail.component.spec.ts | 31 ++- .../components/manage/mail/mail.component.ts | 15 ++ src-ui/src/app/data/mail-rule.ts | 2 + .../services/rest/mail-rule.service.spec.ts | 3 + src-ui/src/styles.scss | 4 + .../tests/test_migration_workflows.py | 2 +- src/paperless_mail/admin.py | 2 +- src/paperless_mail/mail.py | 3 + .../migrations/0026_mailrule_enabled.py | 21 ++ src/paperless_mail/models.py | 2 + src/paperless_mail/serialisers.py | 1 + src/paperless_mail/tests/test_mail.py | 35 ++++ 19 files changed, 248 insertions(+), 91 deletions(-) create mode 100644 src/paperless_mail/migrations/0026_mailrule_enabled.py diff --git a/src-ui/messages.xlf b/src-ui/messages.xlf index c64cef921..e0d30bdc2 100644 --- a/src-ui/messages.xlf +++ b/src-ui/messages.xlf @@ -560,7 +560,7 @@ src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html - 72 + 75 src/app/components/common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component.html @@ -726,7 +726,7 @@ src/app/components/manage/mail/mail.component.html - 137 + 146 src/app/components/manage/management-list/management-list.component.html @@ -1100,11 +1100,11 @@ src/app/components/manage/mail/mail.component.html - 99 + 108 src/app/components/manage/mail/mail.component.html - 111 + 120 src/app/components/manage/management-list/management-list.component.html @@ -1406,7 +1406,7 @@ src/app/components/manage/mail/mail.component.html - 81 + 82 src/app/components/manage/management-list/management-list.component.html @@ -1505,11 +1505,11 @@ src/app/components/manage/mail/mail.component.html - 100 + 109 src/app/components/manage/mail/mail.component.html - 114 + 123 src/app/components/manage/management-list/management-list.component.html @@ -1668,7 +1668,7 @@ src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html - 71 + 74 src/app/components/common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component.html @@ -2227,7 +2227,7 @@ src/app/components/manage/mail/mail.component.ts - 179 + 194 src/app/components/manage/management-list/management-list.component.ts @@ -2430,11 +2430,11 @@ src/app/components/manage/mail/mail.component.html - 98 + 107 src/app/components/manage/mail/mail.component.html - 108 + 117 src/app/components/manage/management-list/management-list.component.html @@ -2578,7 +2578,7 @@ src/app/components/manage/mail/mail.component.ts - 181 + 196 src/app/components/manage/management-list/management-list.component.ts @@ -3666,166 +3666,185 @@ 88 - - Rule order - - src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html - 16 - - Account src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html - 19 + 16 src/app/components/manage/mail/mail.component.html 80 + + Order + + src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html + 19 + + + + Enabled + + src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html + 22 + + + src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html + 19 + + + src/app/components/manage/mail/mail.component.html + 96 + + + src/app/components/manage/workflows/workflows.component.html + 30 + + Paperless will only process mails that match all of the criteria specified below. src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html - 24 + 27 Folder src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html - 26 + 29 Subfolders must be separated by a delimiter, often a dot ('.') or slash ('/'), but it varies by mail server. src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html - 26 + 29 Maximum age (days) src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html - 27 + 30 Filter from src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html - 30 + 33 Filter to src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html - 31 + 34 Filter subject src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html - 32 + 35 Filter body src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html - 33 + 36 Consumption scope src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html - 39 + 42 See docs for .eml processing requirements src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html - 39 + 42 Attachment type src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html - 40 + 43 Include only files matching src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html - 43 + 46 Optional. Wildcards e.g. *.pdf or *invoice* allowed. Can be comma-separated list. Case insensitive. src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html - 43 + 46 src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html - 44 + 47 Exclude files matching src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html - 44 + 47 Action src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html - 50 + 53 Only performed if the mail is processed. src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html - 50 + 53 Action parameter src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html - 52 + 55 Assign title from src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html - 54 + 57 Assign owner from rule src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html - 55 + 58 Assign document type src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html - 59 + 62 src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html @@ -3836,14 +3855,14 @@ Assign correspondent from src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html - 60 + 63 Assign correspondent src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html - 62 + 65 src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html @@ -3854,7 +3873,7 @@ Error src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html - 69 + 72 src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html @@ -4166,17 +4185,6 @@ 18 - - Enabled - - src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html - 19 - - - src/app/components/manage/workflows/workflows.component.html - 30 - - Triggers @@ -5043,11 +5051,11 @@ src/app/components/manage/mail/mail.component.html - 101 + 110 src/app/components/manage/mail/mail.component.html - 119 + 128 src/app/components/manage/workflows/workflows.component.html @@ -5358,6 +5366,10 @@ src/app/components/common/toasts/toasts.component.html 26 + + src/app/components/manage/mail/mail.component.html + 81 + src/app/components/manage/workflows/workflows.component.html 19 @@ -7513,11 +7525,22 @@ 79 + + Disabled + + src/app/components/manage/mail/mail.component.html + 96 + + + src/app/components/manage/workflows/workflows.component.html + 30 + + No mail rules defined. src/app/components/manage/mail/mail.component.html - 128 + 137 @@ -7590,46 +7613,67 @@ 162 + + Rule "" enabled. + + src/app/components/manage/mail/mail.component.ts + 178 + + + + Rule "" disabled. + + src/app/components/manage/mail/mail.component.ts + 179 + + + + Error toggling rule. + + src/app/components/manage/mail/mail.component.ts + 183 + + Confirm delete mail rule src/app/components/manage/mail/mail.component.ts - 177 + 192 This operation will permanently delete this mail rule. src/app/components/manage/mail/mail.component.ts - 178 + 193 Deleted mail rule src/app/components/manage/mail/mail.component.ts - 187 + 202 Error deleting mail rule. src/app/components/manage/mail/mail.component.ts - 196 + 211 Permissions updated src/app/components/manage/mail/mail.component.ts - 218 + 233 Error updating permissions src/app/components/manage/mail/mail.component.ts - 223 + 238 src/app/components/manage/management-list/management-list.component.ts @@ -7879,13 +7923,6 @@ 9 - - Disabled - - src/app/components/manage/workflows/workflows.component.html - 30 - - No workflows defined. @@ -8779,21 +8816,21 @@ Successfully completed one-time migratration of settings to the database! src/app/services/settings.service.ts - 573 + 574 Unable to migrate settings to the database, please try saving manually. src/app/services/settings.service.ts - 574 + 575 You can restart the tour from the settings page. src/app/services/settings.service.ts - 644 + 645 diff --git a/src-ui/src/app/components/app-frame/global-search/global-search.component.scss b/src-ui/src/app/components/app-frame/global-search/global-search.component.scss index 1b4f6cd19..bbb7840c5 100644 --- a/src-ui/src/app/components/app-frame/global-search/global-search.component.scss +++ b/src-ui/src/app/components/app-frame/global-search/global-search.component.scss @@ -65,10 +65,6 @@ form { --pngx-focus-alpha: 0; } -.cursor-pointer { - cursor: pointer; -} - .mh-75 { max-height: 75vh; } diff --git a/src-ui/src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html b/src-ui/src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html index a7c0617b0..a9ad3040b 100644 --- a/src-ui/src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html +++ b/src-ui/src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html @@ -12,12 +12,15 @@
-
- -
-
+
+
+ +
+
+ +

diff --git a/src-ui/src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.spec.ts b/src-ui/src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.spec.ts index 53546a55d..19655ae4d 100644 --- a/src-ui/src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.spec.ts +++ b/src-ui/src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.spec.ts @@ -24,6 +24,7 @@ import { TextComponent } from '../../input/text/text.component' import { EditDialogMode } from '../edit-dialog.component' import { MailRuleEditDialogComponent } from './mail-rule-edit-dialog.component' import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http' +import { SwitchComponent } from '../../input/switch/switch.component' describe('MailRuleEditDialogComponent', () => { let component: MailRuleEditDialogComponent @@ -43,6 +44,7 @@ describe('MailRuleEditDialogComponent', () => { TagsComponent, SafeHtmlPipe, CheckComponent, + SwitchComponent, ], imports: [FormsModule, ReactiveFormsModule, NgSelectModule, NgbModule], providers: [ diff --git a/src-ui/src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.ts b/src-ui/src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.ts index 51793e78a..633c49967 100644 --- a/src-ui/src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.ts +++ b/src-ui/src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.ts @@ -153,6 +153,7 @@ export class MailRuleEditDialogComponent extends EditDialogComponent { return new FormGroup({ name: new FormControl(null), account: new FormControl(null), + enabled: new FormControl(true), folder: new FormControl('INBOX'), filter_from: new FormControl(null), filter_to: new FormControl(null), diff --git a/src-ui/src/app/components/document-list/document-list.component.scss b/src-ui/src/app/components/document-list/document-list.component.scss index fb7db4b01..0e10b83da 100644 --- a/src-ui/src/app/components/document-list/document-list.component.scss +++ b/src-ui/src/app/components/document-list/document-list.component.scss @@ -6,10 +6,6 @@ tr { user-select: none; } -.cursor-pointer { - cursor: pointer; -} - .table-row-selected { background-color: var(--pngx-primary-faded); } diff --git a/src-ui/src/app/components/manage/mail/mail.component.html b/src-ui/src/app/components/manage/mail/mail.component.html index add5614c4..296d80055 100644 --- a/src-ui/src/app/components/manage/mail/mail.component.html +++ b/src-ui/src/app/components/manage/mail/mail.component.html @@ -78,6 +78,7 @@
Name
Sort Order
Account
+
Status
Actions
@@ -86,8 +87,16 @@
  • -
    {{rule.order}}
    +
    {{rule.order}}
    {{(mailAccountService.getCached(rule.account) | async)?.name}}
    +
    +
    + + +
    +
    diff --git a/src-ui/src/app/components/manage/mail/mail.component.spec.ts b/src-ui/src/app/components/manage/mail/mail.component.spec.ts index 4a134b880..14cd10944 100644 --- a/src-ui/src/app/components/manage/mail/mail.component.spec.ts +++ b/src-ui/src/app/components/manage/mail/mail.component.spec.ts @@ -43,14 +43,15 @@ import { EditDialogMode } from '../../common/edit-dialog/edit-dialog.component' import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons' import { SwitchComponent } from '../../common/input/switch/switch.component' import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http' +import { By } from '@angular/platform-browser' const mailAccounts = [ { id: 1, name: 'account1' }, { id: 2, name: 'account2' }, ] const mailRules = [ - { id: 1, name: 'rule1', owner: 1, account: 1 }, - { id: 2, name: 'rule2', owner: 2, account: 2 }, + { id: 1, name: 'rule1', owner: 1, account: 1, enabled: true }, + { id: 2, name: 'rule2', owner: 2, account: 2, enabled: true }, ] describe('MailComponent', () => { @@ -321,4 +322,30 @@ describe('MailComponent', () => { dialog.confirmClicked.emit({ permissions: perms, merge: true }) expect(accountPatchSpy).toHaveBeenCalled() }) + + it('should update mail rule when enable is toggled', () => { + completeSetup() + const patchSpy = jest.spyOn(mailRuleService, 'patch') + const toggleInput = fixture.debugElement.query( + By.css('input[type="checkbox"]') + ) + const toastErrorSpy = jest.spyOn(toastService, 'showError') + const toastInfoSpy = jest.spyOn(toastService, 'showInfo') + // fail first + patchSpy.mockReturnValueOnce( + throwError(() => new Error('Error getting config')) + ) + toggleInput.nativeElement.click() + expect(patchSpy).toHaveBeenCalled() + expect(toastErrorSpy).toHaveBeenCalled() + // succeed second + patchSpy.mockReturnValueOnce(of(mailRules[0] as MailRule)) + toggleInput.nativeElement.click() + patchSpy.mockReturnValueOnce( + of({ ...mailRules[0], enabled: false } as MailRule) + ) + toggleInput.nativeElement.click() + expect(patchSpy).toHaveBeenCalled() + expect(toastInfoSpy).toHaveBeenCalled() + }) }) diff --git a/src-ui/src/app/components/manage/mail/mail.component.ts b/src-ui/src/app/components/manage/mail/mail.component.ts index 5d00b6c13..288e8e121 100644 --- a/src-ui/src/app/components/manage/mail/mail.component.ts +++ b/src-ui/src/app/components/manage/mail/mail.component.ts @@ -170,6 +170,21 @@ export class MailComponent this.editMailRule(clone, true) } + onMailRuleEnableToggled(rule: MailRule) { + this.mailRuleService.patch(rule).subscribe({ + next: () => { + this.toastService.showInfo( + rule.enabled + ? $localize`Rule "${rule.name}" enabled.` + : $localize`Rule "${rule.name}" disabled.` + ) + }, + error: (e) => { + this.toastService.showError($localize`Error toggling rule.`, e) + }, + }) + } + deleteMailRule(rule: MailRule) { const modal = this.modalService.open(ConfirmDialogComponent, { backdrop: 'static', diff --git a/src-ui/src/app/data/mail-rule.ts b/src-ui/src/app/data/mail-rule.ts index 2611fa3ba..7888b19e6 100644 --- a/src-ui/src/app/data/mail-rule.ts +++ b/src-ui/src/app/data/mail-rule.ts @@ -39,6 +39,8 @@ export interface MailRule extends ObjectWithPermissions { order: number + enabled: boolean + folder: string filter_from: string diff --git a/src-ui/src/app/services/rest/mail-rule.service.spec.ts b/src-ui/src/app/services/rest/mail-rule.service.spec.ts index ea84e8b86..87e21172c 100644 --- a/src-ui/src/app/services/rest/mail-rule.service.spec.ts +++ b/src-ui/src/app/services/rest/mail-rule.service.spec.ts @@ -18,6 +18,7 @@ const mail_rules = [ id: 1, account: 1, order: 1, + enabled: true, folder: 'INBOX', filter_from: null, filter_to: null, @@ -36,6 +37,7 @@ const mail_rules = [ id: 2, account: 1, order: 1, + enabled: true, folder: 'INBOX', filter_from: null, filter_to: null, @@ -54,6 +56,7 @@ const mail_rules = [ id: 3, account: 1, order: 1, + enabled: true, folder: 'INBOX', filter_from: null, filter_to: null, diff --git a/src-ui/src/styles.scss b/src-ui/src/styles.scss index c83ebd493..ef856fbc7 100644 --- a/src-ui/src/styles.scss +++ b/src-ui/src/styles.scss @@ -369,6 +369,10 @@ textarea, cursor: not-allowed; } +.cursor-pointer { + cursor: pointer; +} + ul.pagination { margin-bottom: 0; } diff --git a/src/documents/tests/test_migration_workflows.py b/src/documents/tests/test_migration_workflows.py index 403067ca6..81bb577b2 100644 --- a/src/documents/tests/test_migration_workflows.py +++ b/src/documents/tests/test_migration_workflows.py @@ -8,7 +8,7 @@ class TestMigrateWorkflow(TestMigrations): dependencies = ( ( "paperless_mail", - "0025_alter_mailaccount_owner_alter_mailrule_owner_and_more", + "0026_mailrule_enabled", ), ) diff --git a/src/paperless_mail/admin.py b/src/paperless_mail/admin.py index adec5e17c..2ff313584 100644 --- a/src/paperless_mail/admin.py +++ b/src/paperless_mail/admin.py @@ -53,7 +53,7 @@ class MailRuleAdmin(GuardedModelAdmin): } fieldsets = ( - (None, {"fields": ("name", "order", "account", "folder")}), + (None, {"fields": ("name", "order", "account", "enabled", "folder")}), ( _("Filter"), { diff --git a/src/paperless_mail/mail.py b/src/paperless_mail/mail.py index b52a2ebe4..84f97b742 100644 --- a/src/paperless_mail/mail.py +++ b/src/paperless_mail/mail.py @@ -536,6 +536,9 @@ class MailAccountHandler(LoggingMixin): ) for rule in account.rules.order_by("order"): + if not rule.enabled: + self.log.debug(f"Rule {rule}: Skipping disabled rule") + continue try: total_processed_files += self._handle_mail_rule( M, diff --git a/src/paperless_mail/migrations/0026_mailrule_enabled.py b/src/paperless_mail/migrations/0026_mailrule_enabled.py new file mode 100644 index 000000000..c10ee698c --- /dev/null +++ b/src/paperless_mail/migrations/0026_mailrule_enabled.py @@ -0,0 +1,21 @@ +# Generated by Django 5.1.1 on 2024-09-30 15:17 + +from django.db import migrations +from django.db import models + + +class Migration(migrations.Migration): + dependencies = [ + ( + "paperless_mail", + "0025_alter_mailaccount_owner_alter_mailrule_owner_and_more", + ), + ] + + operations = [ + migrations.AddField( + model_name="mailrule", + name="enabled", + field=models.BooleanField(default=True, verbose_name="enabled"), + ), + ] diff --git a/src/paperless_mail/models.py b/src/paperless_mail/models.py index c53b16f1f..c23ea48c7 100644 --- a/src/paperless_mail/models.py +++ b/src/paperless_mail/models.py @@ -115,6 +115,8 @@ class MailRule(document_models.ModelWithOwner): verbose_name=_("account"), ) + enabled = models.BooleanField(_("enabled"), default=True) + folder = models.CharField( _("folder"), default="INBOX", diff --git a/src/paperless_mail/serialisers.py b/src/paperless_mail/serialisers.py index 38ee9661e..9237b47de 100644 --- a/src/paperless_mail/serialisers.py +++ b/src/paperless_mail/serialisers.py @@ -74,6 +74,7 @@ class MailRuleSerializer(OwnedObjectSerializer): "id", "name", "account", + "enabled", "folder", "filter_from", "filter_to", diff --git a/src/paperless_mail/tests/test_mail.py b/src/paperless_mail/tests/test_mail.py index c12b54ffe..9078335a6 100644 --- a/src/paperless_mail/tests/test_mail.py +++ b/src/paperless_mail/tests/test_mail.py @@ -1388,6 +1388,41 @@ class TestMail( self.assertEqual(len(self.mailMocker.bogus_mailbox.fetch("UNSEEN", False)), 0) self.assertEqual(len(self.mailMocker.bogus_mailbox.messages), 3) + def test_disabled_rule(self): + """ + GIVEN: + - Mail rule is disabled + WHEN: + - Mail account is handled + THEN: + - Should not process any messages + """ + account = MailAccount.objects.create( + name="test", + imap_server="", + username="admin", + password="secret", + ) + MailRule.objects.create( + name="testrule", + account=account, + action=MailRule.MailAction.MARK_READ, + enabled=False, + ) + + self.mail_account_handler.handle_mail_account(account) + self.mailMocker.apply_mail_actions() + + self.assertEqual(len(self.mailMocker.bogus_mailbox.messages), 3) + self.assertEqual(len(self.mailMocker.bogus_mailbox.fetch("UNSEEN", False)), 2) + + self.mail_account_handler.handle_mail_account(account) + self.mailMocker.apply_mail_actions() + self.assertEqual( + len(self.mailMocker.bogus_mailbox.fetch("UNSEEN", False)), + 2, + ) # still 2 + class TestManagementCommand(TestCase): @mock.patch( From 0b829cab327c3fd246ed326acd46d4cd319f5ea3 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Mon, 30 Sep 2024 19:44:02 -0700 Subject: [PATCH 4/5] Enhancement: workflow overview toggle enable button (#7818) --- src-ui/messages.xlf | 39 ++++++++++++++----- .../manage/workflows/workflows.component.html | 17 +++++--- .../workflows/workflows.component.spec.ts | 23 +++++++++++ .../manage/workflows/workflows.component.ts | 17 ++++++++ 4 files changed, 82 insertions(+), 14 deletions(-) diff --git a/src-ui/messages.xlf b/src-ui/messages.xlf index e0d30bdc2..f5d270376 100644 --- a/src-ui/messages.xlf +++ b/src-ui/messages.xlf @@ -1565,11 +1565,11 @@ src/app/components/manage/workflows/workflows.component.html - 41 + 48 src/app/components/manage/workflows/workflows.component.html - 52 + 59 @@ -2470,11 +2470,11 @@ src/app/components/manage/workflows/workflows.component.html - 40 + 47 src/app/components/manage/workflows/workflows.component.html - 49 + 56 @@ -3700,7 +3700,7 @@ src/app/components/manage/workflows/workflows.component.html - 30 + 34 @@ -5059,11 +5059,11 @@ src/app/components/manage/workflows/workflows.component.html - 42 + 49 src/app/components/manage/workflows/workflows.component.html - 57 + 64 @@ -7533,7 +7533,7 @@ src/app/components/manage/workflows/workflows.component.html - 30 + 34 @@ -7927,7 +7927,7 @@ No workflows defined. src/app/components/manage/workflows/workflows.component.html - 66 + 73 @@ -7972,6 +7972,27 @@ 128 + + Enabled workflow + + src/app/components/manage/workflows/workflows.component.ts + 139 + + + + Disabled workflow + + src/app/components/manage/workflows/workflows.component.ts + 140 + + + + Error toggling workflow. + + src/app/components/manage/workflows/workflows.component.ts + 146 + + Not Found diff --git a/src-ui/src/app/components/manage/workflows/workflows.component.html b/src-ui/src/app/components/manage/workflows/workflows.component.html index 1e83efd36..ddb8a8654 100644 --- a/src-ui/src/app/components/manage/workflows/workflows.component.html +++ b/src-ui/src/app/components/manage/workflows/workflows.component.html @@ -15,9 +15,9 @@
  • Name
    -
    Sort order
    +
    Sort order
    Status
    -
    Triggers
    +
    Triggers
    Actions
  • @@ -26,9 +26,16 @@
  • -
    {{workflow.order}}
    -
    @if(workflow.enabled) { Enabled } @else { Disabled }
    -
    {{getTypesList(workflow)}}
    +
    {{workflow.order}}
    +
    +
    + + +
    +
    +
    {{getTypesList(workflow)}}
    diff --git a/src-ui/src/app/components/manage/workflows/workflows.component.spec.ts b/src-ui/src/app/components/manage/workflows/workflows.component.spec.ts index 0bccbad2d..9d92d9ba7 100644 --- a/src-ui/src/app/components/manage/workflows/workflows.component.spec.ts +++ b/src-ui/src/app/components/manage/workflows/workflows.component.spec.ts @@ -211,4 +211,27 @@ describe('WorkflowsComponent', () => { editDialog.confirmClicked.emit() expect(reloadSpy).toHaveBeenCalled() }) + + it('should update workflow when enable is toggled', () => { + const patchSpy = jest.spyOn(workflowService, 'patch') + const toggleInput = fixture.debugElement.query( + By.css('input[type="checkbox"]') + ) + const toastErrorSpy = jest.spyOn(toastService, 'showError') + const toastInfoSpy = jest.spyOn(toastService, 'showInfo') + // fail first + patchSpy.mockReturnValueOnce( + throwError(() => new Error('Error getting config')) + ) + toggleInput.nativeElement.click() + expect(patchSpy).toHaveBeenCalled() + expect(toastErrorSpy).toHaveBeenCalled() + // succeed second + patchSpy.mockReturnValueOnce(of(workflows[0])) + toggleInput.nativeElement.click() + patchSpy.mockReturnValueOnce(of({ ...workflows[0], enabled: false })) + toggleInput.nativeElement.click() + expect(patchSpy).toHaveBeenCalled() + expect(toastInfoSpy).toHaveBeenCalled() + }) }) diff --git a/src-ui/src/app/components/manage/workflows/workflows.component.ts b/src-ui/src/app/components/manage/workflows/workflows.component.ts index 92b421e9f..592dd3efe 100644 --- a/src-ui/src/app/components/manage/workflows/workflows.component.ts +++ b/src-ui/src/app/components/manage/workflows/workflows.component.ts @@ -130,4 +130,21 @@ export class WorkflowsComponent }) }) } + + onWorkflowEnableToggled(workflow: Workflow) { + this.workflowService.patch(workflow).subscribe({ + next: () => { + this.toastService.showInfo( + workflow.enabled + ? $localize`Enabled workflow` + : $localize`Disabled workflow` + ) + this.workflowService.clearCache() + this.reload() + }, + error: (e) => { + this.toastService.showError($localize`Error toggling workflow.`, e) + }, + }) + } } From 2ab71137b91baf66b5dc288d10363b4910d43628 Mon Sep 17 00:00:00 2001 From: Trenton H <797416+stumpylog@users.noreply.github.com> Date: Mon, 30 Sep 2024 19:53:44 -0700 Subject: [PATCH 5/5] Chore: Upgrades OCRMyPDF to v16 (#7815) --- .pre-commit-config.yaml | 1 - Pipfile | 2 +- Pipfile.lock | 98 ++++++++++++++------ src/paperless_mail/tests/test_parsers.py | 2 + src/paperless_tika/tests/test_tika_parser.py | 26 ++++-- 5 files changed, 91 insertions(+), 38 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fb38eaf26..a20d4ca8e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -62,7 +62,6 @@ repos: rev: v6.2.1 hooks: - id: beautysh - language_version: '3.10' additional_dependencies: - setuptools args: diff --git a/Pipfile b/Pipfile index 5061a433f..a872e1184 100644 --- a/Pipfile +++ b/Pipfile @@ -35,7 +35,7 @@ inotifyrecursive = "~=0.3" langdetect = "*" mysqlclient = "*" nltk = "*" -ocrmypdf = "~=15.4" +ocrmypdf = "~=16.5" pathvalidate = "*" pdf2image = "*" psycopg = {version = "*", extras = ["c"]} diff --git a/Pipfile.lock b/Pipfile.lock index 8d2f5d8b4..47622a94f 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "e2c4bfb1db243ebdfd0a4ca4a1709c35599e4f3999187870f268416aa01a225f" + "sha256": "1be8ddf875b6aa77fcf61f5c065c9dc3941cad4b9285ce64da60b5684357dade" }, "pipfile-spec": 6, "requires": {}, @@ -261,14 +261,6 @@ "markers": "python_version >= '3.8'", "version": "==4.2.0" }, - "chardet": { - "hashes": [ - "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7", - "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970" - ], - "markers": "python_version >= '3.7'", - "version": "==5.2.0" - }, "charset-normalizer": { "hashes": [ "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", @@ -1212,12 +1204,12 @@ }, "ocrmypdf": { "hashes": [ - "sha256:13fd388035b5f4bb673bff570cfc2cf72e51168646d5401de9e48ca355917c6d", - "sha256:4696c81cc5b5d64f31ccfe685d10baeb69b42bb0974acddf292d8cf9d97605c3" + "sha256:9222b1b0818b65c891559b84efab2e84434c71149b3aaaa6dc654457e0b66b14", + "sha256:cd96bddfb3a986be7bf7857757919332e1db5dab780eb7b321fdea38f60127ac" ], "index": "pypi", - "markers": "python_version >= '3.9'", - "version": "==15.4.4" + "markers": "python_version >= '3.10'", + "version": "==16.5.0" }, "packaging": { "hashes": [ @@ -1244,7 +1236,7 @@ "index": "pypi", "version": "==1.17.0" }, - "pdfminer.six": { + "pdfminer-six": { "hashes": [ "sha256:c631a46d5da957a9ffe4460c5dce21e8431dabb615fee5f9f4400603a58d95a6", "sha256:f4f70e74174b4b3542fcb8406a210b6e2e27cd0f0b5fd04534a8cc0d8951e38c" @@ -1252,6 +1244,64 @@ "markers": "python_version >= '3.8'", "version": "==20240706" }, + "pi-heif": { + "hashes": [ + "sha256:00a6d72ba2cc1477c8a909bfbbac4f5d931a25a88979077b231b76e7b9c80ba6", + "sha256:054cd3544e421b342b15b5eb8db4de222a09ca3ae441f4fa5943f80d9e65c5d6", + "sha256:0962b4cd828ad1ae94f9cd8e95ed0741cddcd19082cb97d5b69bfe1ac6623eb9", + "sha256:0a690159607beaa6712f2c8abaa5168a22314d18f00a617d691548f5acba8070", + "sha256:0d5dd431dbf7be88267fbfb08623bcf2d16628cdcbc898bcc0e05412dc43fd26", + "sha256:1159f54d76b860cc27753c9925e2923959d8b5277372db946cb1078fa11ed1ea", + "sha256:18d113c14fecadb90c3d8838240120e6f93671618eb96d776f994b314f1f858c", + "sha256:24ca403e556c84ce0e36ea1477530f7854e71c2523eb1a97c91d5d9ce8bbc548", + "sha256:286a5d2b5036cf3da8f1a2e1ad54044aaabe4d46b178057323f5a6ce19417741", + "sha256:2b892ebc898ca32c1a1ec9e72658c0d14de5ac31c1bd61a8aa66dc645080e32f", + "sha256:2c912219964dc864e1454ab4f43d97cbf6a88d065410a16936e7c59b1290a7da", + "sha256:34725b542bd2737be7e7909fff1fb6d39760d3d395a36ce6fae5280e88ba94a6", + "sha256:3529f904f51594a613759ab610799ce34b615339d67e642843eec1ac7868814d", + "sha256:3c09d22ed75200372b8102debf4ba69d8f63c595870505b9188d6c9a9b48e1f2", + "sha256:3fa5366b2f555b6b3a56b09aa74f178a040edb174b29060d8d56c03eea154e43", + "sha256:45d360c3a056d9c81b0480a546f291bbc53caf70705f3a49d082e728735ed4ae", + "sha256:4d88aba685051131f103a7afc428412abd7d09640719635f8880898b0e7aec97", + "sha256:4ecb9031ad1cb7eed1591cba95420964557cff8fc63bab9bdc204d53301e502f", + "sha256:5254dc3121d2a38036beae631aae620d0c942f03973ec134ae9827b60e7d5c0b", + "sha256:5424435551e606e1ac515de46a2b1c6d8e82c7a89473bb7cf9398368f051d675", + "sha256:571d69be0088336c4251d7301f3fdc0fecab45e38286e71a23e64814489c5a15", + "sha256:573602d8c68f4ff93c4d35439d7566b3f2d4ab774925367aece20f9cd0ba243d", + "sha256:64ed341f91763e29096b0ddb38b50d13879d06039889d458fc7dac6d5c03dd80", + "sha256:6541a05177c3d8f00e56f4cc8ee9c681eb25fcdc917065acbc426847eb8aea97", + "sha256:6c7a28547e3f1e2f43b395d2764f693fcfa4eb8a4da0d5815c7eb3eeda745fbb", + "sha256:71309d2a632c0b8716ccbbb9e413ee28b8439967c45c92de68888fe4acf80244", + "sha256:742560127423bd179605325a41322df800ca02df768e872bfe189fe371f61578", + "sha256:74d4b07f0589df9fac138ecbcccd248217a12bbebd3443153158d7f54522e257", + "sha256:79969f90a5a01b9a82b18bb0667392da733790585531b3183b7f375b9e88dbcd", + "sha256:7a9a95f54cb3a473005572f7309666b71d03c1764134b2df0ed796744c7aa069", + "sha256:7acdd41dc72c01c1f2cfd91624a1c102ecc324fff6a501ab981c6f803f673b1b", + "sha256:7e0c3286f106f2d22d394b844c0e015f132567d70b31fef6d3cc846b8fe9dbc6", + "sha256:83548aa70e44fef865c2b2575ed949f2e6eba756b114ca6ad525ef56b5449d57", + "sha256:86f7aad733292fea8a2869814117caf11ed424731bd90fe1693b2ccbfcc6bfed", + "sha256:886fbbda898559eba0843feca17e6c7e43c13336404817c6d07a01d4955c3d33", + "sha256:8d0a7529225f1a25231d8f2cfd39f722c31e5396581eeeaa7a30793188e8b4f7", + "sha256:9ff516f9f5118a8f2e47531611324e6a07848e4f1f17c5df485de734e50dee7e", + "sha256:a4b3690f03636944b13ab313d21ee90a46d5fa35a15d884563b0ff400b813042", + "sha256:aac4fc247139081b30581cadbea00bb4c4fb7274140eaa1147e22bcf7ece7525", + "sha256:ad3f54dcc54a4c2ed1c58a135375330fe7b2ba2c2a8a816d3296c12e9d8c284c", + "sha256:b2af8ac6bd93e5df02b9f292a10664524844f37b39079e55aa9ef5857a3b0a22", + "sha256:c5bded35d1cefb594f6ce9d775e3e6b750a32926779f7b496f0f8d4992db09e1", + "sha256:cab6f7a00ccbcc3087d400a544e62ef30eff6339cf0d600588b92b1e7ca49d96", + "sha256:ccd611653581f39c77ab8222a660e471e724d8f7c6f4e50760b10ce06769d9d8", + "sha256:cfa979043be0d4ad1b37f6794fdff010cf69e5ada1ef74eef4a5b3983d3b8881", + "sha256:d7dc682acccd81857fd4b5849ebe7b9504e11eab493ffa0905ea25eaf5fb0f93", + "sha256:e568a323548896848489035c5bb2e4de13df07fbdbd33831b165ff545066b97f", + "sha256:f19d8cdffbc5e8e9f3676839c8632ffd161d17f84f614cad9b98a58e27ffd3a7", + "sha256:f1b7c4daeaffb235e73fc54132f4aa8bcb229dcb463ac0b4def9e1aee5793165", + "sha256:f792a278335c278d2c092a62aaad3a7362021f9341f988b1b8b3ca4783651e49", + "sha256:fae39eec07f4b477c582ddd75d38610553c1b6d19cd6ce4a3ded4c7e0ee029ac", + "sha256:fe0e424d08d59c5a1d74dfa7239b40a935b5a526305ebecd2c27755aa3442225" + ], + "markers": "python_version >= '3.8'", + "version": "==0.18.0" + }, "pikepdf": { "hashes": [ "sha256:01be001988ce0f6a5a89319f37fc14f27df75c4e332222ed8e993d14405acb02", @@ -1788,14 +1838,6 @@ "markers": "python_version >= '3.8'", "version": "==2024.9.11" }, - "reportlab": { - "hashes": [ - "sha256:6e4d86647b8bfd772f475a58f9b0dcba4b340b1969f0db36333089f6ca9ab362", - "sha256:a00b57292e156a7bda84edf31d60c25578153076c8fb96331d0c59eddda052c8" - ], - "markers": "python_version >= '3.7' and python_version < '4'", - "version": "==4.2.4" - }, "requests": { "hashes": [ "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", @@ -3227,12 +3269,12 @@ }, "mkdocs-material": { "hashes": [ - "sha256:1843c5171ad6b489550aeaf7358e5b7128cc03ddcf0fb4d91d19aa1e691a63b8", - "sha256:d4779051d52ba9f1e7e344b34de95449c7c366c212b388e4a2db9a3db043c228" + "sha256:0f2f68c8db89523cb4a59705cd01b4acd62b2f71218ccb67e1e004e560410d2b", + "sha256:25faa06142afa38549d2b781d475a86fb61de93189f532b88e69bf11e5e5c3be" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==9.5.38" + "version": "==9.5.39" }, "mkdocs-material-extensions": { "hashes": [ @@ -3528,12 +3570,12 @@ }, "pytest-httpx": { "hashes": [ - "sha256:6d47849691faf11d2532565d0c8e0e02b9f4ee730da31687feae315581d7520c", - "sha256:755b8edca87c974dd4f3605c374fda11db84631de3d163b99c0df5807023a19a" + "sha256:685d93ce5e5edb5e52310b72342cdc190bebf83aab058328943dd8bd8f6ac790", + "sha256:7807647e8254e5cff79bf2041ae272449ce915d3cf1bbecaa581c384163adb87" ], "index": "pypi", "markers": "python_version >= '3.9'", - "version": "==0.30.0" + "version": "==0.32.0" }, "pytest-mock": { "hashes": [ diff --git a/src/paperless_mail/tests/test_parsers.py b/src/paperless_mail/tests/test_parsers.py index a0baa4821..e8186ea0f 100644 --- a/src/paperless_mail/tests/test_parsers.py +++ b/src/paperless_mail/tests/test_parsers.py @@ -497,6 +497,7 @@ class TestParser: assert mail_parser.archive_path is not None + @pytest.mark.httpx_mock(can_send_already_matched_responses=True) def test_generate_pdf_html_email( self, httpx_mock: HTTPXMock, @@ -575,6 +576,7 @@ class TestParser: with pytest.raises(ParseError): mail_parser.parse(html_email_file, "message/rfc822") + @pytest.mark.httpx_mock(can_send_already_matched_responses=True) def test_generate_pdf_html_email_merge_failure( self, httpx_mock: HTTPXMock, diff --git a/src/paperless_tika/tests/test_tika_parser.py b/src/paperless_tika/tests/test_tika_parser.py index 6b048f252..cebae2486 100644 --- a/src/paperless_tika/tests/test_tika_parser.py +++ b/src/paperless_tika/tests/test_tika_parser.py @@ -5,7 +5,6 @@ from pathlib import Path import pytest from httpx import codes -from httpx._multipart import DataField from pytest_django.fixtures import SettingsWrapper from pytest_httpx import HTTPXMock @@ -128,11 +127,22 @@ class TestTikaParser: tika_parser.convert_to_pdf(sample_odt_file, None) request = httpx_mock.get_request() - found = False - for field in request.stream.fields: - if isinstance(field, DataField) and field.name == "pdfa": - assert field.value == expected_form_value - found = True - assert found, "pdfFormat was not found" - httpx_mock.reset(assert_all_responses_were_requested=False) + expected_field_name = "pdfa" + + content_type = request.headers["Content-Type"] + assert "multipart/form-data" in content_type + + boundary = content_type.split("boundary=")[1] + + parts = request.content.split(f"--{boundary}".encode()) + + form_field_found = any( + f'name="{expected_field_name}"'.encode() in part + and expected_form_value.encode() in part + for part in parts + ) + + assert form_field_found + + httpx_mock.reset()