diff --git a/akarpov/common/views.py b/akarpov/common/views.py index 0f6fb33..3e7d65c 100644 --- a/akarpov/common/views.py +++ b/akarpov/common/views.py @@ -1,6 +1,17 @@ from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin +from django.views.generic.detail import SingleObjectMixin class SuperUserRequiredMixin(LoginRequiredMixin, UserPassesTestMixin): def test_func(self): return self.request.user.is_superuser + + +class HasPermissions(SingleObjectMixin): + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + has_perm = False + if self.request.user.is_authentificated: + has_perm = self.object.user == self.request.user + context["has_permissions"] = has_perm + return context diff --git a/akarpov/gallery/__init__.py b/akarpov/gallery/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/akarpov/gallery/admin.py b/akarpov/gallery/admin.py new file mode 100644 index 0000000..6f4b919 --- /dev/null +++ b/akarpov/gallery/admin.py @@ -0,0 +1,6 @@ +from django.contrib import admin + +from akarpov.gallery.models import Collection, Image + +admin.site.register(Collection) +admin.site.register(Image) diff --git a/akarpov/gallery/apps.py b/akarpov/gallery/apps.py new file mode 100644 index 0000000..1b69b81 --- /dev/null +++ b/akarpov/gallery/apps.py @@ -0,0 +1,12 @@ +from django.apps import AppConfig + + +class GalleryConfig(AppConfig): + name = "akarpov.gallery" + verbose_name = "Gallery" + + def ready(self): + try: + import akarpov.gallery.signals # noqa F401 + except ImportError: + pass diff --git a/akarpov/gallery/migrations/0001_initial.py b/akarpov/gallery/migrations/0001_initial.py new file mode 100644 index 0000000..8e676c0 --- /dev/null +++ b/akarpov/gallery/migrations/0001_initial.py @@ -0,0 +1,134 @@ +# Generated by Django 4.2 on 2023-04-29 08:45 + +import akarpov.utils.files +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import django_extensions.db.fields + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ("shortener", "0001_initial"), + ] + + operations = [ + migrations.CreateModel( + name="Collection", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("slug", models.SlugField(blank=True, max_length=20, unique=True)), + ( + "created", + django_extensions.db.fields.CreationDateTimeField( + auto_now_add=True, verbose_name="created" + ), + ), + ( + "modified", + django_extensions.db.fields.ModificationDateTimeField( + auto_now=True, verbose_name="modified" + ), + ), + ("name", models.CharField(blank=True, max_length=250)), + ("description", models.TextField()), + ("public", models.BooleanField(default=False)), + ( + "short_link", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="shortener.link", + ), + ), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="collections", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "get_latest_by": "modified", + "abstract": False, + }, + ), + migrations.CreateModel( + name="Image", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("image_cropped", models.ImageField(blank=True, upload_to="cropped/")), + ("slug", models.SlugField(blank=True, max_length=20, unique=True)), + ( + "created", + django_extensions.db.fields.CreationDateTimeField( + auto_now_add=True, verbose_name="created" + ), + ), + ( + "modified", + django_extensions.db.fields.ModificationDateTimeField( + auto_now=True, verbose_name="modified" + ), + ), + ( + "image", + models.ImageField( + upload_to=akarpov.utils.files.user_file_upload_mixin + ), + ), + ( + "collection", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="images", + to="gallery.collection", + ), + ), + ( + "short_link", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="shortener.link", + ), + ), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="images", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "get_latest_by": "modified", + "abstract": False, + }, + ), + ] diff --git a/akarpov/gallery/migrations/__init__.py b/akarpov/gallery/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/akarpov/gallery/models.py b/akarpov/gallery/models.py new file mode 100644 index 0000000..8a902fe --- /dev/null +++ b/akarpov/gallery/models.py @@ -0,0 +1,38 @@ +from django.db import models +from django.urls import reverse +from django_extensions.db.models import TimeStampedModel + +from akarpov.common.models import BaseImageModel +from akarpov.tools.shortener.models import ShortLink +from akarpov.utils.files import user_file_upload_mixin + + +class Collection(TimeStampedModel, ShortLink): + name = models.CharField(max_length=250, blank=True) + description = models.TextField() + public = models.BooleanField(default=False) + user = models.ForeignKey( + "users.User", related_name="collections", on_delete=models.CASCADE + ) + + def get_absolute_url(self): + return reverse("gallery:collection", kwargs={"slug": self.slug}) + + def __str__(self): + return self.name + + +class Image(TimeStampedModel, ShortLink, BaseImageModel): + collection = models.ForeignKey( + "Collection", related_name="images", on_delete=models.CASCADE + ) + user = models.ForeignKey( + "users.User", related_name="images", on_delete=models.CASCADE + ) + image = models.ImageField(upload_to=user_file_upload_mixin, blank=False, null=False) + + def get_absolute_url(self): + return reverse("gallery:view", kwargs={"slug": self.slug}) + + def __str__(self): + return self.image.name diff --git a/akarpov/gallery/urls.py b/akarpov/gallery/urls.py new file mode 100644 index 0000000..149e900 --- /dev/null +++ b/akarpov/gallery/urls.py @@ -0,0 +1,10 @@ +from django.urls import path + +from akarpov.gallery.views import collection_view, image_view, list_collections_view + +app_name = "gallery" +urlpatterns = [ + path("", list_collections_view, name="list"), + path("", collection_view, name="collection"), + path("image/", image_view, name="view"), +] diff --git a/akarpov/gallery/views.py b/akarpov/gallery/views.py new file mode 100644 index 0000000..5b70c86 --- /dev/null +++ b/akarpov/gallery/views.py @@ -0,0 +1,33 @@ +from django.views import generic + +from akarpov.common.views import HasPermissions +from akarpov.gallery.models import Collection + + +class ListCollectionsView(generic.ListView): + model = Collection + template_name = "gallery/list.html" + + def get_queryset(self): + if self.request.user.is_authenticated: + return self.request.user.collections.all() + return Collection.objects.filter(public=True) + + +list_collections_view = ListCollectionsView.as_view() + + +class CollectionView(generic.DetailView, HasPermissions): + model = Collection + template_name = "gallery/collection.html" + + +collection_view = CollectionView.as_view() + + +class ImageView(generic.DetailView, HasPermissions): + model = Collection + template_name = "gallery/image.html" + + +image_view = ImageView.as_view() diff --git a/akarpov/templates/gallery/collection.html b/akarpov/templates/gallery/collection.html new file mode 100644 index 0000000..21f5da2 --- /dev/null +++ b/akarpov/templates/gallery/collection.html @@ -0,0 +1 @@ +{% extends 'base.html' %} diff --git a/akarpov/templates/gallery/image.html b/akarpov/templates/gallery/image.html new file mode 100644 index 0000000..21f5da2 --- /dev/null +++ b/akarpov/templates/gallery/image.html @@ -0,0 +1 @@ +{% extends 'base.html' %} diff --git a/akarpov/templates/gallery/list.html b/akarpov/templates/gallery/list.html new file mode 100644 index 0000000..21f5da2 --- /dev/null +++ b/akarpov/templates/gallery/list.html @@ -0,0 +1 @@ +{% extends 'base.html' %} diff --git a/akarpov/templates/shortener/create.html b/akarpov/templates/tools/shortener/create.html similarity index 100% rename from akarpov/templates/shortener/create.html rename to akarpov/templates/tools/shortener/create.html diff --git a/akarpov/test_platform/templates/shortener/revoked.html b/akarpov/templates/tools/shortener/revoked.html similarity index 100% rename from akarpov/test_platform/templates/shortener/revoked.html rename to akarpov/templates/tools/shortener/revoked.html diff --git a/akarpov/templates/shortener/view.html b/akarpov/templates/tools/shortener/view.html similarity index 100% rename from akarpov/templates/shortener/view.html rename to akarpov/templates/tools/shortener/view.html diff --git a/akarpov/test_platform/templates/about/project.html b/akarpov/test_platform/templates/about/project.html deleted file mode 100644 index 55b237d..0000000 --- a/akarpov/test_platform/templates/about/project.html +++ /dev/null @@ -1,29 +0,0 @@ -{% load markdown_extras %} - -{% block content %} -
- back to all projects -
-
- -
-
-
-
-

{{ project.name }}

-

{{ project.md | markdown | safe }}

-

{{ project.description }}

-

Created: {{ project.created|date:"d M Y" }}

-

- {% if project.source_link %} - Source code - {% endif %} - {% if project.view_link %} - View project - {% endif %} -

-
-
-
-
-{% endblock %} diff --git a/akarpov/tools/shortener/views.py b/akarpov/tools/shortener/views.py index a0742ee..d5f94c3 100644 --- a/akarpov/tools/shortener/views.py +++ b/akarpov/tools/shortener/views.py @@ -11,7 +11,7 @@ class ShortLinkCreateView(CreateView): model = Link form_class = LinkForm - template_name = "shortener/create.html" + template_name = "tools/shortener/create.html" def form_valid(self, form): if self.request.user.is_authenticated: @@ -24,7 +24,7 @@ def form_valid(self, form): class LinkDetailView(DetailView): - template_name = "shortener/view.html" + template_name = "tools/shortener/view.html" def get_object(self, *args, **kwargs): link = get_link_from_slug(self.kwargs["slug"]) @@ -39,7 +39,7 @@ def get_object(self, *args, **kwargs): class LinkRevokedView(TemplateView): - template_name = "shortener/revoked.html" + template_name = "tools/shortener/revoked.html" link_revoked_view = LinkRevokedView.as_view() diff --git a/config/settings/base.py b/config/settings/base.py index b4adb23..82ee625 100644 --- a/config/settings/base.py +++ b/config/settings/base.py @@ -136,7 +136,7 @@ "health_check.contrib.redis", ] -ALL_AUTH_PROVIDERS = [ +ALLAUTH_PROVIDERS = [ "allauth.socialaccount.providers.github", # "allauth.socialaccount.providers.google", # "allauth.socialaccount.providers.telegram", @@ -149,6 +149,7 @@ "akarpov.blog", "akarpov.files", "akarpov.music", + "akarpov.gallery", "akarpov.pipeliner", "akarpov.test_platform", "akarpov.tools.shortener", @@ -156,7 +157,7 @@ ] # https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps INSTALLED_APPS = ( - DJANGO_APPS + LOCAL_APPS + THIRD_PARTY_APPS + HEALTH_CHECKS + ALL_AUTH_PROVIDERS + DJANGO_APPS + LOCAL_APPS + THIRD_PARTY_APPS + HEALTH_CHECKS + ALLAUTH_PROVIDERS ) # MIGRATIONS diff --git a/config/urls.py b/config/urls.py index ee337b7..4402299 100644 --- a/config/urls.py +++ b/config/urls.py @@ -36,6 +36,7 @@ path("music/", include("akarpov.music.urls", namespace="music")), path("forms/", include("akarpov.test_platform.urls", namespace="forms")), path("tools/", include("akarpov.tools.urls", namespace="tools")), + path("gallery/", include("akarpov.gallery.urls", namespace="gallery")), path("ckeditor/", include("ckeditor_uploader.urls")), path("accounts/", include("allauth.urls")), path("", include("akarpov.blog.urls", namespace="blog")), diff --git a/poetry.lock b/poetry.lock index 9fb7c2b..90c55e5 100644 --- a/poetry.lock +++ b/poetry.lock @@ -4341,32 +4341,32 @@ files = [ [[package]] name = "rawpy" -version = "0.18.0" +version = "0.18.1" description = "RAW image processing for Python, a wrapper for libraw" category = "main" optional = false python-versions = "*" files = [ - {file = "rawpy-0.18.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9d18fa393257cb731b9bc9a1d4fcb4e40f0c8c3dbf735adce366442edb5b2f32"}, - {file = "rawpy-0.18.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6816c395deb842072eb7937794b515ab88335df711ea5c5a74486f7fad31c34"}, - {file = "rawpy-0.18.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2966e0e8005c2bce1ca44dd342dc073ed5b7ef5591c97b86cafa65397c3f382"}, - {file = "rawpy-0.18.0-cp310-cp310-win_amd64.whl", hash = "sha256:b8d7af8d0ab58c049d208ee92e8404331bacb5f6e1c2912e856f9ed831a05572"}, - {file = "rawpy-0.18.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6c60c3f5c4ea4229baa9d3a02f1205539e0ff9a1650d22c6f9cef4fdaefc5c1e"}, - {file = "rawpy-0.18.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52ff86dd8150aedea306fbd633510d7c49fba9df767b681091babc59a70da5c4"}, - {file = "rawpy-0.18.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1282f2c0b59fe1fc95f1e8ff51d9915ee2d3f85431345550860693b9692eab9"}, - {file = "rawpy-0.18.0-cp311-cp311-win_amd64.whl", hash = "sha256:3d66bd67f37ad68d947336a37137f2717f28ee13200aa0ea51ee7cddffdd8570"}, - {file = "rawpy-0.18.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7264894e625154e3d8db3e7caefbaa5b89738d159649867dc80d86ac0450739f"}, - {file = "rawpy-0.18.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4b3364cfe181c1e55a49e7fdc56e19601c1948e4294ed422ba3a8d5254241fc"}, - {file = "rawpy-0.18.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bfea2755593ec2f357db3d1977080394b8191ee2d850956b3ae01ebb4684cde"}, - {file = "rawpy-0.18.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d000a9b232225220ff3ad89478d474e191e741bc2bdd2761c609ae0221e72372"}, - {file = "rawpy-0.18.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1a90395ffb51fe2fb4186d7a9724cb893fbbfb3aaa51807e2dfec81e6a70566b"}, - {file = "rawpy-0.18.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df68580994bd68e9f5935b9778f07745a0845fa0f52f716bd7532570ef156ada"}, - {file = "rawpy-0.18.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:828703186a4f420c9f03ae271f6caa1458cd5ecc7a1f6a51bf8fad45a074f88d"}, - {file = "rawpy-0.18.0-cp38-cp38-win_amd64.whl", hash = "sha256:b3230514c220f44bea3e8abb229836c67c46ff96bca67b408141b01eb2e73908"}, - {file = "rawpy-0.18.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9b4f5e5b38af08c1d9c01f04cfccbb472633e2fe9a0f4b0ea2e3aa847890daae"}, - {file = "rawpy-0.18.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51a7b39b534e22aaa716da41a0dab62457e26d3da6490b84b59a0054ffc4529e"}, - {file = "rawpy-0.18.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:115c0f05184ee0273afccae39ef658f89e58edb39fd60198cc60f0fb4692609b"}, - {file = "rawpy-0.18.0-cp39-cp39-win_amd64.whl", hash = "sha256:8f07f9366e20651cf49dc029c69f46c26d6a50b2b78b4a99a0caad2b87861825"}, + {file = "rawpy-0.18.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:30f5a7f3b80db8aa18876831b73b5cf0530da3f2dd8be9870cc1bae271b5fdb0"}, + {file = "rawpy-0.18.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6cdb86817f4740c3252d4797e0b5926d499330b46da1255d56d6a401a34c3ec"}, + {file = "rawpy-0.18.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c0a040c33fe3b14a6bd9d81a8c2d6cb49657530ede982ca1585108229fec651"}, + {file = "rawpy-0.18.1-cp310-cp310-win_amd64.whl", hash = "sha256:bc2fc6e346278ea3fc0fe76b704984551055914905e6187be7844fecfc756b22"}, + {file = "rawpy-0.18.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e836ff1a0f7d8eb24665f4f230a41f14eda66a86445b372b906c2cf8651a5d17"}, + {file = "rawpy-0.18.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f3b352da1316f5ac9a41e418f6247587420ff9966441b95f2aa6cefdb167c51"}, + {file = "rawpy-0.18.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fac80b3aa1cec375345c14a63c1c0360520ef34b7f1150478cf453e43585c1c"}, + {file = "rawpy-0.18.1-cp311-cp311-win_amd64.whl", hash = "sha256:fff49c7529f3c06aff2daa9d61a092a0f13ca30fdf722b6d12ca5cff9e21180a"}, + {file = "rawpy-0.18.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:296bb1bcc397de4a9e018cb4136c395f7c49547eae9e3de1b5e785db2b23657a"}, + {file = "rawpy-0.18.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08160b50b4b63a9150334c79a30c2c24c69824386c3a92fa2d8c66a2a29680f6"}, + {file = "rawpy-0.18.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6effc4a49f64acaa01e35ec42b7c29f2e7453b8f010dbcf4aacd0f1394c186c"}, + {file = "rawpy-0.18.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a4f4f51d55e073394340cb52f5fcb4bb2d1c596884666ee85e61c15b9c2eef59"}, + {file = "rawpy-0.18.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:710fdaf4af31171d847eae4dc4bbd717a951d75d159cdcc27a9ee8b046354447"}, + {file = "rawpy-0.18.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18171458cff40f968797fac4681aed6ec9bf3b2278b2235d39b09f987d2470b8"}, + {file = "rawpy-0.18.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cfd92c94eae2f8bdde4b249007f8207e74f0bc8f3f5998e6784eaf1e9b67dd7a"}, + {file = "rawpy-0.18.1-cp38-cp38-win_amd64.whl", hash = "sha256:d37c144ac4922ce20acccf93bb97b2d5a3e06ab782de58d9630bd77733962cb6"}, + {file = "rawpy-0.18.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:be306f686039dc2e2f7374ba15ce64b4392f10ca586f6ca6dd3252777588e750"}, + {file = "rawpy-0.18.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:10cea749fc9d8cf1ae716172dd09b36accfd1de576351ce6a650b5b30f9dc6f8"}, + {file = "rawpy-0.18.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1a6437ebdac9c3ce09932d752eac7acfda6e56a7996b211ac2a625b80168dad"}, + {file = "rawpy-0.18.1-cp39-cp39-win_amd64.whl", hash = "sha256:139715d16ff64c47f53972e6b07576ce5c47cef899a187b149750855d08ef557"}, ] [package.dependencies] diff --git a/pyproject.toml b/pyproject.toml index 98751b3..9f5990f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -80,7 +80,7 @@ pydub = "^0.25.1" python-mpd2 = "^3.0.5" yandex-music = "^2.1.0" pyjwt = "^2.6.0" -rawpy = "^0.18.0" +rawpy = "^0.18.1" xvfbwrapper = "^0.2.9" vtk = "^9.2.6" ffmpeg-python = "^0.2.0"