From 1ed238e7b19add9877e1d80f6c7ba7ce521afaca Mon Sep 17 00:00:00 2001 From: Jacob Rief Date: Tue, 7 Jan 2025 21:33:35 +0100 Subject: [PATCH] starting with pytest --- .github/workflows/frontend.yml | 21 ---- .github/workflows/test.yml | 44 ------- .github/workflows/unittests.yml | 51 ++++++++ client/scss/finder-admin.scss | 1 - demoapp/e2etests/__init__.py | 0 demoapp/e2etests/conftest.py | 109 ++++++++++++++++++ demoapp/pytest.ini | 4 + demoapp/settings.py | 39 +++---- demoapp/unittests/__init__.py | 0 demoapp/unittests/conftest.py | 50 ++++++++ demoapp/unittests/test_folder_admin.py | 42 +++++++ demoapp/utils.py | 29 +++++ finder/admin/__init__.py | 2 +- finder/admin/folder.py | 23 ++-- finder/admin/inode.py | 1 - finder/contrib/archive/admin.py | 15 +-- finder/contrib/audio/models.py | 2 +- finder/contrib/image/pil/models.py | 2 +- finder/contrib/video/models.py | 4 +- finder/forms/widgets.py | 4 +- finder/management/commands/filer_to_finder.py | 2 +- finder/models/folder.py | 2 +- finder/models/inode.py | 14 +-- finder/models/label.py | 2 +- finder/models/realm.py | 5 + package.json | 3 +- 26 files changed, 337 insertions(+), 134 deletions(-) delete mode 100644 .github/workflows/frontend.yml delete mode 100644 .github/workflows/test.yml create mode 100644 .github/workflows/unittests.yml create mode 100644 demoapp/e2etests/__init__.py create mode 100644 demoapp/e2etests/conftest.py create mode 100644 demoapp/pytest.ini create mode 100644 demoapp/unittests/__init__.py create mode 100644 demoapp/unittests/conftest.py create mode 100644 demoapp/unittests/test_folder_admin.py create mode 100644 demoapp/utils.py diff --git a/.github/workflows/frontend.yml b/.github/workflows/frontend.yml deleted file mode 100644 index 836dd4f47..000000000 --- a/.github/workflows/frontend.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: gulp - -on: [push] - -jobs: - gulp: - runs-on: ubuntu-20.04 - strategy: - matrix: - node-version: [16.20.x] - - steps: - - uses: actions/checkout@v2 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 - with: - node-version: ${{ matrix.node-version }} - - run: python -m pip install -r tests/requirements/frontend.txt - - run: npm install - - run: npm install -g gulp@4.0.2 - - run: gulp ci diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index 136b3ef29..000000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: CodeCov - -on: [push, pull_request] - -jobs: - unit-tests: - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - python-version: ['3.10', '3.11', '3.12'] - requirements-file: [ - django-4.2.txt, - django-5.0.txt, - django-main.txt, - ] - custom-image-model: [false, true] - os: [ - ubuntu-20.04, - ] - - steps: - - uses: actions/checkout@v1 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - name: library prerequisites - run: sudo apt-get install python-dev libpq-dev libmagic1 gcc libxml2-dev libxslt1-dev libjpeg62 libopenjp2-7 -y - - name: Install extra dependencies - run: pip install lxml - if: matrix.python-version == '3.10' - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -r tests/requirements/${{ matrix.requirements-file }} - python setup.py install - - name: Enable the custom image model - run: echo "CUSTOM_IMAGE=custom_image.Image" >> $GITHUB_ENV - if: ${{ matrix.custom-image-model }} - - name: Run coverage - run: coverage run setup.py test - - name: Upload Coverage to Codecov - uses: codecov/codecov-action@v1 diff --git a/.github/workflows/unittests.yml b/.github/workflows/unittests.yml new file mode 100644 index 000000000..ddfa4c23c --- /dev/null +++ b/.github/workflows/unittests.yml @@ -0,0 +1,51 @@ +name: Test django-filer + +on: + push: + branches: + - finder + paths-ignore: + - '**.md' + - '**.rst' + - '/docs/**' + pull_request: + branches: + - develop + paths-ignore: + - '**.md' + - '**.rst' + - '/docs/**' + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.11", "3.12"] + django-version: ["5.2.*"] + node-version: ["18.x"] + + steps: + - uses: actions/checkout@v3 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v2 + with: + node-version: ${{ matrix.node-version }} + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + npm install --include=dev + python -m pip install --upgrade pip + python -m pip install https://github.com/django/django/archive/refs/heads/main.zip + python -m pip install django-cte django-entangled ffmpeg-python pillow reportlab svglib + python -m pip install beautifulsoup4 coverage Faker lxml pytest pytest-django pytest-cov + - name: Build Client + run: | + npm run compilescss + npm run esbuild + - name: Test with pytest + run: | + python -m pytest -v demoapp/unittests diff --git a/client/scss/finder-admin.scss b/client/scss/finder-admin.scss index 0fedbf42b..58bb8c688 100644 --- a/client/scss/finder-admin.scss +++ b/client/scss/finder-admin.scss @@ -695,4 +695,3 @@ ul.messagelist { } @import 'node_modules/react-image-crop/src/ReactCrop.scss'; -@import 'node_modules/react-h5-audio-player/src/styles.scss'; diff --git a/demoapp/e2etests/__init__.py b/demoapp/e2etests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/demoapp/e2etests/conftest.py b/demoapp/e2etests/conftest.py new file mode 100644 index 000000000..af42db94f --- /dev/null +++ b/demoapp/e2etests/conftest.py @@ -0,0 +1,109 @@ +import os +import pytest + +from playwright.sync_api import sync_playwright + +from django.conf import settings +from django.core.management import call_command +from django.urls import reverse + +from finder.models.realm import RealmModel + +from ..utils import create_random_image + +os.environ.setdefault('DJANGO_ALLOW_ASYNC_UNSAFE', 'true') + + +@pytest.fixture(autouse=True, scope='session') +def create_assets(): + os.makedirs(settings.BASE_DIR / 'workdir/assets', exist_ok=True) + for counter in range(10): + image = create_random_image() + image.save(settings.BASE_DIR / 'workdir/assets' / f'image_{counter:01d}.png') + + +@pytest.fixture(scope='session') +def django_db_setup(django_db_blocker): + database_file = settings.BASE_DIR / 'workdir/test_db.sqlite3' + settings.DATABASES['default']['NAME'] = database_file + with django_db_blocker.unblock(): + call_command('migrate', verbosity=0) + yield + os.remove(database_file) + + +@pytest.fixture +def realm(admin_client): + if realm := RealmModel.objects.first(): + return realm + response = admin_client.get(reverse('admin:finder_foldermodel_changelist')) + assert response.status_code == 302 + realm = RealmModel.objects.first() + assert realm is not None + redirected = reverse('admin:finder_inodemodel_change', kwargs={'inode_id': realm.root_folder.id}) + assert response.url == redirected + assert realm.root_folder.is_folder is True + assert realm.root_folder.is_trash is False + assert realm.root_folder.owner == response.wsgi_request.user + assert realm.root_folder.name == '__root__' + assert realm.root_folder.parent is None + assert realm.root_folder.is_root + assert realm.trash_folders.count() == 0 + return realm + + +class Connector: + def __init__(self, live_server): + print(f"\nStarting end-to-end test server at {live_server}\n") + self.live_server = live_server + + def __enter__(self): + def print_args(msg): + if msg.type in ['info', 'debug']: + return + for arg in msg.args: + print(arg.json_value()) + + self.playwright = sync_playwright().start() + self.browser = self.playwright.chromium.launch() + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.browser.close() + self.playwright.stop() + + +@pytest.fixture(scope='session') +def connector(live_server): + with Connector(live_server) as connector: + yield connector + + +@pytest.fixture +def locale(): + return 'en-US' + + +@pytest.fixture +def language(): + return 'en' + + +def print_args(msg): + """ + Print messages from the browser console. + """ + for arg in msg.args: + print(arg.json_value()) + + +@pytest.fixture() +def page(connector, viewname, locale, language): + context = connector.browser.new_context(locale=locale) + context.add_cookies([{'name': 'django_language', 'value': language, 'domain': 'localhost', 'path': '/'}]) + page = context.new_page() + # page.on('console', print_args) + page.goto(connector.live_server.url + reverse(viewname)) + # django_formset = page.locator('django-formset:defined') + # django_formset.wait_for() + return page diff --git a/demoapp/pytest.ini b/demoapp/pytest.ini new file mode 100644 index 000000000..d6e8c50eb --- /dev/null +++ b/demoapp/pytest.ini @@ -0,0 +1,4 @@ +[pytest] +DJANGO_SETTINGS_MODULE = demoapp.settings +django_find_project = false +addopts = --tb=native diff --git a/demoapp/settings.py b/demoapp/settings.py index 7420c4688..8f4795943 100644 --- a/demoapp/settings.py +++ b/demoapp/settings.py @@ -34,8 +34,6 @@ 'django.contrib.messages', 'django.contrib.sites', 'django.contrib.staticfiles', - 'easy_thumbnails', - 'filer', 'finder', 'finder.contrib.archive', 'finder.contrib.audio', @@ -75,24 +73,25 @@ WSGI_APPLICATION = 'wsgi.application' -# Database -# https://docs.djangoproject.com/en/4.1/ref/settings/#databases - -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': BASE_DIR / 'workdir/db.sqlite3', - }, - 'default_': { - 'ENGINE': 'django.db.backends.postgresql', - 'NAME': 'finder', - 'USER': 'finder', - 'PASSWORD': '', - 'HOST': 'localhost', - 'PORT': 5432, - # 'CONN_MAX_AGE': 900, - }, -} +if os.getenv('USE_POSTGRES', False) in ['1', 'True', 'true']: + DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': 'finder', + 'USER': 'finder', + 'PASSWORD': '', + 'HOST': 'localhost', + 'PORT': 5432, + # 'CONN_MAX_AGE': 900, + }, + } +else: + DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': BASE_DIR / 'workdir/db.sqlite3', + }, + } DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' diff --git a/demoapp/unittests/__init__.py b/demoapp/unittests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/demoapp/unittests/conftest.py b/demoapp/unittests/conftest.py new file mode 100644 index 000000000..2b67a819d --- /dev/null +++ b/demoapp/unittests/conftest.py @@ -0,0 +1,50 @@ +import os +import pytest + +from django.conf import settings +from django.core.management import call_command +from django.urls import reverse + +from finder.models.realm import RealmModel + +from ..utils import create_random_image + +os.environ.setdefault('DJANGO_ALLOW_ASYNC_UNSAFE', 'true') + + +@pytest.fixture(autouse=True, scope='session') +def create_assets(): + os.makedirs(settings.BASE_DIR / 'workdir/assets', exist_ok=True) + for counter in range(10): + image = create_random_image() + image.save(settings.BASE_DIR / 'workdir/assets' / f'image_{counter:01d}.png') + + +@pytest.fixture(scope='session') +def django_db_setup(django_db_blocker): + database_file = settings.BASE_DIR / 'workdir/test_db.sqlite3' + settings.DATABASES['default']['NAME'] = database_file + with django_db_blocker.unblock(): + call_command('migrate', verbosity=0) + yield + os.remove(database_file) + + +@pytest.fixture +def realm(admin_client): + if realm := RealmModel.objects.first(): + return realm + response = admin_client.get(reverse('admin:finder_foldermodel_changelist')) + assert response.status_code == 302 + realm = RealmModel.objects.first() + assert realm is not None + redirected = reverse('admin:finder_inodemodel_change', kwargs={'inode_id': realm.root_folder.id}) + assert response.url == redirected + assert realm.root_folder.is_folder is True + assert realm.root_folder.is_trash is False + assert realm.root_folder.owner == response.wsgi_request.user + assert realm.root_folder.name == '__root__' + assert realm.root_folder.parent is None + assert realm.root_folder.is_root + assert realm.trash_folders.count() == 0 + return realm diff --git a/demoapp/unittests/test_folder_admin.py b/demoapp/unittests/test_folder_admin.py new file mode 100644 index 000000000..c10939756 --- /dev/null +++ b/demoapp/unittests/test_folder_admin.py @@ -0,0 +1,42 @@ +import json +from uuid import uuid5, NAMESPACE_DNS + +import pytest + +from bs4 import BeautifulSoup + +from django.urls import reverse + + +@pytest.mark.django_db +def test_access_root_folder(realm, admin_client): + admin_url = reverse('admin:finder_inodemodel_change', kwargs={'inode_id': realm.root_folder.id}) + request = admin_client.get(admin_url) + assert request.status_code == 200 + soup = BeautifulSoup(request.content, 'html.parser') + assert soup.title.string == "Root | Change Folder | Django site admin" + script_element = soup.find(id='finder-settings') + assert script_element.name == 'script' + finder_settings = json.loads(script_element.string) + finder_settings.pop('csrf_token') + finder_settings.pop('favorite_folders') + finder_settings.pop('menu_extensions') + assert finder_settings == { + 'name': '__root__', + 'is_folder': True, + 'folder_id': str(realm.root_folder.id), + 'parent_id': None, + 'parent_url': None, + 'is_root': True, + 'is_trash': False, + 'folder_url': admin_url, + 'base_url': reverse('admin:finder_foldermodel_changelist'), + 'ancestors': [str(realm.root_folder.id)], + } + + +@pytest.mark.django_db +def test_access_folder_not_found(admin_client): + admin_url = reverse('admin:finder_inodemodel_change', kwargs={'inode_id': uuid5(NAMESPACE_DNS, 'not-found')}) + request = admin_client.get(admin_url) + assert request.status_code == 404 diff --git a/demoapp/utils.py b/demoapp/utils.py new file mode 100644 index 000000000..81f6b9837 --- /dev/null +++ b/demoapp/utils.py @@ -0,0 +1,29 @@ +import colorsys +from faker import Faker +import random +from PIL import Image, ImageDraw +from typing import NewType + + +ColorRGBA = NewType('ColorRGBA', tuple[int, int, int, int]) + + +def random_color() -> ColorRGBA: + return *(random.randint(0, 255) for _ in range(3)), 255 + + +def rotate_hue(rgb: ColorRGBA, degrees: float) -> ColorRGBA: + hue, lum, sat = colorsys.rgb_to_hls(rgb[0], rgb[1], rgb[2]) + hue = (hue + degrees / 360.0) % 1.0 + lum = 255.0 - lum + return *map(lambda c: int(c), colorsys.hls_to_rgb(hue, lum, sat)), rgb[3] + + +def create_random_image() -> Image: + faker = Faker() + background_color = random_color() + image = Image.new('RGB', (100, 100), color=background_color) + drawing = ImageDraw.Draw(image) + foreground_color = rotate_hue(background_color, 180) + drawing.text((5, 40), faker.text(20), fill=foreground_color) + return image diff --git a/finder/admin/__init__.py b/finder/admin/__init__.py index 757e38179..a2a30e94a 100644 --- a/finder/admin/__init__.py +++ b/finder/admin/__init__.py @@ -2,4 +2,4 @@ from . import folder from . import label -__all__ = ['file', 'folder'] +__all__ = ['file', 'folder', 'label'] diff --git a/finder/admin/folder.py b/finder/admin/folder.py index 3012315e9..ed00bdf61 100644 --- a/finder/admin/folder.py +++ b/finder/admin/folder.py @@ -8,7 +8,7 @@ from django.http.response import HttpResponse, HttpResponseBadRequest, HttpResponseNotFound, JsonResponse from django.templatetags.static import static from django.urls import path, reverse -from django.utils.translation import gettext, gettext_lazy as _ +from django.utils.translation import gettext from django.utils.html import format_html from finder.models.file import InodeModel, FileModel @@ -23,13 +23,6 @@ class FolderAdmin(InodeAdmin): form_template = 'finder/admin/change_folder_form.html' _model_admin_cache = {} - _legends = { - 'name': _("Name"), - 'owner_name': _("Owner"), - 'details': _("Details"), - 'created_at': _("Created at"), - 'mime_type': _("Mime type"), - } @property def media(self): @@ -100,7 +93,10 @@ def has_add_permission(self, request): return False def change_view(self, request, inode_id, **kwargs): - inode_obj = self.get_object(request, inode_id) + try: + inode_obj = self.get_object(request, inode_id) + except FolderModel.DoesNotExist: + return HttpResponseNotFound(f"Folder<{inode_id}> not found.") if inode_obj is None: return self._get_obj_does_not_exist_redirect(request, self.model._meta, str(inode_id)) if inode_obj.is_folder: @@ -146,7 +142,6 @@ def get_editor_settings(self, request, inode): settings.update( base_url=reverse('admin:finder_foldermodel_changelist', current_app=self.admin_site.name), ancestors=ancestor_ids, - legends=self._legends, menu_extensions=self.get_menu_extension_settings(request), ) return settings @@ -155,9 +150,9 @@ def get_menu_extension_settings(self, request): extensions = [] for model in InodeModel.get_models(include_proxy=True): if model_admin := self.admin_site._registry.get(model): - extension = model_admin.get_menu_extension_settings(request) - if extension.get('component'): - extensions.append(extension) + extension = model_admin.get_menu_extension_settings(request) + if extension.get('component'): + extensions.append(extension) return extensions def get_model_admin(self, mime_type): @@ -378,7 +373,7 @@ def get_or_create_folder(self, request, folder_id): if response := self.check_for_valid_post_request(request, folder_id): return response if not (folder := self.get_object(request, folder_id)): - return HttpResponseNotFound(f"Folder {folder_id} not found.") + return HttpResponseNotFound(f"FolderModel<{folder_id}> not found.") body = json.loads(request.body) for folder_name in body['relative_path'].split('/'): folder, _ = FolderModel.objects.get_or_create( diff --git a/finder/admin/inode.py b/finder/admin/inode.py index b33227c9c..89004b18c 100644 --- a/finder/admin/inode.py +++ b/finder/admin/inode.py @@ -28,7 +28,6 @@ def get_urls(self): return urls def get_object(self, request, inode_id, *args): - site = get_current_site(request) return FolderModel.objects.get_inode(id=inode_id) def check_for_valid_post_request(self, request, folder_id): diff --git a/finder/contrib/archive/admin.py b/finder/contrib/archive/admin.py index d9ad5c7b7..d06c523e8 100644 --- a/finder/contrib/archive/admin.py +++ b/finder/contrib/archive/admin.py @@ -31,13 +31,13 @@ def get_editor_urls(self): return urls def get_menu_extension_urls(self): - urls = [ + urls = super().get_menu_extension_urls() + urls.append( path( '/archive', self.admin_site.admin_view(self.archive_selected), ), - ] - urls.extend(super().get_menu_extension_urls()) + ) return urls def archive_selected(self, request, folder_id): @@ -165,14 +165,5 @@ def get_editor_settings(self, request, inode): def get_menu_extension_settings(self, request): return {'component': 'Archive'} - def get_menu_extension_urls(self): - urls = super().get_menu_extension_urls() - urls.append( - path( - '/archive', - self.admin_site.admin_view(self.archive_selected), - ), - ) - return urls admin.site.register(ArchiveModel, ArchiveAdmin) diff --git a/finder/contrib/audio/models.py b/finder/contrib/audio/models.py index 9c0592fa3..96971249d 100644 --- a/finder/contrib/audio/models.py +++ b/finder/contrib/audio/models.py @@ -35,7 +35,7 @@ def get_sample_url(self): stream = ffmpeg.output(stream, default_storage.path(sample_path)) try: ffmpeg.run(stream) - except ffmpeg.Error as exp: + except ffmpeg.Error: return self.fallback_thumbnail_url return default_storage.url(sample_path) diff --git a/finder/contrib/image/pil/models.py b/finder/contrib/image/pil/models.py index c65cb7580..5319a5f79 100644 --- a/finder/contrib/image/pil/models.py +++ b/finder/contrib/image/pil/models.py @@ -87,7 +87,7 @@ def get_thumbnail_url(self): image.thumbnail((self.thumbnail_size, self.thumbnail_size)) (default_storage.base_location / thumbnail_path.parent).mkdir(parents=True, exist_ok=True) image.save(default_storage.open(thumbnail_path, 'wb'), image.format) - except Exception as exception: + except Exception: # thumbnail image could not be created return self.fallback_thumbnail_url return default_storage.url(thumbnail_path) diff --git a/finder/contrib/video/models.py b/finder/contrib/video/models.py index fc664377b..0345e63e6 100644 --- a/finder/contrib/video/models.py +++ b/finder/contrib/video/models.py @@ -40,7 +40,7 @@ def get_sample_url(self): stream = ffmpeg.output(stream, default_storage.path(sample_path)) try: ffmpeg.run(stream) - except ffmpeg.Error as exp: + except ffmpeg.Error: return self.fallback_thumbnail_url return default_storage.url(sample_path) @@ -57,7 +57,7 @@ def get_thumbnail_url(self): stream = ffmpeg.output(video_stream, default_storage.path(poster_path), vframes=1) try: ffmpeg.run(stream) - except ffmpeg.Error as exp: + except ffmpeg.Error: return self.fallback_thumbnail_url return default_storage.url(poster_path) diff --git a/finder/forms/widgets.py b/finder/forms/widgets.py index c007146de..74d76b308 100644 --- a/finder/forms/widgets.py +++ b/finder/forms/widgets.py @@ -1,7 +1,7 @@ import json from django.core.serializers.json import DjangoJSONEncoder -from django.forms.widgets import TextInput, SelectMultiple +from django.forms.widgets import TextInput from django.templatetags.static import static from django.urls import reverse from django.utils.html import format_html @@ -13,7 +13,7 @@ class FinderFileSelect(TextInput): template_name = 'finder/widgets/finder_file_select.html' class Media: - css={'all': ['finder/css/finder-select.css']} + css = {'all': ['finder/css/finder-select.css']} js = [format_html( '', static('finder/js/finder-select.js') diff --git a/finder/management/commands/filer_to_finder.py b/finder/management/commands/filer_to_finder.py index d1f0e1f85..307041b23 100644 --- a/finder/management/commands/filer_to_finder.py +++ b/finder/management/commands/filer_to_finder.py @@ -1,9 +1,9 @@ from pathlib import Path +from django.conf import settings from django.contrib.admin import site as admin_site from django.contrib.sites.models import Site from django.core.management.base import BaseCommand -from easy_thumbnails.conf import settings from filer.models.filemodels import Folder as FilerFolder from filer.models.imagemodels import Image as FilerImage diff --git a/finder/models/folder.py b/finder/models/folder.py index 96f9a3a51..edce49523 100644 --- a/finder/models/folder.py +++ b/finder/models/folder.py @@ -14,7 +14,7 @@ ModelManager = models.Manager from .inode import InodeManagerMixin, InodeModel -from.realm import RealmModel +from .realm import RealmModel class FolderModelManager(InodeManagerMixin, ModelManager): diff --git a/finder/models/inode.py b/finder/models/inode.py index 5ef0f984f..2c6cc1ac1 100644 --- a/finder/models/inode.py +++ b/finder/models/inode.py @@ -89,8 +89,8 @@ class InodeManagerMixin: def filter_unified(self, **lookup): """ - Returns a unified QuerySet of all folders and files with fields from all involved models inheriting - from InodeModel. The QuerySet is filtered by the given lookup parameters. + Returns a unified QuerySet of all folders and files with fields from all involved models + inheriting from InodeModel. The QuerySet is filtered by the given lookup parameters. Entries are represented as dictionaries rather than model instances. """ from .file import FileModel @@ -147,12 +147,9 @@ def get_inode(self, **lookup): folder_qs = FolderModel.objects.none() elif (folder_qs := FolderModel.objects.filter(**lookup)).exists(): return folder_qs.get() - try: - values = folder_qs.values('id', mime_type=Value(None, output_field=models.CharField())).union(*[ - model.objects.values('id', 'mime_type').filter(**lookup) for model in FileModel.get_models() - ]).get() - except FolderModel.DoesNotExist as exc: - raise FileModel.DoesNotExist(exc) + values = folder_qs.values('id', mime_type=Value(None, output_field=models.CharField())).union(*[ + model.objects.values('id', 'mime_type').filter(**lookup) for model in FileModel.get_models() + ]).get() return FileModel.objects.get_model_for(values['mime_type']).objects.get(id=values['id']) @classmethod @@ -178,7 +175,6 @@ def get_queryset(self): return queryset.filter(self.model.mime_types_query()) - def filename_validator(value): pattern = re.compile(r"^[\w\d\s\.&%!_#~+-]+$") if not pattern.match(value): diff --git a/finder/models/label.py b/finder/models/label.py index db4fc5cb8..66223b515 100644 --- a/finder/models/label.py +++ b/finder/models/label.py @@ -1,5 +1,5 @@ from django.db import models -from django.utils.translation import gettext, gettext_lazy as _ +from django.utils.translation import gettext_lazy as _ class Label(models.Model): diff --git a/finder/models/realm.py b/finder/models/realm.py index aa94b9cf1..a01dd7d07 100644 --- a/finder/models/realm.py +++ b/finder/models/realm.py @@ -4,6 +4,11 @@ class RealmModel(models.Model): + """ + The RealmModel is the top-level container for each tennant. This usually is associated with a + Django Admin Site. + Each RealmModel has one root folder and a trash folder per user. + """ site = models.ForeignKey( Site, on_delete=models.CASCADE, diff --git a/package.json b/package.json index d5e1982bd..6c0444c52 100644 --- a/package.json +++ b/package.json @@ -19,12 +19,11 @@ "downshift": "^9.0.8", "esbuild": "^0.19.12", "esbuild-plugin-svgr": "^2.1.0", - "react-h5-audio-player": "^3.9.3", "react-image-crop": "^11.0.7", "react-intersection-observer": "^9.13.1", "react-player": "^2.16.0", "request": "^2.88.2", - "sass": "^1.80.4", + "sass": "^1.78.0", "typescript": "^5.6.3", "yargs-parser": "^21.1.1" },