diff --git a/pypaperless/api.py b/pypaperless/api.py index 2dddb09..f64536b 100644 --- a/pypaperless/api.py +++ b/pypaperless/api.py @@ -184,6 +184,8 @@ async def request( ## pylint: disable=too-many-arguments **kwargs: Any, ) -> AsyncGenerator[aiohttp.ClientResponse, None]: """Perform a request.""" + if method == "post": + pass res = await self._session.request( method, path, diff --git a/pypaperless/models/base.py b/pypaperless/models/base.py index c8fafec..e7ef89d 100644 --- a/pypaperless/models/base.py +++ b/pypaperless/models/base.py @@ -57,6 +57,7 @@ class PaperlessModelProtocol(Protocol): _api_path: str _data: dict[str, Any] _fetched: bool + _params: dict[str, Any] def _get_dataclass_fields(self) -> list[Field]: ... @@ -71,8 +72,9 @@ def __init__(self, api: "Paperless", data: dict[str, Any]): """Initialize a `PaperlessModel` instance.""" super().__init__(api) self._data = {} - self._fetched = False self._data.update(data) + self._fetched = False + self._params: dict[str, Any] = {} @final @classmethod @@ -118,7 +120,7 @@ def _set_dataclass_fields(self) -> None: async def load(self) -> None: """Get `model data` from DRF.""" - data = await self._api.request_json("get", self._api_path) + data = await self._api.request_json("get", self._api_path, params=self._params) self._data.update(data) self._set_dataclass_fields() diff --git a/pypaperless/models/classifiers.py b/pypaperless/models/classifiers.py index d413c69..d8a1615 100644 --- a/pypaperless/models/classifiers.py +++ b/pypaperless/models/classifiers.py @@ -17,7 +17,7 @@ class Correspondent( # pylint: disable=too-many-ancestors PaperlessModel, models.MatchingFieldsMixin, - models.PermissionFieldsMixin, + models.SecurableMixin, models.UpdatableMixin, models.DeletableMixin, ): @@ -39,12 +39,13 @@ def __init__(self, api: "Paperless", data: dict[str, Any]): @dataclass(init=False) -class CorrespondentDraft( +class CorrespondentDraft( # pylint: disable=too-many-ancestors PaperlessModel, models.MatchingFieldsMixin, + models.SecurableDraftMixin, models.CreatableMixin, ): - """Represent a new Paperless `Correspondent`, which is not stored in Paperless.""" + """Represent a new `Correspondent`, which is not yet stored in Paperless.""" _api_path = API_PATH["correspondents"] @@ -56,14 +57,13 @@ class CorrespondentDraft( } name: str | None = None - owner: int | None = None @dataclass(init=False) class DocumentType( # pylint: disable=too-many-ancestors PaperlessModel, models.MatchingFieldsMixin, - models.PermissionFieldsMixin, + models.SecurableMixin, models.UpdatableMixin, models.DeletableMixin, ): @@ -84,12 +84,13 @@ def __init__(self, api: "Paperless", data: dict[str, Any]): @dataclass(init=False) -class DocumentTypeDraft( +class DocumentTypeDraft( # pylint: disable=too-many-ancestors PaperlessModel, models.MatchingFieldsMixin, + models.SecurableDraftMixin, models.CreatableMixin, ): - """Represent a new Paperless `DocumentType`, which is not stored in Paperless.""" + """Represent a new `DocumentType`, which is not yet stored in Paperless.""" _api_path = API_PATH["document_types"] @@ -108,7 +109,7 @@ class DocumentTypeDraft( class StoragePath( # pylint: disable=too-many-ancestors PaperlessModel, models.MatchingFieldsMixin, - models.PermissionFieldsMixin, + models.SecurableMixin, models.UpdatableMixin, models.DeletableMixin, ): @@ -130,12 +131,13 @@ def __init__(self, api: "Paperless", data: dict[str, Any]): @dataclass(init=False) -class StoragePathDraft( +class StoragePathDraft( # pylint: disable=too-many-ancestors PaperlessModel, models.MatchingFieldsMixin, + models.SecurableDraftMixin, models.CreatableMixin, ): - """Represent a new Paperless `StoragePath`, which is not stored in Paperless.""" + """Represent a new `StoragePath`, which is not yet stored in Paperless.""" _api_path = API_PATH["storage_paths"] @@ -156,7 +158,7 @@ class StoragePathDraft( class Tag( # pylint: disable=too-many-ancestors,too-many-instance-attributes PaperlessModel, models.MatchingFieldsMixin, - models.PermissionFieldsMixin, + models.SecurableMixin, models.UpdatableMixin, models.DeletableMixin, ): @@ -180,12 +182,13 @@ def __init__(self, api: "Paperless", data: dict[str, Any]): @dataclass(init=False) -class TagDraft( +class TagDraft( # pylint: disable=too-many-ancestors PaperlessModel, models.MatchingFieldsMixin, + models.SecurableDraftMixin, models.CreatableMixin, ): - """Represent a new Paperless `Tag`, which is not stored in Paperless.""" + """Represent a new `Tag`, which is not yet stored in Paperless.""" _api_path = API_PATH["tags"] @@ -208,6 +211,7 @@ class TagDraft( class CorrespondentHelper( # pylint: disable=too-many-ancestors HelperBase[Correspondent], + helpers.SecurableMixin, helpers.CallableMixin[Correspondent], helpers.DraftableMixin[CorrespondentDraft], helpers.IterableMixin[Correspondent], @@ -223,6 +227,7 @@ class CorrespondentHelper( # pylint: disable=too-many-ancestors class DocumentTypeHelper( # pylint: disable=too-many-ancestors HelperBase[DocumentType], + helpers.SecurableMixin, helpers.CallableMixin[DocumentType], helpers.DraftableMixin[DocumentTypeDraft], helpers.IterableMixin[DocumentType], @@ -238,6 +243,7 @@ class DocumentTypeHelper( # pylint: disable=too-many-ancestors class StoragePathHelper( # pylint: disable=too-many-ancestors HelperBase[StoragePath], + helpers.SecurableMixin, helpers.CallableMixin[StoragePath], helpers.DraftableMixin[StoragePathDraft], helpers.IterableMixin[StoragePath], @@ -253,6 +259,7 @@ class StoragePathHelper( # pylint: disable=too-many-ancestors class TagHelper( # pylint: disable=too-many-ancestors HelperBase[Tag], + helpers.SecurableMixin, helpers.CallableMixin[Tag], helpers.DraftableMixin[TagDraft], helpers.IterableMixin[Tag], diff --git a/pypaperless/models/common.py b/pypaperless/models/common.py index 4dc2110..70bd986 100644 --- a/pypaperless/models/common.py +++ b/pypaperless/models/common.py @@ -1,6 +1,6 @@ """PyPaperless common types.""" -from dataclasses import dataclass +from dataclasses import dataclass, field from enum import Enum, StrEnum from typing import Any @@ -75,6 +75,24 @@ def _missing_(cls: type, value: object) -> "MatchingAlgorithmType": # noqa ARG0 return MatchingAlgorithmType.UNKNOWN +# mixins/models/securable +@dataclass(kw_only=True) +class PermissionSetType: + """Represent a Paperless permission set.""" + + users: list[int] = field(default_factory=list) + groups: list[int] = field(default_factory=list) + + +# mixins/models/securable +@dataclass(kw_only=True) +class PermissionTableType: + """Represent a Paperless permissions type.""" + + view: PermissionSetType = field(default_factory=PermissionSetType) + change: PermissionSetType = field(default_factory=PermissionSetType) + + # documents class RetrieveFileMode(StrEnum): """Represent a subtype of `DownloadedDocument`.""" diff --git a/pypaperless/models/documents.py b/pypaperless/models/documents.py index 1362778..0d9185d 100644 --- a/pypaperless/models/documents.py +++ b/pypaperless/models/documents.py @@ -25,7 +25,7 @@ @dataclass(init=False) class Document( # pylint: disable=too-many-instance-attributes, too-many-ancestors PaperlessModel, - models.PermissionFieldsMixin, + models.SecurableMixin, models.UpdatableMixin, models.DeletableMixin, ): @@ -461,6 +461,7 @@ def draft(self, pk: int | None = None, **kwargs: Any) -> DocumentNoteDraft: class DocumentHelper( # pylint: disable=too-many-ancestors HelperBase[Document], + helpers.SecurableMixin, helpers.CallableMixin[Document], helpers.DraftableMixin[DocumentDraft], helpers.IterableMixin[Document], diff --git a/pypaperless/models/mails.py b/pypaperless/models/mails.py index 9a264a5..1a6a7cc 100644 --- a/pypaperless/models/mails.py +++ b/pypaperless/models/mails.py @@ -15,7 +15,7 @@ @dataclass(init=False) class MailAccount( PaperlessModel, - models.PermissionFieldsMixin, + models.SecurableMixin, ): # pylint: disable=too-many-instance-attributes """Represent a Paperless `MailAccount`.""" @@ -42,7 +42,7 @@ def __init__(self, api: "Paperless", data: dict[str, Any]): @dataclass(init=False) class MailRule( PaperlessModel, - models.PermissionFieldsMixin, + models.SecurableMixin, ): # pylint: disable=too-many-instance-attributes """Represent a Paperless `MailRule`.""" @@ -78,10 +78,11 @@ def __init__(self, api: "Paperless", data: dict[str, Any]): self._api_path = self._api_path.format(pk=data.get("id")) -class MailAccountHelper( +class MailAccountHelper( # pylint: disable=too-many-ancestors HelperBase[MailAccount], helpers.CallableMixin[MailAccount], helpers.IterableMixin[MailAccount], + helpers.SecurableMixin, ): """Represent a factory for Paperless `MailAccount` models.""" @@ -91,10 +92,11 @@ class MailAccountHelper( _resource_cls = MailAccount -class MailRuleHelper( +class MailRuleHelper( # pylint: disable=too-many-ancestors HelperBase[MailRule], helpers.CallableMixin[MailRule], helpers.IterableMixin[MailRule], + helpers.SecurableMixin, ): """Represent a factory for Paperless `MailRule` models.""" diff --git a/pypaperless/models/mixins/helpers/__init__.py b/pypaperless/models/mixins/helpers/__init__.py index bd68bc1..0608cad 100644 --- a/pypaperless/models/mixins/helpers/__init__.py +++ b/pypaperless/models/mixins/helpers/__init__.py @@ -3,9 +3,11 @@ from .callable import CallableMixin from .draftable import DraftableMixin from .iterable import IterableMixin +from .securable import SecurableMixin __all__ = ( "CallableMixin", "DraftableMixin", "IterableMixin", + "SecurableMixin", ) diff --git a/pypaperless/models/mixins/helpers/callable.py b/pypaperless/models/mixins/helpers/callable.py index f0ef0d7..f8bf6d3 100644 --- a/pypaperless/models/mixins/helpers/callable.py +++ b/pypaperless/models/mixins/helpers/callable.py @@ -2,6 +2,8 @@ from pypaperless.models.base import HelperProtocol, ResourceT +from .securable import SecurableMixin + class CallableMixin(HelperProtocol[ResourceT]): # pylint: disable=too-few-public-methods """Provide methods for calling a specific resource item.""" @@ -26,6 +28,11 @@ async def __call__( "id": pk, } item = self._resource_cls.create_with_data(self._api, data) + + # set requesting full permissions + if SecurableMixin in type(self).__bases__ and getattr(self, "_request_full_perms", False): + item._params.update({"full_perms": "true"}) # pylint: disable=protected-access + if not lazy: await item.load() return item diff --git a/pypaperless/models/mixins/helpers/draftable.py b/pypaperless/models/mixins/helpers/draftable.py index 62894bf..7345243 100644 --- a/pypaperless/models/mixins/helpers/draftable.py +++ b/pypaperless/models/mixins/helpers/draftable.py @@ -24,4 +24,6 @@ def draft(self, **kwargs: Any) -> ResourceT: raise DraftNotSupported("Helper class has no _draft_cls attribute.") kwargs.update({"id": -1}) - return self._draft_cls.create_with_data(self._api, data=kwargs, fetched=True) + item = self._draft_cls.create_with_data(self._api, data=kwargs, fetched=True) + + return item diff --git a/pypaperless/models/mixins/helpers/securable.py b/pypaperless/models/mixins/helpers/securable.py new file mode 100644 index 0000000..5d7ffdf --- /dev/null +++ b/pypaperless/models/mixins/helpers/securable.py @@ -0,0 +1,23 @@ +"""SecurableMixin for PyPaperless helpers.""" + + +class SecurableMixin: # pylint: disable=too-few-public-methods + """Provide the `request_full_permissions` property for PyPaperless helpers.""" + + _request_full_perms: bool = False + + @property + def request_permissions(self) -> bool: + """Return whether the helper requests items with the `permissions` table, or not. + + Documentation: https://docs.paperless-ngx.com/api/#permissions + """ + return self._request_full_perms + + @request_permissions.setter + def request_permissions(self, value: bool) -> None: + """Set whether the helper requests items with the `permissions` table, or not. + + Documentation: https://docs.paperless-ngx.com/api/#permissions + """ + self._request_full_perms = value diff --git a/pypaperless/models/mixins/models/__init__.py b/pypaperless/models/mixins/models/__init__.py index 6d3d1ac..2b5b157 100644 --- a/pypaperless/models/mixins/models/__init__.py +++ b/pypaperless/models/mixins/models/__init__.py @@ -1,14 +1,17 @@ """Mixins for PyPaperless models.""" from .creatable import CreatableMixin -from .data_fields import MatchingFieldsMixin, PermissionFieldsMixin +from .data_fields import MatchingFieldsMixin # , PermissionFieldsMixin from .deletable import DeletableMixin +from .securable import SecurableDraftMixin, SecurableMixin from .updatable import UpdatableMixin __all__ = ( "CreatableMixin", "DeletableMixin", "MatchingFieldsMixin", - "PermissionFieldsMixin", + # "PermissionFieldsMixin", "UpdatableMixin", + "SecurableMixin", + "SecurableDraftMixin", ) diff --git a/pypaperless/models/mixins/models/creatable.py b/pypaperless/models/mixins/models/creatable.py index 88d8465..969243b 100644 --- a/pypaperless/models/mixins/models/creatable.py +++ b/pypaperless/models/mixins/models/creatable.py @@ -28,17 +28,6 @@ async def save(self) -> int | str | tuple[int, int]: """ self.validate() kwdict = self._serialize() - # in case of DocumentDraft, we want to transmit data as form - # this is kind of dirty, but should do its job in this case - # as_form = type(self).__name__ == "DocumentDraft" - # kwargs = { - # "form" - # if as_form - # else "json": { - # field.name: object_to_dict_value(getattr(self, field.name)) - # for field in self._get_dataclass_fields() - # } - # } res = await self._api.request_json("post", self._api_path, **kwdict) if type(self).__name__ == "DocumentNoteDraft": @@ -56,8 +45,12 @@ def _serialize(self) -> dict[str, Any]: "json": { field.name: object_to_dict_value(getattr(self, field.name)) for field in self._get_dataclass_fields() - } + }, } + # check for empty permissions as they will raise if None + if "set_permissions" in data["json"] and data["json"]["set_permissions"] is None: + del data["json"]["set_permissions"] + return data def validate(self) -> None: @@ -67,5 +60,5 @@ def validate(self) -> None: if len(missing) == 0: return raise DraftFieldRequired( - f"Missing fields for saving a `{self.__class__.__name__}`: {', '.join(missing)}." + f"Missing fields for saving a `{type(self).__name__}`: {', '.join(missing)}." ) diff --git a/pypaperless/models/mixins/models/data_fields.py b/pypaperless/models/mixins/models/data_fields.py index 0da076b..b55e4fa 100644 --- a/pypaperless/models/mixins/models/data_fields.py +++ b/pypaperless/models/mixins/models/data_fields.py @@ -12,11 +12,3 @@ class MatchingFieldsMixin: match: str | None = None matching_algorithm: MatchingAlgorithmType | None = None is_insensitive: bool | None = None - - -@dataclass -class PermissionFieldsMixin: - """Provide shared owner fields for PyPaperless models.""" - - owner: int | None = None - user_can_change: bool | None = None diff --git a/pypaperless/models/mixins/models/securable.py b/pypaperless/models/mixins/models/securable.py new file mode 100644 index 0000000..ae9a6bd --- /dev/null +++ b/pypaperless/models/mixins/models/securable.py @@ -0,0 +1,27 @@ +"""SecurableMixin for PyPaperless models.""" + +from dataclasses import dataclass + +from pypaperless.models.common import PermissionTableType + + +@dataclass(kw_only=True) +class SecurableMixin: + """Provide permission fields for PyPaperless models.""" + + owner: int | None = None + user_can_change: bool | None = None + permissions: PermissionTableType | None = None + + @property + def has_permissions(self) -> bool: + """Return if the model data includes the permission field.""" + return self.permissions is not None + + +@dataclass(kw_only=True) +class SecurableDraftMixin: + """Provide permission fields for PyPaperless draft models.""" + + owner: int | None = None + set_permissions: PermissionTableType | None = None diff --git a/pypaperless/models/mixins/models/updatable.py b/pypaperless/models/mixins/models/updatable.py index aa0a748..9b43e66 100644 --- a/pypaperless/models/mixins/models/updatable.py +++ b/pypaperless/models/mixins/models/updatable.py @@ -52,6 +52,7 @@ async def _patch_fields(self) -> bool: "patch", self._api_path, json=changed, + params=self._params, ) return True @@ -61,5 +62,10 @@ async def _put_fields(self) -> bool: field.name: object_to_dict_value(getattr(self, field.name)) for field in self._get_dataclass_fields() } - self._data = await self._api.request_json("put", self._api_path, json=data) + self._data = await self._api.request_json( + "put", + self._api_path, + json=data, + params=self._params, + ) return True diff --git a/pypaperless/models/saved_views.py b/pypaperless/models/saved_views.py index 016708a..5f432fa 100644 --- a/pypaperless/models/saved_views.py +++ b/pypaperless/models/saved_views.py @@ -16,7 +16,7 @@ @dataclass(init=False) class SavedView( PaperlessModel, - models.PermissionFieldsMixin, + models.SecurableMixin, ): # pylint: disable=too-many-instance-attributes """Represent a Paperless `SavedView`.""" @@ -37,10 +37,11 @@ def __init__(self, api: "Paperless", data: dict[str, Any]): self._api_path = self._api_path.format(pk=data.get("id")) -class SavedViewHelper( +class SavedViewHelper( # pylint: disable=too-many-ancestors HelperBase[SavedView], helpers.CallableMixin[SavedView], helpers.IterableMixin[SavedView], + helpers.SecurableMixin, ): """Represent a factory for Paperless `SavedView` models.""" diff --git a/pypaperless/sessions.py b/pypaperless/sessions.py index 2e9ce0a..e657956 100644 --- a/pypaperless/sessions.py +++ b/pypaperless/sessions.py @@ -141,9 +141,6 @@ async def request( # pylint: disable=too-many-arguments # add base path url = f"{self._base_url}{path}" if not path.startswith("http") else path - # check for trailing slash - if URL(url).query_string == "": - url = url.rstrip("/") + "/" try: return await self._session.request( diff --git a/tests/data/v0_0_0/__init__.py b/tests/data/v0_0_0/__init__.py index 48298de..6fe4b12 100644 --- a/tests/data/v0_0_0/__init__.py +++ b/tests/data/v0_0_0/__init__.py @@ -20,6 +20,17 @@ "mail_rules": f"{PAPERLESS_TEST_URL}/api/mail_rules/", } +V0_0_0_OBJECT_PERMISSIONS = { + "view": { + "users": [1, 2], + "groups": [], + }, + "change": { + "users": [], + "groups": [1], + }, +} + V0_0_0_CORRESPONDENTS = { "count": 5, "next": None, diff --git a/tests/test_paperless_0_0_0.py b/tests/test_paperless_0_0_0.py index 2d0ada8..ec746aa 100644 --- a/tests/test_paperless_0_0_0.py +++ b/tests/test_paperless_0_0_0.py @@ -12,7 +12,7 @@ ) from pypaperless.models import DocumentMeta, Page from pypaperless.models import documents as doc_helpers -from pypaperless.models.common import DocumentMetadataType, RetrieveFileMode +from pypaperless.models.common import DocumentMetadataType, PermissionTableType, RetrieveFileMode from pypaperless.models.documents import DocumentSuggestions, DownloadedDocument from pypaperless.models.mixins import helpers as helper_mixins from pypaperless.models.mixins import models as model_mixins @@ -89,14 +89,17 @@ async def test_helper(self, p: Paperless, mapping: ResourceTestMapping): assert helper_mixins.CallableMixin in mapping.helper_cls.__bases__ assert helper_mixins.DraftableMixin in mapping.helper_cls.__bases__ assert helper_mixins.IterableMixin in mapping.helper_cls.__bases__ + assert helper_mixins.SecurableMixin in mapping.helper_cls.__bases__ async def test_model(self, mapping: ResourceTestMapping): """Test model.""" assert model_mixins.DeletableMixin in mapping.model_cls.__bases__ assert model_mixins.MatchingFieldsMixin in mapping.model_cls.__bases__ - assert model_mixins.PermissionFieldsMixin in mapping.model_cls.__bases__ + assert model_mixins.SecurableMixin in mapping.model_cls.__bases__ assert model_mixins.UpdatableMixin in mapping.model_cls.__bases__ + # draft assert model_mixins.CreatableMixin in mapping.draft_cls.__bases__ + assert model_mixins.SecurableDraftMixin in mapping.draft_cls.__bases__ async def test_pages(self, p: Paperless, mapping: ResourceTestMapping): """Test pages.""" @@ -159,6 +162,17 @@ async def test_delete(self, p: Paperless, mapping: ResourceTestMapping): with pytest.raises(RequestException): await getattr(p, mapping.resource)(5) + async def test_permissions(self, p: Paperless, mapping: ResourceTestMapping): + """Test permissions.""" + getattr(p, mapping.resource).request_permissions = True + item = await getattr(p, mapping.resource)(1) + assert item.has_permissions + assert isinstance(item.permissions, PermissionTableType) + # check disabling again + getattr(p, mapping.resource).request_permissions = False + item = await getattr(p, mapping.resource)(1) + assert not item.has_permissions + @pytest.mark.parametrize( "mapping", @@ -179,13 +193,19 @@ async def test_helper(self, p: Paperless, mapping: ResourceTestMapping): assert helper_mixins.DraftableMixin not in mapping.helper_cls.__bases__ assert helper_mixins.IterableMixin in mapping.helper_cls.__bases__ + perms = helper_mixins.SecurableMixin in mapping.helper_cls.__bases__ + if mapping.resource in (PaperlessResource.GROUPS, PaperlessResource.USERS): + assert not perms + else: + assert perms + async def test_model(self, mapping: ResourceTestMapping): """Test model.""" assert model_mixins.DeletableMixin not in mapping.model_cls.__bases__ assert model_mixins.MatchingFieldsMixin not in mapping.model_cls.__bases__ assert model_mixins.UpdatableMixin not in mapping.model_cls.__bases__ - perms = model_mixins.PermissionFieldsMixin in mapping.model_cls.__bases__ + perms = model_mixins.SecurableMixin in mapping.model_cls.__bases__ if mapping.resource in (PaperlessResource.GROUPS, PaperlessResource.USERS): assert not perms else: @@ -230,12 +250,13 @@ async def test_helper(self, p: Paperless, mapping: ResourceTestMapping): assert helper_mixins.CallableMixin not in mapping.helper_cls.__bases__ assert helper_mixins.DraftableMixin not in mapping.helper_cls.__bases__ assert helper_mixins.IterableMixin not in mapping.helper_cls.__bases__ + assert helper_mixins.SecurableMixin not in mapping.helper_cls.__bases__ async def test_model(self, mapping: ResourceTestMapping): """Test model.""" assert model_mixins.DeletableMixin not in mapping.model_cls.__bases__ assert model_mixins.MatchingFieldsMixin not in mapping.model_cls.__bases__ - assert model_mixins.PermissionFieldsMixin not in mapping.model_cls.__bases__ + assert model_mixins.SecurableMixin not in mapping.model_cls.__bases__ assert model_mixins.UpdatableMixin not in mapping.model_cls.__bases__ async def test_iter(self, p: Paperless, mapping: ResourceTestMapping): @@ -277,6 +298,7 @@ async def test_helper(self, p: Paperless, mapping: ResourceTestMapping): assert helper_mixins.CallableMixin in mapping.helper_cls.__bases__ assert helper_mixins.DraftableMixin in mapping.helper_cls.__bases__ assert helper_mixins.IterableMixin in mapping.helper_cls.__bases__ + assert helper_mixins.SecurableMixin in mapping.helper_cls.__bases__ # test sub helpers assert isinstance(p.documents.download, doc_helpers.DocumentFileDownloadHelper) assert isinstance(p.documents.metadata, doc_helpers.DocumentMetaHelper) @@ -287,9 +309,11 @@ async def test_model(self, mapping: ResourceTestMapping): """Test model.""" assert model_mixins.DeletableMixin in mapping.model_cls.__bases__ assert model_mixins.MatchingFieldsMixin not in mapping.model_cls.__bases__ - assert model_mixins.PermissionFieldsMixin in mapping.model_cls.__bases__ + assert model_mixins.SecurableMixin in mapping.model_cls.__bases__ assert model_mixins.UpdatableMixin in mapping.model_cls.__bases__ + # draft assert model_mixins.CreatableMixin in mapping.draft_cls.__bases__ + assert model_mixins.SecurableDraftMixin not in mapping.draft_cls.__bases__ async def test_pages(self, p: Paperless, mapping: ResourceTestMapping): """Test pages.""" diff --git a/tests/test_paperless_1_17_0.py b/tests/test_paperless_1_17_0.py index 9fe1033..90632b1 100644 --- a/tests/test_paperless_1_17_0.py +++ b/tests/test_paperless_1_17_0.py @@ -71,7 +71,7 @@ async def test_model( """Test model.""" assert model_mixins.DeletableMixin not in DocumentNote.__bases__ assert model_mixins.MatchingFieldsMixin not in DocumentNote.__bases__ - assert model_mixins.PermissionFieldsMixin not in DocumentNote.__bases__ + assert model_mixins.SecurableMixin not in DocumentNote.__bases__ assert model_mixins.UpdatableMixin not in DocumentNote.__bases__ assert model_mixins.CreatableMixin in DocumentNoteDraft.__bases__ diff --git a/tests/test_paperless_1_8_0.py b/tests/test_paperless_1_8_0.py index 84b8d30..33a9ee9 100644 --- a/tests/test_paperless_1_8_0.py +++ b/tests/test_paperless_1_8_0.py @@ -71,9 +71,11 @@ async def test_model(self, mapping: ResourceTestMapping): """Test model.""" assert model_mixins.DeletableMixin in mapping.model_cls.__bases__ assert model_mixins.MatchingFieldsMixin in mapping.model_cls.__bases__ - assert model_mixins.PermissionFieldsMixin in mapping.model_cls.__bases__ + assert model_mixins.SecurableMixin in mapping.model_cls.__bases__ assert model_mixins.UpdatableMixin in mapping.model_cls.__bases__ + # draft assert model_mixins.CreatableMixin in mapping.draft_cls.__bases__ + assert model_mixins.SecurableDraftMixin in mapping.draft_cls.__bases__ async def test_pages(self, p: Paperless, mapping: ResourceTestMapping): """Test pages.""" diff --git a/tests/test_paperless_2_0_0.py b/tests/test_paperless_2_0_0.py index 6f73e63..fed2179 100644 --- a/tests/test_paperless_2_0_0.py +++ b/tests/test_paperless_2_0_0.py @@ -67,14 +67,17 @@ async def test_helper(self, p: Paperless, mapping: ResourceTestMapping): assert helper_mixins.CallableMixin in mapping.helper_cls.__bases__ assert helper_mixins.DraftableMixin in mapping.helper_cls.__bases__ assert helper_mixins.IterableMixin in mapping.helper_cls.__bases__ + assert helper_mixins.SecurableMixin not in mapping.helper_cls.__bases__ async def test_model(self, mapping: ResourceTestMapping): """Test model.""" assert model_mixins.DeletableMixin in mapping.model_cls.__bases__ assert model_mixins.MatchingFieldsMixin not in mapping.model_cls.__bases__ - assert model_mixins.PermissionFieldsMixin not in mapping.model_cls.__bases__ + assert model_mixins.SecurableMixin not in mapping.model_cls.__bases__ assert model_mixins.UpdatableMixin in mapping.model_cls.__bases__ + # draft assert model_mixins.CreatableMixin in mapping.draft_cls.__bases__ + assert model_mixins.SecurableDraftMixin not in mapping.draft_cls.__bases__ async def test_pages(self, p: Paperless, mapping: ResourceTestMapping): """Test pages.""" @@ -150,7 +153,7 @@ async def test_model(self, mapping: ResourceTestMapping): """Test model.""" assert model_mixins.DeletableMixin not in mapping.model_cls.__bases__ assert model_mixins.MatchingFieldsMixin not in mapping.model_cls.__bases__ - assert model_mixins.PermissionFieldsMixin not in mapping.model_cls.__bases__ + assert model_mixins.SecurableMixin not in mapping.model_cls.__bases__ assert model_mixins.UpdatableMixin not in mapping.model_cls.__bases__ async def test_call(self, p: Paperless, mapping: ResourceTestMapping): diff --git a/tests/test_paperless_2_3_0.py b/tests/test_paperless_2_3_0.py index 22b2a29..a3057a5 100644 --- a/tests/test_paperless_2_3_0.py +++ b/tests/test_paperless_2_3_0.py @@ -67,6 +67,7 @@ async def test_helper(self, p: Paperless, mapping: ResourceTestMapping): assert helper_mixins.CallableMixin in mapping.helper_cls.__bases__ assert helper_mixins.DraftableMixin not in mapping.helper_cls.__bases__ assert helper_mixins.IterableMixin in mapping.helper_cls.__bases__ + assert helper_mixins.SecurableMixin not in mapping.helper_cls.__bases__ # test sub helpers assert isinstance(p.workflows.actions, wf_helpers.WorkflowActionHelper) assert isinstance(p.workflows.triggers, wf_helpers.WorkflowTriggerHelper) @@ -79,7 +80,7 @@ async def test_model(self, mapping: ResourceTestMapping): WorkflowTrigger, ): assert model_mixins.DeletableMixin not in model_cls.__bases__ - assert model_mixins.PermissionFieldsMixin not in model_cls.__bases__ + assert model_mixins.SecurableMixin not in model_cls.__bases__ assert model_mixins.UpdatableMixin not in model_cls.__bases__ matching = model_mixins.MatchingFieldsMixin in model_cls.__bases__ diff --git a/tests/util/router.py b/tests/util/router.py index fba676c..85af402 100644 --- a/tests/util/router.py +++ b/tests/util/router.py @@ -3,6 +3,7 @@ import datetime import random import uuid +from copy import deepcopy from aiohttp.web_exceptions import HTTPBadRequest, HTTPNotFound from fastapi import FastAPI, Request, Response @@ -17,6 +18,7 @@ V0_0_0_GROUPS, V0_0_0_MAIL_ACCOUNTS, V0_0_0_MAIL_RULES, + V0_0_0_OBJECT_PERMISSIONS, V0_0_0_PATHS, V0_0_0_SAVED_VIEWS, V0_0_0_TAGS, @@ -54,6 +56,7 @@ "GROUPS": V0_0_0_GROUPS, "MAIL_ACCOUNTS": V0_0_0_MAIL_ACCOUNTS, "MAIL_RULES": V0_0_0_MAIL_RULES, + "OBJECT_PERMISSIONS": V0_0_0_OBJECT_PERMISSIONS, "SAVED_VIEWS": V0_0_0_SAVED_VIEWS, "TAGS": V0_0_0_TAGS, "TASKS": V0_0_0_TASKS, @@ -282,10 +285,11 @@ async def get_resource(req: Request, resource: str): data = _api_switcher(req, f"{resource}_search".upper()) return data - if resource == "tasks": - return {} + data = deepcopy(_api_switcher(req, resource.upper())) + + if req.query_params.get("full_perms", "false") == "true": + data["permissions"] = _api_switcher(req, "OBJECT_PERMISSIONS") - data = _api_switcher(req, resource.upper()) return data @@ -308,7 +312,10 @@ async def get_resource_item(req: Request, resource: str, item: int): iterable = data["results"] if isinstance(data, dict) else data for result in iterable: if result["id"] == item: - return result + found = deepcopy(result) + if req.query_params.get("full_perms", "false") == "true": + found["permissions"] = _api_switcher(req, "OBJECT_PERMISSIONS") + return found raise HTTPNotFound()