Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions pdfding/e2e/test_collection_e2e.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from django.contrib.auth.models import User
from django.urls import reverse
from helpers import PdfDingE2ETestCase
from pdf.models.collection_models import Collection
from pdf.services.workspace_services import create_collection
from playwright.sync_api import expect, sync_playwright

Expand Down Expand Up @@ -75,3 +76,33 @@ def test_details_change_collection(self):
self.page.locator("#current_collection_name").click()
self.page.get_by_text("other_collection").click()
expect(self.page.locator("#current_collection_name")).to_contain_text("other_collection")

def test_cancel_delete(self):
other_collection = create_collection(self.user.profile.current_workspace, 'other_collection')

with sync_playwright() as p:
self.open(reverse('collection_details', kwargs={'identifier': other_collection.id}), p)

expect(self.page.locator("#delete_collection_modal").first).not_to_be_visible()
self.page.locator("#delete-collection").click()
expect(self.page.locator("#delete_collection_modal").first).to_be_visible()
self.page.locator("#cancel_delete").get_by_text("Cancel").click()
expect(self.page.locator("#delete_collection_modal").first).not_to_be_visible()

def test_delete(self):
other_collection = create_collection(self.user.profile.current_workspace, 'other_collection')

with sync_playwright() as p:
self.open(reverse('collection_details', kwargs={'identifier': other_collection.id}), p)

self.page.locator("#delete-collection").click()
self.page.locator("#confirm_delete").get_by_text("Submit").click()
expect(self.page.locator("#delete_collection_modal").first).not_to_be_visible()

assert not Collection.objects.filter(id=other_collection.id).count()

def test_delete_not_visible_default(self):
with sync_playwright() as p:
self.open(reverse('collection_details', kwargs={'identifier': self.user.profile.current_collection_id}), p)

expect(self.page.locator("#delete-collection").first).not_to_be_visible()
1 change: 0 additions & 1 deletion pdfding/e2e/test_workspace_e2e.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@ def test_cancel_delete(self):
self.user.profile.save()

with sync_playwright() as p:
# only display one pdf
self.open(reverse('workspace_details'), p)

expect(self.page.locator("#delete_workspace_modal").first).not_to_be_visible()
Expand Down
19 changes: 19 additions & 0 deletions pdfding/pdf/models/collection_models.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
from shutil import rmtree
from uuid import uuid4

from django.db import models
from pdf.models.helpers import get_collection_path
from pdf.models.workspace_models import Workspace


def get_uuid4_str() -> str:
return str(uuid4())


class CollectionError(Exception):
"""Exceptions for collection related problems"""


class Collection(models.Model):
"""The model for the collections used for organizing PDF files."""

Expand All @@ -21,6 +27,19 @@ class Collection(models.Model):
def __str__(self): # pragma: no cover
return str(self.name)

def delete(self, *args, **kwargs) -> None:
"""
Override default delete method so that the collection directory gets deleted after the collection is deleted.
"""

collection_path = get_collection_path(self)
super().delete(*args, **kwargs)

try:
rmtree(collection_path)
except Exception: # pragma: no cover # nosec B110
pass

@property
def pdfs(self) -> models.QuerySet:
"""Get the pdfs of the collection"""
Expand Down
27 changes: 27 additions & 0 deletions pdfding/pdf/models/helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from pathlib import Path

from core.settings import MEDIA_ROOT


def get_workspace_path(workspace) -> Path: # pragma: no cover
"""Get the path of a workspace."""

return MEDIA_ROOT / get_workspace_dir(workspace)


def get_collection_path(collection) -> Path: # pragma: no cover
"""Get the path of a collection."""

return MEDIA_ROOT / get_collection_dir(collection)


def get_workspace_dir(workspace) -> str:
"""Get the directory of a workspace."""

return workspace.id


def get_collection_dir(collection) -> str:
"""Get the directory of a collection."""

return f'{get_workspace_dir(collection.workspace)}/{collection.name.lower()}'
15 changes: 4 additions & 11 deletions pdfding/pdf/models/pdf_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,11 @@
from django.utils.safestring import mark_safe
from django.utils.text import slugify
from pdf.models.collection_models import Collection
from pdf.models.helpers import get_collection_dir
from pdf.models.tag_models import Tag
from pdf.models.workspace_models import Workspace


def get_pdf_parent_dirs(instance) -> str:
"""Get the parent directories (worskpace_id/collection_name) of a PDF file."""

parent_dirs = f'{instance.workspace.id}/{instance.collection.name.lower()}'

return parent_dirs


def get_file_path(instance, _) -> str:
"""
Get the file path for a PDF inside the media root based on the pdf name. File paths are
Expand Down Expand Up @@ -51,7 +44,7 @@ def get_file_path(instance, _) -> str:
else:
file_path = '/'.join(['pdf', file_name])

file_path = f'{get_pdf_parent_dirs(instance)}/{file_path}'
file_path = f'{get_collection_dir(instance.collection)}/{file_path}'
existing_pdf = Pdf.objects.filter(file=file_path).first()

# make sure there each file path is unique
Expand Down Expand Up @@ -87,7 +80,7 @@ def get_thumbnail_path(instance, _) -> str:
"""Get the file path for the thumbnail of a PDF."""

file_name = f'thumbnails/{instance.id}.png'
file_path = f'{get_pdf_parent_dirs(instance)}/{file_name}'
file_path = f'{get_collection_dir(instance.collection)}/{file_name}'

return file_path

Expand All @@ -96,7 +89,7 @@ def get_preview_path(instance, _) -> str:
"""Get the file path for the preview of a PDF."""

file_name = f'previews/{instance.id}.png'
file_path = f'{get_pdf_parent_dirs(instance)}/{file_name}'
file_path = f'{get_collection_dir(instance.collection)}/{file_name}'

return file_path

Expand Down
5 changes: 3 additions & 2 deletions pdfding/pdf/models/shared_pdf_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@

from django.contrib.humanize.templatetags.humanize import naturaltime
from django.db import models
from pdf.models.pdf_models import Pdf, get_pdf_parent_dirs
from pdf.models.helpers import get_collection_dir
from pdf.models.pdf_models import Pdf


def get_qrcode_file_path(instance, _) -> str:
"""Get the file path for the qr code of a shared PDF."""

file_name = f'qr/{instance.id}.svg'
file_path = f'{get_pdf_parent_dirs(instance.pdf)}/{file_name}'
file_path = f'{get_collection_dir(instance.pdf.collection)}/{file_name}'

return str(file_path)

Expand Down
15 changes: 15 additions & 0 deletions pdfding/pdf/models/workspace_models.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from shutil import rmtree
from uuid import uuid4

from django.contrib.auth.models import User
from django.db import models
from pdf.models.helpers import get_workspace_path


def get_uuid4_str() -> str:
Expand Down Expand Up @@ -31,6 +33,19 @@ class Workspace(models.Model):
def __str__(self): # pragma: no cover
return str(self.name)

def delete(self, *args, **kwargs) -> None:
"""
Override default delete method so that workspace directory gets deleted after the workspace is deleted.
"""

ws_path = get_workspace_path(self)
super().delete(*args, **kwargs)

try:
rmtree(ws_path)
except Exception: # pragma: no cover # nosec B110
pass

@property
def users(self) -> models.QuerySet[User]:
"""Get the users of the workspace."""
Expand Down
10 changes: 5 additions & 5 deletions pdfding/pdf/templates/collection_details.html
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{% extends 'layouts/blank.html' %}

{% block content %}
<div x-data="{ show_delete_workspace_modal: false }">
<div x-data="{ show_delete_collection_modal: false }">
<div class="flex flex-col md:flex-row md:justify-end"
:class="{ 'opacity-15': show_delete_workspace_modal }">
:class="{ 'opacity-15': show_delete_collection_modal }">
<div class="w-full! md:w-72! lg:w-72! px-2 md:px-4 pt-2">
{% include 'includes/workspace_sidebar.html' with page='collection_details' %}
</div>
Expand Down Expand Up @@ -80,16 +80,16 @@
{% if not collection.default_collection %}
<div class="pt-12 text-xl font-bold text-red-500">Danger Zone</div>
<div class="pt-3">
<span class="text-lg font-bold">Delete Account</span>
<span class="text-lg font-bold">Delete Collection</span>
</div>
<div class="flex justify-between text-slate-600 dark:text-slate-400 creme:text-stone-500">
<div class="w-5/6">
<span>Delete the collection and all its PDFs</span>
</div>
<div class="pr-0 ml-2 md:pl-0 md:pr-4">
<a id="delete-collection" class="text-red-500 hover:text-red-600 cursor-pointer"
@click="show_delete_delete_modal = true"
hx-get="{% url 'delete_workspace' collection.id %}"
@click="show_delete_collection_modal = true"
hx-get="{% url 'delete_collection' collection.id %}"
hx-target="#delete_collection_modal"
hx-swap="innerHTML">
Delete
Expand Down
19 changes: 19 additions & 0 deletions pdfding/pdf/templates/partials/delete_collection.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<div id="delete_collection_modal" class="rounded-2xl drop-shadow-2xl shadow-black w-full py-4
bg-slate-100 dark:bg-slate-800 creme:bg-creme-light
border border-slate-300 dark:border-slate-700 creme:border-creme-dark">
<div class="px-4">
<h2 class="pb-2">Delete {{ collection_name }}</h2>
<div>Are you sure you want to delete this collections and all its PDFs? This action cannot be reversed!</div>
<div class="pt-4 flex gap-x-2">
<button id="confirm_delete" class="flex! items-center justify-center py-1! px-1 bg-red-500! hover:bg-red-600!"
hx-delete="{% url 'delete_collection' collection_id %}"
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'>
Submit
</button>
<div id="cancel_delete" @click="show_delete_collection_modal = false"
class="flex! items-center font-semibold text-red-500 border-2 border-red-500 rounded-md cursor-pointer hover:bg-red-500 hover:text-white">
<span class="px-1">Cancel</span>
</div>
</div>
</div>
</div>
4 changes: 2 additions & 2 deletions pdfding/pdf/tests/test_migrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,8 @@ def test_fill_collections_workspaces(self):
self.assertEqual(changed_pdf.collection, profile.collections[0])
self.assertEqual(changed_tag.workspace, workspace)

@patch('pdf.models.shared_pdf_models.get_pdf_parent_dirs')
@patch('pdf.models.pdf_models.get_pdf_parent_dirs')
@patch('pdf.models.shared_pdf_models.get_collection_dir')
@patch('pdf.models.pdf_models.get_collection_dir')
def test_adjust_file_paths_to_ws_collection(self, mock_get_parent_dirs, mock_shared_get_parent_dirs):
self.pdf.delete() # we need to delete the pdf created by setUp
user = User.objects.create_user(username='user', password='12345')
Expand Down
14 changes: 14 additions & 0 deletions pdfding/pdf/tests/test_models/test_collection_models.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,26 @@
from django.contrib.auth.models import User
from django.test import TestCase
from pdf.models.helpers import get_collection_path
from pdf.models.pdf_models import Pdf


class TestWorkspace(TestCase):
def setUp(self):
self.user = User.objects.create_user(username='user_1', password='password')

def test_delete(self):
collection = self.user.profile.current_collection
collection_path = get_collection_path(collection)
pdfs_path = collection_path / 'pdf'
pdfs_path.mkdir(parents=True, exist_ok=True)

dummy_file_path = pdfs_path / 'dummy.pdf'
dummy_file_path.touch()

collection.delete()

self.assertFalse(collection_path.exists())

def test_pdf_property(self):
collection = self.user.profile.current_collection
pdf_1 = Pdf.objects.create(name='pdf_1', collection=collection)
Expand Down
21 changes: 21 additions & 0 deletions pdfding/pdf/tests/test_models/test_helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from django.contrib.auth.models import User
from django.test import TestCase
from pdf.models.helpers import get_collection_dir
from pdf.models.pdf_models import Pdf
from pdf.services.workspace_services import create_collection, create_workspace


class TestHelpers(TestCase):
def setUp(self):
self.user = User.objects.create_user(username='testuser', password='12345')
self.pdf = Pdf(collection=self.user.profile.current_collection, name='pdf')

def test_get_collection_dir(self):
ws = create_workspace('bla', self.user)
ws.id = '12345'
ws.save()
collection = create_collection(ws, 'Test')

expected_path = '12345/test'

self.assertEqual(expected_path, get_collection_dir(collection))
15 changes: 1 addition & 14 deletions pdfding/pdf/tests/test_models/test_pdf_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,10 @@
convert_to_natural_age,
delete_empty_dirs_after_rename_or_delete,
get_file_path,
get_pdf_parent_dirs,
get_preview_path,
get_thumbnail_path,
)

from pdfding.pdf.services.workspace_services import create_collection, create_workspace
from pdf.services.workspace_services import create_workspace


class TestPdf(TestCase):
Expand Down Expand Up @@ -63,17 +61,6 @@ def test_delete_with_file_directory(self, mock_delete_empty_dirs_after_rename_or
file_name, collection.workspace.id, collection.name
)

def test_get_pdf_parent_dirs(self):
ws = create_workspace('bla', self.user)
ws.id = '12345'
ws.save()
collection = create_collection(ws, 'Test')
pdf = Pdf.objects.create(name='asd', collection=collection)

expected_parent_dir = '12345/test'

self.assertEqual(expected_parent_dir, get_pdf_parent_dirs(pdf))

def test_get_file_path(self):
collection = self.user.profile.current_collection
pdf = Pdf(collection=collection, name='PDF_3! 寝る 12/3? ')
Expand Down
13 changes: 13 additions & 0 deletions pdfding/pdf/tests/test_models/test_workspace_models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from django.contrib.auth.models import User
from django.test import TestCase
from pdf.models.helpers import get_workspace_path
from pdf.models.workspace_models import WorkspaceError, WorkspaceRoles, WorkspaceUser
from pdf.services.workspace_services import create_collection, create_workspace

Expand All @@ -10,6 +11,18 @@ def setUp(self):
self.ws = create_workspace(name='ws', creator=self.user)
self.other_user = User.objects.create_user(username='other_user', password='password')

def test_delete(self):
ws_path = get_workspace_path(self.ws)
pdfs_path = ws_path / 'some_collection' / 'pdf'
pdfs_path.mkdir(parents=True)

dummy_file_path = pdfs_path / 'dummy.pdf'
dummy_file_path.touch()

self.ws.delete()

self.assertFalse(ws_path.exists())

def test_user_property(self):
self.assertEqual(self.ws.users.count(), 1)

Expand Down
Loading