diff --git a/src/documents/filters.py b/src/documents/filters.py index 891f20dde..b75f9c9c4 100644 --- a/src/documents/filters.py +++ b/src/documents/filters.py @@ -19,6 +19,7 @@ from documents.models import Log from documents.models import ShareLink from documents.models import StoragePath from documents.models import Tag +from documents.models import Warehouse CHAR_KWARGS = ["istartswith", "iendswith", "icontains", "iexact"] ID_KWARGS = ["in", "exact"] @@ -257,3 +258,16 @@ class ObjectOwnedOrGrantedPermissionsFilter(ObjectPermissionsFilter): objects_owned = queryset.filter(owner=request.user) objects_unowned = queryset.filter(owner__isnull=True) return objects_with_perms | objects_owned | objects_unowned + + + + +class WarehouseFilterSet(FilterSet): + class Meta: + model = Warehouse + fields = { + "id": ID_KWARGS, + "name": CHAR_KWARGS, + "type": CHAR_KWARGS, + "parent_warehouse": ID_KWARGS, + } \ No newline at end of file diff --git a/src/documents/migrations/1047_warehouse.py b/src/documents/migrations/1047_warehouse.py new file mode 100644 index 000000000..1ac590460 --- /dev/null +++ b/src/documents/migrations/1047_warehouse.py @@ -0,0 +1,30 @@ +# Generated by Django 4.2.11 on 2024-05-15 04:18 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('documents', '1046_workflowaction_remove_all_correspondents_and_more'), + ] + + operations = [ + migrations.CreateModel( + name='Warehouse', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=256, unique=True, verbose_name='name')), + ('type', models.CharField(blank=True, choices=[(1, 'Warehouse'), (2, 'Shelf'), (3, 'Boxcase')], default=1, max_length=20, null=True)), + ('owner', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='owner')), + ('parent_warehouse', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='parent_warehouses', to='documents.warehouse')), + ], + options={ + 'verbose_name': 'warehouse', + 'verbose_name_plural': 'warehouses', + }, + ), + ] diff --git a/src/documents/models.py b/src/documents/models.py index 8e7a16a60..5613735b0 100644 --- a/src/documents/models.py +++ b/src/documents/models.py @@ -1237,3 +1237,30 @@ class Workflow(models.Model): def __str__(self): return f"Workflow: {self.name}" + + + + +class Warehouse(ModelWithOwner): + + WAREHOUSE = "Warehouse" + SHELF = "Shelf" + BOXCASE = "Boxcase" + TYPE_WAREHOUSE = ( + (WAREHOUSE, _("Warehouse")), + (SHELF, _("Shelf")), + (BOXCASE, _("Boxcase")), + ) + + name = models.CharField(_("name"), max_length=256, unique=True) + type = models.CharField(max_length=20, null=True, blank=True, + choices=TYPE_WAREHOUSE, + default=WAREHOUSE,) + parent_warehouse = models.ForeignKey('self', on_delete=models.CASCADE, null=True, blank=True, related_name="parent_warehouses" ) + + class Meta: + verbose_name = _("warehouse") + verbose_name_plural = _("warehouses") + + def __str__(self): + return self.name \ No newline at end of file diff --git a/src/documents/serialisers.py b/src/documents/serialisers.py index 26930ccec..e170f8155 100644 --- a/src/documents/serialisers.py +++ b/src/documents/serialisers.py @@ -45,6 +45,7 @@ from documents.models import UiSettings from documents.models import Workflow from documents.models import WorkflowAction from documents.models import WorkflowTrigger +from documents.models import Warehouse from documents.parsers import is_mime_type_supported from documents.permissions import get_groups_with_only_permission from documents.permissions import set_permissions_for_object @@ -1384,6 +1385,7 @@ class BulkEditObjectsSerializer(SerializerWithPerms, SetPermissionsMixin): "correspondents", "document_types", "storage_paths", + "warehouses", ], label="Object Type", write_only=True, @@ -1428,6 +1430,8 @@ class BulkEditObjectsSerializer(SerializerWithPerms, SetPermissionsMixin): object_class = DocumentType elif object_type == "storage_paths": object_class = StoragePath + elif object_type == "warehouses": + object_class = Warehouse return object_class def _validate_objects(self, objects, object_type): @@ -1740,3 +1744,22 @@ class WorkflowSerializer(serializers.ModelSerializer): self.prune_triggers_and_actions() return instance + + + + + + +class WarehouseSerializer(MatchingModelSerializer, OwnedObjectSerializer): + parent_warehouse_reference = serializers.SerializerMethodField() + class Meta: + model = Warehouse + fields = '__all__' + + def get_parent_warehouse_reference(self, obj): + if obj.parent_warehouse: + return WarehouseSerializer(obj.parent_warehouse).data + return None + + + \ No newline at end of file diff --git a/src/documents/views.py b/src/documents/views.py index 5841649d0..e3a74cd4f 100644 --- a/src/documents/views.py +++ b/src/documents/views.py @@ -99,6 +99,8 @@ from documents.filters import ObjectOwnedOrGrantedPermissionsFilter from documents.filters import ShareLinkFilterSet from documents.filters import StoragePathFilterSet from documents.filters import TagFilterSet +from documents.filters import WarehouseFilterSet + from documents.matching import match_correspondents from documents.matching import match_document_types from documents.matching import match_storage_paths @@ -117,6 +119,8 @@ from documents.models import UiSettings from documents.models import Workflow from documents.models import WorkflowAction from documents.models import WorkflowTrigger +from documents.models import Warehouse + from documents.parsers import get_parser_class_for_mime_type from documents.parsers import parse_date_generator from documents.permissions import PaperlessAdminPermissions @@ -144,6 +148,8 @@ from documents.serialisers import UiSettingsViewSerializer from documents.serialisers import WorkflowActionSerializer from documents.serialisers import WorkflowSerializer from documents.serialisers import WorkflowTriggerSerializer +from documents.serialisers import WarehouseSerializer + from documents.signals import document_updated from documents.tasks import consume_file from paperless import version @@ -1749,3 +1755,24 @@ class SystemStatusView(PassUserMixin): }, }, ) + + + +class WarehouseViewSet(ModelViewSet): + model = Warehouse + + queryset = Warehouse.objects.select_related("owner").order_by( + Lower("name"), + ) + + serializer_class = WarehouseSerializer + pagination_class = StandardPagination + permission_classes = (IsAuthenticated, PaperlessObjectPermissions) + filter_backends = ( + DjangoFilterBackend, + OrderingFilter, + ObjectOwnedOrGrantedPermissionsFilter, + ) + filterset_class = WarehouseFilterSet + ordering_fields = ("name", "type", "parent_warehouse") + diff --git a/src/paperless/urls.py b/src/paperless/urls.py index 12b049918..f22fd040d 100644 --- a/src/paperless/urls.py +++ b/src/paperless/urls.py @@ -40,6 +40,7 @@ from documents.views import UnifiedSearchViewSet from documents.views import WorkflowActionViewSet from documents.views import WorkflowTriggerViewSet from documents.views import WorkflowViewSet +from documents.views import WarehouseViewSet from paperless.consumers import StatusConsumer from paperless.views import ApplicationConfigurationViewSet from paperless.views import DisconnectSocialAccountView @@ -53,6 +54,7 @@ from paperless_mail.views import MailAccountTestView from paperless_mail.views import MailAccountViewSet from paperless_mail.views import MailRuleViewSet + api_router = DefaultRouter() api_router.register(r"correspondents", CorrespondentViewSet) api_router.register(r"document_types", DocumentTypeViewSet) @@ -72,9 +74,11 @@ api_router.register(r"workflow_actions", WorkflowActionViewSet) api_router.register(r"workflows", WorkflowViewSet) api_router.register(r"custom_fields", CustomFieldViewSet) api_router.register(r"config", ApplicationConfigurationViewSet) +api_router.register(r"warehouses", WarehouseViewSet) urlpatterns = [ + re_path( r"^api/", include(