diff --git a/papermerge/core/alembic/versions/85fda75f19f1_make_document_type_name_unique_for_user_.py b/papermerge/core/alembic/versions/85fda75f19f1_make_document_type_name_unique_for_user_.py new file mode 100644 index 000000000..9e7f0c2a1 --- /dev/null +++ b/papermerge/core/alembic/versions/85fda75f19f1_make_document_type_name_unique_for_user_.py @@ -0,0 +1,31 @@ +"""make document type name unique for user_id + +Revision ID: 85fda75f19f1 +Revises: bc29f69daca4 +Create Date: 2024-11-25 10:03:03.516065 + +""" + +from typing import Sequence, Union + +from alembic import op + + +# revision identifiers, used by Alembic. +revision: str = "85fda75f19f1" +down_revision: Union[str, None] = "bc29f69daca4" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + with op.batch_alter_table("document_types") as batch_op: + batch_op.create_unique_constraint( + "unique document type per user", + columns=["name", "user_id"], + ) + + +def downgrade() -> None: + with op.batch_alter_table("document_types") as batch_op: + batch_op.drop_constraint("unique document type per user") diff --git a/papermerge/core/features/conftest.py b/papermerge/core/features/conftest.py index 24314c2cb..2e2834635 100644 --- a/papermerge/core/features/conftest.py +++ b/papermerge/core/features/conftest.py @@ -444,10 +444,14 @@ def token(): @pytest.fixture -def make_document_type(db_session, user: orm.User, make_custom_field): +def make_document_type(db_session, make_user, make_custom_field): cf = make_custom_field(name="some-random-cf", type=schema.CustomFieldType.boolean) - def _make_document_type(name: str, path_template: str | None = None): + def _make_document_type( + name: str, user: orm.User | None = None, path_template: str | None = None + ): + if user is None: + user = make_user("john") return dbapi.create_document_type( db_session, name=name, diff --git a/papermerge/core/features/document_types/db/orm.py b/papermerge/core/features/document_types/db/orm.py index 21eab2a44..86eea21ae 100644 --- a/papermerge/core/features/document_types/db/orm.py +++ b/papermerge/core/features/document_types/db/orm.py @@ -1,7 +1,7 @@ from datetime import datetime from uuid import UUID -from sqlalchemy import ForeignKey, func +from sqlalchemy import ForeignKey, func, UniqueConstraint from sqlalchemy.orm import Mapped, mapped_column, relationship from papermerge.core.db.base import Base @@ -28,3 +28,7 @@ class DocumentType(Base): ) user_id: Mapped[UUID] = mapped_column(ForeignKey("users.id")) created_at: Mapped[datetime] = mapped_column(insert_default=func.now()) + + __table_args__ = ( + UniqueConstraint("name", "user_id", name="unique document type per user"), + ) diff --git a/papermerge/core/features/document_types/tests/test_router_document_types.py b/papermerge/core/features/document_types/tests/test_router_document_types.py index da825f193..7a6fabd0d 100644 --- a/papermerge/core/features/document_types/tests/test_router_document_types.py +++ b/papermerge/core/features/document_types/tests/test_router_document_types.py @@ -118,11 +118,11 @@ def test_delete_document_type( def test_paginated_result__9_items_first_page( - make_document_type, auth_api_client: AuthTestClient + make_document_type, auth_api_client: AuthTestClient, user ): total_doc_type_items = 9 - for _ in range(total_doc_type_items): - make_document_type(name="Invoice") + for i in range(total_doc_type_items): + make_document_type(name=f"Invoice {i}", user=user) params = {"page_size": 5, "page_number": 1} response = auth_api_client.get("/document-types/", params=params) @@ -138,11 +138,11 @@ def test_paginated_result__9_items_first_page( def test_paginated_result__9_items_second_page( - make_document_type, auth_api_client: AuthTestClient + make_document_type, auth_api_client: AuthTestClient, user ): total_doc_type_items = 9 - for _ in range(total_doc_type_items): - make_document_type(name="Invoice") + for i in range(total_doc_type_items): + make_document_type(name=f"Invoice {i}", user=user) params = {"page_size": 5, "page_number": 2} response = auth_api_client.get("/document-types/", params=params) @@ -160,11 +160,11 @@ def test_paginated_result__9_items_second_page( def test_paginated_result__9_items_3rd_page( - make_document_type, auth_api_client: AuthTestClient + make_document_type, auth_api_client: AuthTestClient, user ): total_doc_type_items = 9 - for _ in range(total_doc_type_items): - make_document_type(name="Invoice") + for i in range(total_doc_type_items): + make_document_type(name=f"Invoice {i}", user=user) params = {"page_size": 5, "page_number": 3} response = auth_api_client.get("/document-types/", params=params) @@ -180,10 +180,12 @@ def test_paginated_result__9_items_3rd_page( assert len(paginated_items.items) == 0 -def test_document_types_all_route(make_document_type, auth_api_client: AuthTestClient): +def test_document_types_all_route( + make_document_type, auth_api_client: AuthTestClient, user +): total_doc_type_items = 9 - for _ in range(total_doc_type_items): - make_document_type(name="Invoice") + for i in range(total_doc_type_items): + make_document_type(name=f"Invoice {i}", user=user) response = auth_api_client.get("/document-types/all") diff --git a/tests/conftest.py b/tests/conftest.py index 5f27c0fe4..08f4c53ca 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -104,10 +104,13 @@ def index(tmp_path, request) -> IndexRW: @pytest.fixture -def make_document_type(db_session: Session, user: User, make_custom_field): +def make_document_type(db_session: Session, make_user, make_custom_field): cf = make_custom_field(name="some-random-cf", type=CustomFieldType.boolean) - def _make_document_type(name: str): + def _make_document_type(name: str, user: orm.User | None = None): + if user is None: + user = make_user("john") + return create_document_type( db_session, name=name,