diff --git a/pdfding/pdf/tests/test_services/test_collection_services.py b/pdfding/pdf/tests/test_services/test_collection_services.py
new file mode 100644
index 00000000..98af7f87
--- /dev/null
+++ b/pdfding/pdf/tests/test_services/test_collection_services.py
@@ -0,0 +1,58 @@
+from unittest import mock
+
+from core.settings import MEDIA_ROOT
+from django.contrib.auth.models import User
+from django.test import TestCase
+from pdf.models.pdf_models import Pdf
+from pdf.models.shared_pdf_models import SharedPdf
+from pdf.services import collection_services
+
+
+class TestCollectionServices(TestCase):
+ def setUp(self) -> None:
+ self.user = User.objects.create_user(username='user', password='12345', email='a@a.com')
+
+ @mock.patch('pdf.services.collection_services.adjust_pdf_path')
+ @mock.patch('pdf.services.collection_services.move')
+ def test_move_collection(self, mock_move, mock_adjust_pdf_path):
+ collection = self.user.profile.current_collection
+ pdf_1 = Pdf.objects.create(collection=collection, name='pdf_1')
+ pdf_2 = Pdf.objects.create(collection=collection, name='pdf_2')
+ assert collection.name == 'Default'
+
+ collection.name = 'NEW_name'
+ collection_services.move_collection(collection)
+
+ assert collection.name == 'NEW_name'
+ mock_move.assert_called_once_with(
+ MEDIA_ROOT / collection.workspace.id / 'default', MEDIA_ROOT / collection.workspace.id / 'new_name'
+ )
+ assert mock_adjust_pdf_path.call_count == 2
+
+ mock_adjust_pdf_path.assert_has_calls(
+ [
+ mock.call(pdf_1, '/default/', '/new_name/'),
+ mock.call(pdf_2, '/default/', '/new_name/'),
+ ],
+ any_order=True,
+ )
+
+ def test_adjust_pdf_path(self):
+ collection = self.user.profile.current_collection
+ pdf = Pdf.objects.create(collection=collection, name='pdf')
+ # make sure only the first occurence is replaced
+ pdf.file.name = '1/old/pdf/old/pdf.pdf'
+ pdf.preview.name = '1/old/previews/old/pdf'
+ pdf.thumbnail.name = '1/old/thumbnails/old/pdf'
+ pdf.save()
+ shared_pdf = SharedPdf.objects.create(pdf=pdf, name='shared_pdf')
+ shared_pdf.file.name = '1/old/qr/old/pdf'
+ shared_pdf.save()
+
+ collection_services.adjust_pdf_path(pdf, '/old/', '/new/')
+ assert pdf.file.name == '1/new/pdf/old/pdf.pdf'
+ assert pdf.thumbnail.name == '1/new/thumbnails/old/pdf'
+ assert pdf.preview.name == '1/new/previews/old/pdf'
+ # do not ask me why we need to fetch the shared pdf and not the normal one
+ changed_shared_pdf = SharedPdf.objects.get(id=shared_pdf.id)
+ assert changed_shared_pdf.file.name == '1/new/qr/old/pdf'
diff --git a/pdfding/pdf/tests/test_views/test_collection_views.py b/pdfding/pdf/tests/test_views/test_collection_views.py
index afa92430..f5715607 100644
--- a/pdfding/pdf/tests/test_views/test_collection_views.py
+++ b/pdfding/pdf/tests/test_views/test_collection_views.py
@@ -1,9 +1,13 @@
from unittest.mock import MagicMock, patch
+import pytest
from django.contrib.auth.models import User
+from django.contrib.messages import get_messages
+from django.http import Http404
from django.test import Client, TestCase
from django.urls import reverse
-from pdf.forms import CollectionForm
+from pdf.forms import CollectionDescriptionForm, CollectionForm, CollectionNameForm
+from pdf.services.workspace_services import create_collection, create_workspace
from pdf.views import collection_views
@@ -17,6 +21,28 @@ def setUp(self):
self.client.login(username=self.username, password=self.password)
+class TestCollectionMixin(CollectionTestCase):
+ def test_get_object(self):
+ # do a dummy request so we can get a request object
+ response = self.client.get(reverse('pdf_overview'))
+
+ other_collection = create_collection(self.user.profile.current_workspace, 'other')
+
+ assert other_collection == collection_views.CollectionMixin.get_object(
+ response.wsgi_request, other_collection.id
+ )
+
+ def test_get_object_other_workspace(self):
+ # do a dummy request so we can get a request object
+ response = self.client.get(reverse('pdf_overview'))
+
+ other_ws = create_workspace('other', self.user)
+ other_collection = create_collection(other_ws, 'other')
+
+ with pytest.raises(Http404, match='Given query not found...'):
+ collection_views.CollectionMixin.get_object(response.wsgi_request, other_collection.id)
+
+
class TestCreateCollectionMixin(CollectionTestCase):
@patch('pdf.views.collection_views.CreateCollectionMixin.form')
def test_get_context_get(self, mock_add_form):
@@ -49,3 +75,47 @@ def test_obj_save(self):
self.assertEqual(created_collection.name, 'some_collection')
self.assertEqual(created_collection.description, 'some_description')
self.assertFalse(created_collection.default_collection)
+
+
+class TestEditCollectionMixin(CollectionTestCase):
+ def test_get_edit_form_get(self):
+ collection = self.user.profile.current_collection
+ edit_collection_mixin_object = collection_views.EditCollectionMixin()
+
+ for field, form_class, field_value in zip(
+ ['name', 'description'],
+ [CollectionNameForm, CollectionDescriptionForm],
+ ['Default', 'Default Collection'],
+ ):
+ form = edit_collection_mixin_object.get_edit_form_get(field, collection)
+ self.assertIsInstance(form, form_class)
+ self.assertEqual(form.initial, {field: field_value})
+
+ @patch('pdf.views.collection_views.move_collection')
+ def test_process_field_name(self, mock_move_collection):
+ # do a dummy request so we can get a request object
+ response = self.client.get(reverse('pdf_overview'))
+ collection = self.user.profile.current_collection
+
+ collection_views.EditCollectionMixin.process_field(
+ 'name', collection, response.wsgi_request, {'name': 'new_name'}
+ )
+ mock_move_collection.assert_called_once_with(collection)
+
+ def test_process_field_name_existing(self):
+ # do a dummy request so we can get a request object
+ response = self.client.get(reverse('pdf_overview'))
+ request = response.wsgi_request
+ collection = self.user.profile.current_collection
+ create_collection(self.user.profile.current_workspace, 'existing')
+
+ collection_views.EditCollectionMixin.process_field(
+ 'name', collection, response.wsgi_request, {'name': 'existing'}
+ )
+
+ messages = get_messages(request)
+
+ self.assertEqual(len(messages), 1)
+ self.assertEqual(
+ list(messages)[0].message, 'This name is already used by another collection in this workspace!'
+ )
diff --git a/pdfding/pdf/tests/test_views/test_pdf_views.py b/pdfding/pdf/tests/test_views/test_pdf_views.py
index 63d7713f..ff0aaf35 100644
--- a/pdfding/pdf/tests/test_views/test_pdf_views.py
+++ b/pdfding/pdf/tests/test_views/test_pdf_views.py
@@ -416,6 +416,8 @@ def test_get_extra_context(self, mock_get_tag_info_dict):
'page': 'pdf_overview',
'layout': 'Compact',
'needs_nagging': False,
+ 'current_collection_id': str(self.user.id),
+ 'current_collection_name': 'Default',
}
self.assertEqual(generated_extra_context, expected_extra_context)
@@ -434,6 +436,8 @@ def test_get_extra_context_selection(self, mock_get_tag_info_dict):
'page': 'pdf_overview_starred',
'layout': 'Compact',
'needs_nagging': False,
+ 'current_collection_id': str(self.user.id),
+ 'current_collection_name': 'Default',
}
self.assertEqual(generated_extra_context, expected_extra_context)
@@ -452,6 +456,8 @@ def test_get_extra_context_selection_invalid(self, mock_get_tag_info_dict):
'page': 'pdf_overview',
'layout': 'Compact',
'needs_nagging': False,
+ 'current_collection_id': str(self.user.id),
+ 'current_collection_name': 'Default',
}
self.assertEqual(generated_extra_context, expected_extra_context)
@@ -470,6 +476,8 @@ def test_get_extra_context_empty_queries(self, mock_get_tag_info_dict):
'page': 'pdf_overview',
'layout': 'Compact',
'needs_nagging': False,
+ 'current_collection_id': str(self.user.id),
+ 'current_collection_name': 'Default',
}
self.assertEqual(generated_extra_context, expected_extra_context)
diff --git a/pdfding/pdf/tests/test_views/test_share_views.py b/pdfding/pdf/tests/test_views/test_share_views.py
index d942048d..1a2ba5a5 100644
--- a/pdfding/pdf/tests/test_views/test_share_views.py
+++ b/pdfding/pdf/tests/test_views/test_share_views.py
@@ -29,6 +29,8 @@
SharedPdfMixin,
)
+from pdfding.pdf.views import share_views
+
def set_up(self):
self.client = Client()
@@ -130,6 +132,19 @@ def test_filter_objects(self):
self.assertEqual(shared_names, ['shared_1', 'shared_2', 'shared_3'])
+ def test_get_extra_context(self):
+ self.client.login(username=self.username, password=self.password)
+ response = self.client.get(reverse('shared_pdf_overview'))
+
+ generated_extra_context = share_views.OverviewMixin.get_extra_context(response.wsgi_request)
+ expected_extra_context = {
+ 'page': 'shared_pdf_overview',
+ 'current_collection_id': str(self.user.id),
+ 'current_collection_name': 'Default',
+ }
+
+ self.assertEqual(generated_extra_context, expected_extra_context)
+
class TestSharedPdfMixin(TestCase):
username = 'user'
diff --git a/pdfding/pdf/tests/test_views/test_workspace_views.py b/pdfding/pdf/tests/test_views/test_workspace_views.py
index 46971bf3..d9e02004 100644
--- a/pdfding/pdf/tests/test_views/test_workspace_views.py
+++ b/pdfding/pdf/tests/test_views/test_workspace_views.py
@@ -66,6 +66,20 @@ def test_get_object(self):
assert ws == workspace_views.WorkspaceMixin.get_object(response.wsgi_request, ws.id)
+class TestCollectionDetails(WorkspaceTestCase):
+ def test_get(self):
+ default_collection = self.user.profile.current_collection
+
+ response = self.client.get(reverse('collection_details', kwargs={'identifier': default_collection.id}))
+
+ self.assertTemplateUsed(response, 'collection_details.html')
+
+ assert response.context['workspace'] == self.user.profile.current_workspace
+ assert response.context['collection'] == default_collection
+ assert response.context['current_collection_id'] == default_collection.id
+ assert response.context['current_collection_name'] == default_collection.name
+
+
class TestEditWorkspaceMixin(WorkspaceTestCase):
def test_get_edit_form_get(self):
ws = self.user.profile.current_workspace
diff --git a/pdfding/pdf/urls.py b/pdfding/pdf/urls.py
index f741d9e5..be20e33c 100644
--- a/pdfding/pdf/urls.py
+++ b/pdfding/pdf/urls.py
@@ -83,10 +83,14 @@
path('edit_tag/', pdf_views.EditTag.as_view(), name='edit_tag'),
# workspace related views
path('workspace/create', workspace_views.Create.as_view(), name='create_workspace'),
+ path('workspace/details', workspace_views.Details.as_view(), name='workspace_details'),
path('workspace/details/
', workspace_views.Details.as_view(), name='workspace_details'),
path('workspace/edit//', workspace_views.Edit.as_view(), name='edit_workspace'),
path('workspace/delete/', workspace_views.Delete.as_view(), name='delete_workspace'),
path('', pdf_views.Overview.as_view(), name='workspace_overview'), # needed for base views working
path('collection/create', collection_views.Create.as_view(), name='create_collection'),
+ path('collection/details/', workspace_views.CollectionDetails.as_view(), name='collection_details'),
+ path('collection/edit//', collection_views.Edit.as_view(), name='edit_collection'),
+ path('collection/delete/', workspace_views.Delete.as_view(), name='delete_collection'),
path('', pdf_views.Overview.as_view(), name='collection_overview'), # needed for base views working
]
diff --git a/pdfding/pdf/views/collection_views.py b/pdfding/pdf/views/collection_views.py
index 81ff0902..8bcc8cdd 100644
--- a/pdfding/pdf/views/collection_views.py
+++ b/pdfding/pdf/views/collection_views.py
@@ -1,6 +1,10 @@
from base import base_views
+from django.contrib import messages
from django.http import HttpRequest
-from pdf.forms import CollectionForm
+from pdf.forms import CollectionDescriptionForm, CollectionForm, CollectionNameForm
+from pdf.models.collection_models import Collection
+from pdf.services.collection_services import move_collection
+from pdf.services.pdf_services import check_object_access_allowed
from pdf.services.workspace_services import create_collection
@@ -30,5 +34,66 @@ def obj_save(cls, form: CollectionForm, request: HttpRequest, _):
create_collection(workspace=workspace, collection_name=form.data['name'], description=form.data['description'])
+class CollectionMixin(BaseCollectionMixin):
+ @staticmethod
+ @check_object_access_allowed
+ def get_object(request: HttpRequest, collection_id: str) -> Collection:
+ """Get the current collection."""
+
+ user_profile = request.user.profile
+ collection = user_profile.collections.get(id=collection_id)
+
+ return collection
+
+
+class EditCollectionMixin(CollectionMixin):
+ fields_requiring_extra_processing = ['name']
+
+ @staticmethod
+ def get_edit_form_dict():
+ """Get the forms of the fields that can be edited as a dict."""
+
+ form_dict = {
+ 'description': CollectionDescriptionForm,
+ 'name': CollectionNameForm,
+ }
+
+ return form_dict
+
+ def get_edit_form_get(self, field_name: str, collection: Collection):
+ """Get the form belonging to the specified field."""
+
+ form_dict = self.get_edit_form_dict()
+
+ initial_dict = {
+ 'name': {'name': collection.name},
+ 'description': {'description': collection.description},
+ }
+
+ form = form_dict[field_name](initial=initial_dict[field_name])
+
+ return form
+
+ @classmethod
+ def process_field(cls, field_name: str, collection: Collection, request: HttpRequest, form_data: dict):
+ """Process fields that are not covered in the base edit view."""
+
+ if field_name == 'name':
+ profile = request.user.profile
+ existing_collection = profile.collections.filter(name__iexact=form_data.get('name').strip()).first()
+
+ if existing_collection and str(existing_collection.id) != str(collection.id):
+ messages.warning(request, 'This name is already used by another collection in this workspace!')
+ else:
+ move_collection(collection)
+
+
class Create(CreateCollectionMixin, base_views.BaseAdd):
"""View for creating new collections."""
+
+
+class Edit(EditCollectionMixin, base_views.BaseDetailsEdit):
+ """
+ The view for editing a collection's name and description. The field, that is to be changed, is specified by the
+ 'field' argument.
+ """
diff --git a/pdfding/pdf/views/pdf_views.py b/pdfding/pdf/views/pdf_views.py
index 83bd94bd..5019815f 100644
--- a/pdfding/pdf/views/pdf_views.py
+++ b/pdfding/pdf/views/pdf_views.py
@@ -222,6 +222,8 @@ def get_extra_context(request: HttpRequest) -> dict:
'special_pdf_selection': special_pdf_selection,
'tag_info_dict': TagServices.get_tag_info_dict(request.user.profile),
'tag_query': tag_query,
+ 'current_collection_id': request.user.profile.current_collection_id,
+ 'current_collection_name': request.user.profile.current_collection_name,
}
return extra_context
diff --git a/pdfding/pdf/views/share_views.py b/pdfding/pdf/views/share_views.py
index 9c8ca04f..3499f82e 100644
--- a/pdfding/pdf/views/share_views.py
+++ b/pdfding/pdf/views/share_views.py
@@ -133,10 +133,14 @@ def filter_objects(request: HttpRequest) -> QuerySet:
return shared_pdfs
@staticmethod
- def get_extra_context(_) -> dict: # pragma: no cover
+ def get_extra_context(request: HttpRequest) -> dict: # pragma: no cover
"""get further information that needs to be passed to the template."""
- return {'page': 'shared_pdf_overview'}
+ return {
+ 'page': 'shared_pdf_overview',
+ 'current_collection_id': request.user.profile.current_collection_id,
+ 'current_collection_name': request.user.profile.current_collection_name,
+ }
class SharedPdfMixin(BaseShareMixin):
diff --git a/pdfding/pdf/views/workspace_views.py b/pdfding/pdf/views/workspace_views.py
index f1d1b30f..b9fef682 100644
--- a/pdfding/pdf/views/workspace_views.py
+++ b/pdfding/pdf/views/workspace_views.py
@@ -2,7 +2,9 @@
from django.http import HttpRequest
from django.shortcuts import redirect, render
from pdf.forms import WorkspaceDescriptionForm, WorkspaceForm, WorkspaceNameForm
+from pdf.models.collection_models import Collection
from pdf.models.workspace_models import Workspace, WorkspaceError
+from pdf.services.pdf_services import check_object_access_allowed
from pdf.services.workspace_services import create_workspace
@@ -39,6 +41,7 @@ def obj_save(cls, form: WorkspaceForm, request: HttpRequest, _):
class WorkspaceMixin(BaseWorkspaceMixin):
@staticmethod
+ @check_object_access_allowed
def get_object(request: HttpRequest, ws_id: str) -> Workspace:
"""Get the current workspace."""
@@ -118,7 +121,7 @@ class Details(WorkspaceMixin, base_views.BaseDetails):
"""View for displaying the details page of a workspace."""
# returns the current workspace, needs to be adjusted in the future
- def get(self, request: HttpRequest, identifier: str): # pragma: no cover
+ def get(self, request: HttpRequest, identifier: str | None = None): # pragma: no cover
"""Display the details page."""
obj = request.user.profile.current_workspace
@@ -127,6 +130,31 @@ def get(self, request: HttpRequest, identifier: str): # pragma: no cover
return render(request, 'workspace_details.html', context)
+class CollectionDetails(WorkspaceMixin, base_views.BaseDetails):
+ """View for displaying the collections page of a workspace."""
+
+ def get(self, request: HttpRequest, identifier: str):
+ """Display the collection page."""
+
+ workspace = request.user.profile.current_workspace
+ collection = self.get_collection(request, identifier=identifier)
+ context = {
+ 'workspace': workspace,
+ 'current_collection_id': identifier,
+ 'collection': collection,
+ 'current_collection_name': collection.name,
+ }
+
+ return render(request, 'collection_details.html', context)
+
+ @staticmethod
+ @check_object_access_allowed
+ def get_collection(request: HttpRequest, identifier: str) -> Collection:
+ collection = request.user.profile.collections.get(id=identifier)
+
+ return collection
+
+
class Edit(EditWorkspaceMixin, base_views.BaseDetailsEdit):
"""
The view for editing a workspace's name and description. The field, that is to be changed, is specified by the
diff --git a/pdfding/users/models.py b/pdfding/users/models.py
index df5fb0fa..ae27d7f3 100644
--- a/pdfding/users/models.py
+++ b/pdfding/users/models.py
@@ -149,6 +149,17 @@ def current_collection(self):
return self.collections.get(id=self.current_collection_id)
+ @property
+ def current_collection_name(self):
+ """Return the name of the current collection"""
+
+ if self.current_collection_id == 'all':
+ current_collection_name = 'All'
+ else:
+ current_collection_name = self.current_collection.name
+
+ return current_collection_name
+
@property
def all_pdfs(self) -> QuerySet:
"""Return all PDFs of all workspaces the user has access to."""
diff --git a/pdfding/users/templates/account_settings.html b/pdfding/users/templates/account_settings.html
index 907d25fb..c2788975 100644
--- a/pdfding/users/templates/account_settings.html
+++ b/pdfding/users/templates/account_settings.html
@@ -5,7 +5,7 @@
{% include 'includes/settings_sidebar.html' with page='account_settings' %}
-
+
diff --git a/pdfding/users/templates/danger_settings.html b/pdfding/users/templates/danger_settings.html
index 301a10d2..6ecb9269 100644
--- a/pdfding/users/templates/danger_settings.html
+++ b/pdfding/users/templates/danger_settings.html
@@ -5,7 +5,7 @@
{% include 'includes/settings_sidebar.html' with page='danger_settings' %}
-
+
diff --git a/pdfding/users/templates/ui_settings.html b/pdfding/users/templates/ui_settings.html
index aeb20f29..a93e1357 100644
--- a/pdfding/users/templates/ui_settings.html
+++ b/pdfding/users/templates/ui_settings.html
@@ -5,7 +5,7 @@
{% include 'includes/settings_sidebar.html' with page='ui_settings' %}
-
+
diff --git a/pdfding/users/templates/viewer_settings.html b/pdfding/users/templates/viewer_settings.html
index dab35428..bb93b789 100644
--- a/pdfding/users/templates/viewer_settings.html
+++ b/pdfding/users/templates/viewer_settings.html
@@ -5,7 +5,7 @@
{% include 'includes/settings_sidebar.html' with page='viewer_settings' %}
-
+
diff --git a/pdfding/users/tests/test_models.py b/pdfding/users/tests/test_models.py
index f98eddda..c3a226d6 100644
--- a/pdfding/users/tests/test_models.py
+++ b/pdfding/users/tests/test_models.py
@@ -189,14 +189,25 @@ def test_current_workspace_property(self):
self.assertEqual(self.user.profile.current_workspace, other_workspace)
def test_current_collection_property(self):
- self.assertEqual(self.user.profile.current_collection.id, str(self.user.id))
-
other_collection = create_collection(self.user.profile.current_workspace, 'other')
self.user.profile.current_collection_id = other_collection.id
self.user.profile.save()
self.assertEqual(other_collection, self.user.profile.current_collection)
+ def test_current_collection_name_property(self):
+ other_collection = create_collection(self.user.profile.current_workspace, 'other')
+ self.user.profile.current_collection_id = other_collection.id
+ self.user.profile.save()
+
+ self.assertEqual('other', self.user.profile.current_collection_name)
+
+ def test_current_collection_name_all_property(self):
+ self.user.profile.current_collection_id = 'all'
+ self.user.profile.save()
+
+ self.assertEqual('All', self.user.profile.current_collection_name)
+
def test_has_access_to_workspace(self):
profile = self.user.profile
other_workspace = create_workspace('other_workspace', self.user)