diff --git a/src-ui/src/app/components/document-list/document-list.component.html b/src-ui/src/app/components/document-list/document-list.component.html
index a29e146bf..cfc2e655d 100644
--- a/src-ui/src/app/components/document-list/document-list.component.html
+++ b/src-ui/src/app/components/document-list/document-list.component.html
@@ -152,7 +152,7 @@
{{d.title | documentTitle}}
-
+
diff --git a/src-ui/src/app/components/document-list/filter-editor/filter-editor.component.ts b/src-ui/src/app/components/document-list/filter-editor/filter-editor.component.ts
index b12fe00f0..b5cca38ac 100644
--- a/src-ui/src/app/components/document-list/filter-editor/filter-editor.component.ts
+++ b/src-ui/src/app/components/document-list/filter-editor/filter-editor.component.ts
@@ -8,7 +8,7 @@ import { DocumentTypeService } from 'src/app/services/rest/document-type.service
import { TagService } from 'src/app/services/rest/tag.service';
import { CorrespondentService } from 'src/app/services/rest/correspondent.service';
import { FilterRule } from 'src/app/data/filter-rule';
-import { FILTER_ADDED_AFTER, FILTER_ADDED_BEFORE, FILTER_CORRESPONDENT, FILTER_CREATED_AFTER, FILTER_CREATED_BEFORE, FILTER_DOCUMENT_TYPE, FILTER_HAS_ANY_TAG, FILTER_HAS_TAGS_ALL, FILTER_HAS_TAGS_ANY, FILTER_TITLE } from 'src/app/data/filter-rule-type';
+import { FILTER_ADDED_AFTER, FILTER_ADDED_BEFORE, FILTER_CORRESPONDENT, FILTER_CREATED_AFTER, FILTER_CREATED_BEFORE, FILTER_DOCUMENT_TYPE, FILTER_HAS_ANY_TAG, FILTER_HAS_TAGS_ALL, FILTER_HAS_TAGS_ANY, FILTER_DOES_NOT_HAVE_TAG, FILTER_TITLE } from 'src/app/data/filter-rule-type';
import { FilterableDropdownSelectionModel } from '../../common/filterable-dropdown/filterable-dropdown.component';
import { ToggleableItemState } from '../../common/filterable-dropdown/toggleable-dropdown-button/toggleable-dropdown-button.component';
@@ -111,6 +111,9 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
case FILTER_HAS_ANY_TAG:
this.tagSelectionModel.set(null, ToggleableItemState.Selected, false)
break
+ case FILTER_DOES_NOT_HAVE_TAG:
+ this.tagSelectionModel.set(rule.value ? +rule.value : null, ToggleableItemState.Excluded, false)
+ break
case FILTER_CORRESPONDENT:
this.correspondentSelectionModel.set(rule.value ? +rule.value : null, ToggleableItemState.Selected, false)
break
@@ -133,6 +136,9 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
this.tagSelectionModel.getSelectedItems().filter(tag => tag.id).forEach(tag => {
filterRules.push({rule_type: tagFilterType, value: tag.id?.toString()})
})
+ this.tagSelectionModel.getExcludedItems().filter(tag => tag.id).forEach(tag => {
+ filterRules.push({rule_type: FILTER_DOES_NOT_HAVE_TAG, value: tag.id?.toString()})
+ })
}
this.correspondentSelectionModel.getSelectedItems().forEach(correspondent => {
filterRules.push({rule_type: FILTER_CORRESPONDENT, value: correspondent.id?.toString()})
diff --git a/src-ui/src/locale/messages.de.xlf b/src-ui/src/locale/messages.de.xlf
index 80cfefab9..e1539618c 100644
--- a/src-ui/src/locale/messages.de.xlf
+++ b/src-ui/src/locale/messages.de.xlf
@@ -449,12 +449,20 @@
2
+
+ Filter by:
+ Filtern nach:
+
+ src/app/components/manage/tag-list/tag-list.component.html
+ 8
+
+
Name
Name
src/app/components/manage/tag-list/tag-list.component.html
- 13
+ 9
@@ -462,7 +470,7 @@
Farbe
src/app/components/manage/tag-list/tag-list.component.html
- 14
+ 20
@@ -470,7 +478,7 @@
Zuweisung
src/app/components/manage/tag-list/tag-list.component.html
- 15
+ 21
@@ -478,7 +486,7 @@
Anzahl Dokumente
src/app/components/manage/tag-list/tag-list.component.html
- 16
+ 22
@@ -486,7 +494,7 @@
Aktionen
src/app/components/manage/tag-list/tag-list.component.html
- 17
+ 23
@@ -494,7 +502,7 @@
Dokumente
src/app/components/manage/tag-list/tag-list.component.html
- 32
+ 38
@@ -502,7 +510,7 @@
Bearbeiten
src/app/components/manage/tag-list/tag-list.component.html
- 37
+ 43
@@ -814,7 +822,7 @@
Letzter Kontakt
src/app/components/manage/correspondent-list/correspondent-list.component.html
- 15
+ 22
@@ -1114,14 +1122,6 @@
46
-
- Filter by:
- Filtern nach:
-
- src/app/components/document-list/filter-editor/filter-editor.component.html
- 4
-
-
Filter tags
Tags filtern
@@ -1907,7 +1907,7 @@
Automatisch
src/app/components/manage/generic-list/generic-list.component.ts
- 33
+ 39
@@ -1915,7 +1915,7 @@
Möchten Sie dieses Element wirklich löschen?
src/app/components/manage/generic-list/generic-list.component.ts
- 76
+ 97
@@ -1923,7 +1923,7 @@
Assoziierte Dokumente werden nicht gelöscht.
src/app/components/manage/generic-list/generic-list.component.ts
- 83
+ 104
@@ -1931,7 +1931,7 @@
Löschen
src/app/components/manage/generic-list/generic-list.component.ts
- 85
+ 106
@@ -1939,7 +1939,7 @@
Fehler beim Löschen des Elements:
src/app/components/manage/generic-list/generic-list.component.ts
- 93
+ 114
diff --git a/src-ui/src/locale/messages.fr.xlf b/src-ui/src/locale/messages.fr.xlf
index ee97f7ce0..0ee824d5b 100644
--- a/src-ui/src/locale/messages.fr.xlf
+++ b/src-ui/src/locale/messages.fr.xlf
@@ -449,12 +449,20 @@
2
+
+ Filter by:
+ Filtrer par :
+
+ src/app/components/manage/tag-list/tag-list.component.html
+ 8
+
+
Name
Nom
src/app/components/manage/tag-list/tag-list.component.html
- 13
+ 9
@@ -462,7 +470,7 @@
Couleur
src/app/components/manage/tag-list/tag-list.component.html
- 14
+ 20
@@ -470,7 +478,7 @@
Rapprochement
src/app/components/manage/tag-list/tag-list.component.html
- 15
+ 21
@@ -478,7 +486,7 @@
Nombre de documents
src/app/components/manage/tag-list/tag-list.component.html
- 16
+ 22
@@ -486,7 +494,7 @@
Actions
src/app/components/manage/tag-list/tag-list.component.html
- 17
+ 23
@@ -494,7 +502,7 @@
Documents
src/app/components/manage/tag-list/tag-list.component.html
- 32
+ 38
@@ -502,7 +510,7 @@
Éditer
src/app/components/manage/tag-list/tag-list.component.html
- 37
+ 43
@@ -814,7 +822,7 @@
Dernière correspondance
src/app/components/manage/correspondent-list/correspondent-list.component.html
- 15
+ 22
@@ -1114,14 +1122,6 @@
46
-
- Filter by:
- Filtrer par :
-
- src/app/components/document-list/filter-editor/filter-editor.component.html
- 4
-
-
Filter tags
Filtrer les étiquettes
@@ -1907,7 +1907,7 @@
Automatique
src/app/components/manage/generic-list/generic-list.component.ts
- 33
+ 39
@@ -1915,7 +1915,7 @@
Voulez-vous vraiment supprimer cet élément ?
src/app/components/manage/generic-list/generic-list.component.ts
- 76
+ 97
@@ -1923,7 +1923,7 @@
Les documents associés ne seront pas supprimés.
src/app/components/manage/generic-list/generic-list.component.ts
- 83
+ 104
@@ -1931,7 +1931,7 @@
Supprimer
src/app/components/manage/generic-list/generic-list.component.ts
- 85
+ 106
@@ -1939,7 +1939,7 @@
Une erreur s'est produite lors de la suppression de l'élément :
src/app/components/manage/generic-list/generic-list.component.ts
- 93
+ 114
diff --git a/src-ui/src/locale/messages.nl_NL.xlf b/src-ui/src/locale/messages.nl_NL.xlf
index 39545c0e2..49f3010ad 100644
--- a/src-ui/src/locale/messages.nl_NL.xlf
+++ b/src-ui/src/locale/messages.nl_NL.xlf
@@ -449,12 +449,20 @@
2
+
+ Filter by:
+ Filter op:
+
+ src/app/components/manage/tag-list/tag-list.component.html
+ 8
+
+
Name
Naam
src/app/components/manage/tag-list/tag-list.component.html
- 13
+ 9
@@ -462,7 +470,7 @@
Kleur
src/app/components/manage/tag-list/tag-list.component.html
- 14
+ 20
@@ -470,7 +478,7 @@
Overeenkomend
src/app/components/manage/tag-list/tag-list.component.html
- 15
+ 21
@@ -478,7 +486,7 @@
Aantal documenten
src/app/components/manage/tag-list/tag-list.component.html
- 16
+ 22
@@ -486,7 +494,7 @@
Acties
src/app/components/manage/tag-list/tag-list.component.html
- 17
+ 23
@@ -494,7 +502,7 @@
Documenten
src/app/components/manage/tag-list/tag-list.component.html
- 32
+ 38
@@ -502,7 +510,7 @@
Bewerk
src/app/components/manage/tag-list/tag-list.component.html
- 37
+ 43
@@ -814,7 +822,7 @@
Laatste correspondentie
src/app/components/manage/correspondent-list/correspondent-list.component.html
- 15
+ 22
@@ -1114,14 +1122,6 @@
46
-
- Filter by:
- Filter op:
-
- src/app/components/document-list/filter-editor/filter-editor.component.html
- 4
-
-
Filter tags
Etiketten filteren
@@ -1907,7 +1907,7 @@
Automatisch
src/app/components/manage/generic-list/generic-list.component.ts
- 33
+ 39
@@ -1915,7 +1915,7 @@
Wil je dit element echt verwijderen?
src/app/components/manage/generic-list/generic-list.component.ts
- 76
+ 97
@@ -1923,7 +1923,7 @@
Geassocieerde documenten zullen niet verwijderd worden.
src/app/components/manage/generic-list/generic-list.component.ts
- 83
+ 104
@@ -1931,7 +1931,7 @@
Verwijderen
src/app/components/manage/generic-list/generic-list.component.ts
- 85
+ 106
@@ -1939,7 +1939,7 @@
Fout bij het verwijderen van het element:
src/app/components/manage/generic-list/generic-list.component.ts
- 93
+ 114
diff --git a/src/documents/management/commands/document_consumer.py b/src/documents/management/commands/document_consumer.py
index 595bd39cd..690f5299a 100644
--- a/src/documents/management/commands/document_consumer.py
+++ b/src/documents/management/commands/document_consumer.py
@@ -70,31 +70,6 @@ def _consume(filepath):
"Error while consuming document: {}".format(e))
-def _test_inotify(directory):
- if not INotify:
- return False
-
- test_file = os.path.join(directory, "__inotify_test_file__")
- inotify = INotify()
- descriptor = None
- try:
- inotify_flags = flags.CLOSE_WRITE | flags.MOVED_TO
- descriptor = inotify.add_watch(directory, inotify_flags)
- Path(test_file).touch()
- events = inotify.read(timeout=1000)
- return len(events) == 1
- except Exception as e:
- logger.warning(
- f"Error while checking inotify availability: {str(e)}")
- return False
- finally:
- if descriptor:
- inotify.rm_watch(descriptor)
- inotify.close()
- if os.path.isfile(test_file):
- os.unlink(test_file)
-
-
def _consume_wait_unmodified(file, num_tries=20, wait_time=1):
mtime = -1
current_try = 0
@@ -179,24 +154,16 @@ class Command(BaseCommand):
return
if settings.CONSUMER_POLLING == 0:
- if _test_inotify(directory):
- self.handle_inotify(directory, recursive)
- else:
- logger.warning(
- f"Inotify notifications are not available on {directory}, "
- f"falling back to polling every 10 seconds")
- self.handle_polling(
- directory, recursive, 10)
+ self.handle_inotify(directory, recursive)
else:
- self.handle_polling(
- directory, recursive, settings.CONSUMER_POLLING)
+ self.handle_polling(directory, recursive)
logger.debug("Consumer exiting.")
- def handle_polling(self, directory, recursive, timeout):
+ def handle_polling(self, directory, recursive):
logging.getLogger(__name__).info(
f"Polling directory for changes: {directory}")
- self.observer = PollingObserver(timeout=timeout)
+ self.observer = PollingObserver(timeout=settings.CONSUMER_POLLING)
self.observer.schedule(Handler(), directory, recursive=recursive)
self.observer.start()
try:
diff --git a/src/documents/models.py b/src/documents/models.py
index 0920f9e47..444738252 100755
--- a/src/documents/models.py
+++ b/src/documents/models.py
@@ -63,12 +63,6 @@ class MatchingModel(models.Model):
def __str__(self):
return self.name
- def save(self, *args, **kwargs):
-
- self.match = self.match.lower()
-
- models.Model.save(self, *args, **kwargs)
-
class Correspondent(MatchingModel):
diff --git a/src/documents/tests/test_api.py b/src/documents/tests/test_api.py
index 5f81b83b6..6b9cf14e0 100644
--- a/src/documents/tests/test_api.py
+++ b/src/documents/tests/test_api.py
@@ -923,6 +923,14 @@ class TestBulkEdit(DirectoriesMixin, APITestCase):
doc2 = Document.objects.get(id=self.doc2.id)
self.assertEqual(doc2.correspondent, self.c1)
+ def test_api_no_correspondent(self):
+ response = self.client.post("/api/documents/bulk_edit/", json.dumps({
+ "documents": [self.doc2.id],
+ "method": "set_correspondent",
+ "parameters": {}
+ }), content_type='application/json')
+ self.assertEqual(response.status_code, 400)
+
def test_api_invalid_document_type(self):
self.assertEqual(self.doc2.document_type, self.dt1)
response = self.client.post("/api/documents/bulk_edit/", json.dumps({
@@ -935,6 +943,14 @@ class TestBulkEdit(DirectoriesMixin, APITestCase):
doc2 = Document.objects.get(id=self.doc2.id)
self.assertEqual(doc2.document_type, self.dt1)
+ def test_api_no_document_type(self):
+ response = self.client.post("/api/documents/bulk_edit/", json.dumps({
+ "documents": [self.doc2.id],
+ "method": "set_document_type",
+ "parameters": {}
+ }), content_type='application/json')
+ self.assertEqual(response.status_code, 400)
+
def test_api_add_invalid_tag(self):
self.assertEqual(list(self.doc2.tags.all()), [self.t1])
response = self.client.post("/api/documents/bulk_edit/", json.dumps({
@@ -946,6 +962,14 @@ class TestBulkEdit(DirectoriesMixin, APITestCase):
self.assertEqual(list(self.doc2.tags.all()), [self.t1])
+ def test_api_add_tag_no_tag(self):
+ response = self.client.post("/api/documents/bulk_edit/", json.dumps({
+ "documents": [self.doc2.id],
+ "method": "add_tag",
+ "parameters": {}
+ }), content_type='application/json')
+ self.assertEqual(response.status_code, 400)
+
def test_api_delete_invalid_tag(self):
self.assertEqual(list(self.doc2.tags.all()), [self.t1])
response = self.client.post("/api/documents/bulk_edit/", json.dumps({
@@ -957,6 +981,14 @@ class TestBulkEdit(DirectoriesMixin, APITestCase):
self.assertEqual(list(self.doc2.tags.all()), [self.t1])
+ def test_api_delete_tag_no_tag(self):
+ response = self.client.post("/api/documents/bulk_edit/", json.dumps({
+ "documents": [self.doc2.id],
+ "method": "remove_tag",
+ "parameters": {}
+ }), content_type='application/json')
+ self.assertEqual(response.status_code, 400)
+
def test_api_modify_invalid_tags(self):
self.assertEqual(list(self.doc2.tags.all()), [self.t1])
response = self.client.post("/api/documents/bulk_edit/", json.dumps({
@@ -966,6 +998,21 @@ class TestBulkEdit(DirectoriesMixin, APITestCase):
}), content_type='application/json')
self.assertEqual(response.status_code, 400)
+ def test_api_modify_tags_no_tags(self):
+ response = self.client.post("/api/documents/bulk_edit/", json.dumps({
+ "documents": [self.doc2.id],
+ "method": "modify_tags",
+ "parameters": {"remove_tags": [1123123]}
+ }), content_type='application/json')
+ self.assertEqual(response.status_code, 400)
+
+ response = self.client.post("/api/documents/bulk_edit/", json.dumps({
+ "documents": [self.doc2.id],
+ "method": "modify_tags",
+ "parameters": {'add_tags': [self.t2.id, 1657]}
+ }), content_type='application/json')
+ self.assertEqual(response.status_code, 400)
+
def test_api_selection_data_empty(self):
response = self.client.post("/api/documents/selection_data/", json.dumps({
"documents": []
diff --git a/src/documents/tests/test_management_consumer.py b/src/documents/tests/test_management_consumer.py
index 0680e7f56..b6a61a167 100644
--- a/src/documents/tests/test_management_consumer.py
+++ b/src/documents/tests/test_management_consumer.py
@@ -7,9 +7,8 @@ from unittest import mock
from django.conf import settings
from django.core.management import call_command, CommandError
-from django.test import override_settings, TransactionTestCase, TestCase
+from django.test import override_settings, TransactionTestCase
-from documents.management.commands.document_consumer import _test_inotify
from documents.models import Tag
from documents.consumer import ConsumerError
from documents.management.commands import document_consumer
@@ -261,27 +260,3 @@ class TestConsumerTags(DirectoriesMixin, ConsumerMixin, TransactionTestCase):
@override_settings(CONSUMER_POLLING=1)
def test_consume_file_with_path_tags_polling(self):
self.test_consume_file_with_path_tags()
-
-
-class TestInotify(DirectoriesMixin, TestCase):
-
- def test_inotify(self):
- self.assertTrue(_test_inotify(self.dirs.consumption_dir))
-
- @mock.patch("documents.management.commands.document_consumer.Path.touch")
- def test_inotify_error(self, m):
- m.side_effect = OSError("Permission error")
- self.assertFalse(_test_inotify(self.dirs.consumption_dir))
-
- @mock.patch("documents.management.commands.document_consumer.Command.handle_polling")
- @mock.patch("documents.management.commands.document_consumer.Command.handle_inotify")
- @mock.patch("documents.management.commands.document_consumer._test_inotify")
- def test_polling_fallback(self, test_inotify, handle_inotify, handle_polling):
- test_inotify.return_value = False
-
- cmd = document_consumer.Command()
- cmd.handle(directory=settings.CONSUMPTION_DIR, oneshot=False)
-
- test_inotify.assert_called_once()
- handle_polling.assert_called_once()
- handle_inotify.assert_not_called()
diff --git a/src/documents/tests/test_parsers.py b/src/documents/tests/test_parsers.py
index 805e4beac..392c0504f 100644
--- a/src/documents/tests/test_parsers.py
+++ b/src/documents/tests/test_parsers.py
@@ -120,3 +120,4 @@ class TestParserAvailability(TestCase):
self.assertTrue(is_file_ext_supported('.pdf'))
self.assertFalse(is_file_ext_supported('.hsdfh'))
+ self.assertFalse(is_file_ext_supported(''))