From 17088d367dbfe7494e8862dbe9a9783da0b75b90 Mon Sep 17 00:00:00 2001 From: Mike0001-droid Date: Thu, 4 Jul 2024 10:12:45 +0500 Subject: [PATCH] =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20?= =?UTF-8?q?=D0=BD=D0=B0=D1=81=D1=82=D1=80=D0=BE=D0=B9=D0=BA=D0=B8=20=D0=B4?= =?UTF-8?q?=D0=BB=D1=8F=20=D0=B5=D0=B4=D0=B8=D0=BD=D0=B8=D1=87=D0=BD=D0=BE?= =?UTF-8?q?=D0=B3=D0=BE=20=D1=81=D0=BE=D0=B7=D0=B4=D0=B0=D0=BD=D0=B8=D1=8F?= =?UTF-8?q?=20=D0=BE=D0=B1=D1=8A=D0=B5=D0=BA=D1=82=D0=B0=20=D1=82=D0=B5?= =?UTF-8?q?=D1=85=20=D0=BF=D0=BE=D0=B4=D0=B4=D0=B5=D1=80=D0=B6=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/proj/config_site/admin.py | 3 +- .../proj/config_site/admin_configuration.py | 99 +++++++++++++++++++ server/proj/config_site/models.py | 6 +- .../proj/config_site/single_configuration.py | 77 +++++++++++++++ server/proj/config_site/solo_settings.py | 21 ++++ .../templates/admin/solo/change_form.html | 22 +++++ .../templates/admin/solo/object_history.html | 15 +++ server/proj/config_site/views.py | 8 +- 8 files changed, 244 insertions(+), 7 deletions(-) create mode 100644 server/proj/config_site/admin_configuration.py create mode 100644 server/proj/config_site/single_configuration.py create mode 100644 server/proj/config_site/solo_settings.py create mode 100644 server/proj/config_site/templates/admin/solo/change_form.html create mode 100644 server/proj/config_site/templates/admin/solo/object_history.html diff --git a/server/proj/config_site/admin.py b/server/proj/config_site/admin.py index 0e04d4c..875998f 100644 --- a/server/proj/config_site/admin.py +++ b/server/proj/config_site/admin.py @@ -1,10 +1,11 @@ from .models import Team, SupportInfo from django.contrib import admin +from config_site.admin_configuration import SingletonModelAdmin @admin.register(Team) class TeamAdmin(admin.ModelAdmin): list_display = ('name', 'last_name', 'position', 'img_person') @admin.register(SupportInfo) -class SupportInfoAdmin(admin.ModelAdmin): +class SupportInfoAdmin(SingletonModelAdmin): list_display = ('phone', 'city', 'street', 'house', 'email', 'vk_url', 'telegram_url') \ No newline at end of file diff --git a/server/proj/config_site/admin_configuration.py b/server/proj/config_site/admin_configuration.py new file mode 100644 index 0000000..fd70feb --- /dev/null +++ b/server/proj/config_site/admin_configuration.py @@ -0,0 +1,99 @@ +from __future__ import annotations + +from typing import Any + +from django.db.models import Model +from django.urls import URLPattern, re_path +from django.contrib import admin +from django.http import HttpRequest, HttpResponse, HttpResponseRedirect +from django.utils.encoding import force_str +from django.utils.translation import gettext as _ + +from config_site.single_configuration import DEFAULT_SINGLETON_INSTANCE_ID +from config_site import solo_settings + + +class SingletonModelAdmin(admin.ModelAdmin): # type: ignore[type-arg] + object_history_template = "admin/solo/object_history.html" + change_form_template = "admin/solo/change_form.html" + + def has_add_permission(self, request: HttpRequest) -> bool: + return False + + def has_delete_permission(self, request: HttpRequest, obj: Model | None = None) -> bool: + return False + + def get_urls(self) -> list[URLPattern]: + urls = super().get_urls() + + if not solo_settings.SOLO_ADMIN_SKIP_OBJECT_LIST_PAGE: + return urls + + # _meta.model_name only exists on Django>=1.6 - + # on earlier versions, use module_name.lower() + try: + model_name = self.model._meta.model_name + except AttributeError: + model_name = self.model._meta.module_name.lower() + + self.model._meta.verbose_name_plural = self.model._meta.verbose_name + url_name_prefix = '%(app_name)s_%(model_name)s' % { + 'app_name': self.model._meta.app_label, + 'model_name': model_name, + } + custom_urls = [ + re_path(r'^history/$', + self.admin_site.admin_view(self.history_view), + {'object_id': str(self.singleton_instance_id)}, + name='%s_history' % url_name_prefix), + re_path(r'^$', + self.admin_site.admin_view(self.change_view), + {'object_id': str(self.singleton_instance_id)}, + name='%s_change' % url_name_prefix), + ] + + # By inserting the custom URLs first, we overwrite the standard URLs. + return custom_urls + urls + + def response_change(self, request: HttpRequest, obj: Model) -> HttpResponseRedirect: + msg = _('%(obj)s was changed successfully.') % { + 'obj': force_str(obj)} + if '_continue' in request.POST: + self.message_user(request, msg + ' ' + + _('You may edit it again below.')) + return HttpResponseRedirect(request.path) + else: + self.message_user(request, msg) + return HttpResponseRedirect("../../") + + def change_view(self, request: HttpRequest, object_id: str, form_url: str = '', extra_context: dict[str, Any] | None = None) -> HttpResponse: + if object_id == str(self.singleton_instance_id): + self.model.objects.get_or_create(pk=self.singleton_instance_id) + + if not extra_context: + extra_context = dict() + extra_context['skip_object_list_page'] = solo_settings.SOLO_ADMIN_SKIP_OBJECT_LIST_PAGE + + return super().change_view( + request, + object_id, + form_url=form_url, + extra_context=extra_context, + ) + + def history_view(self, request: HttpRequest, object_id: str, extra_context: dict[str, Any] | None = None) -> HttpResponse: + if not extra_context: + extra_context = dict() + extra_context['skip_object_list_page'] = solo_settings.SOLO_ADMIN_SKIP_OBJECT_LIST_PAGE + + return super().history_view( + request, + object_id, + extra_context=extra_context, + ) + + @property + def singleton_instance_id(self) -> int: + return getattr(self.model, 'singleton_instance_id', DEFAULT_SINGLETON_INSTANCE_ID) + + diff --git a/server/proj/config_site/models.py b/server/proj/config_site/models.py index 05f0e7d..4396a93 100644 --- a/server/proj/config_site/models.py +++ b/server/proj/config_site/models.py @@ -1,4 +1,5 @@ from django.db import models +from config_site.single_configuration import SingletonModel class Page(models.Model): name = models.CharField('Название страницы', max_length=50) @@ -25,7 +26,7 @@ class Team(models.Model): verbose_name = 'Команда' verbose_name_plural = 'Команда' -class SupportInfo(models.Model): +class SupportInfo(SingletonModel): phone = models.CharField('Номер телефона', max_length=50, null=True, blank=True) city = models.CharField('Город', max_length=50, null=True, blank=True) street = models.CharField('Улица', max_length=50, null=True, blank=True) @@ -34,6 +35,9 @@ class SupportInfo(models.Model): vk_url = models.URLField('Ссылка на группу Вк', max_length=50, null=True, blank=True) telegram_url = models.URLField('Ссылка на канал в Телегерамме', max_length=50, null=True, blank=True) + def __str__(self): + return "Информация о тех поддержке" + class Meta: verbose_name = 'Тех поддержка' verbose_name_plural = 'Тех поддержка' diff --git a/server/proj/config_site/single_configuration.py b/server/proj/config_site/single_configuration.py new file mode 100644 index 0000000..df12f58 --- /dev/null +++ b/server/proj/config_site/single_configuration.py @@ -0,0 +1,77 @@ +from __future__ import annotations +import sys +import warnings +from typing import Any +from django.conf import settings +from django.core.cache import BaseCache, caches +from django.db import models +from config_site import solo_settings +from typing_extensions import Self +if sys.version_info >= (3, 11): + from typing import Self +else: + from typing_extensions import Self + +DEFAULT_SINGLETON_INSTANCE_ID = 1 + + +def get_cache(cache_name: str) -> BaseCache: + warnings.warn( + "'get_cache' is deprecated and will be removed in django-solo 2.4.0. " + "Instead, use 'caches' from 'django.core.cache'.", + DeprecationWarning, + stacklevel=2, + ) + return caches[cache_name] # type: ignore[no-any-return] # mypy bug, unable to get a MRE + + +class SingletonModel(models.Model): + singleton_instance_id = DEFAULT_SINGLETON_INSTANCE_ID + + class Meta: + abstract = True + + def save(self, *args: Any, **kwargs: Any) -> None: + self.pk = self.singleton_instance_id + super().save(*args, **kwargs) + self.set_to_cache() + + def delete(self, *args: Any, **kwargs: Any) -> tuple[int, dict[str, int]]: + self.clear_cache() + return super().delete(*args, **kwargs) + + @classmethod + def clear_cache(cls) -> None: + cache_name = getattr(settings, 'SOLO_CACHE', solo_settings.SOLO_CACHE) + if cache_name: + cache = caches[cache_name] + cache_key = cls.get_cache_key() + cache.delete(cache_key) + + def set_to_cache(self) -> None: + cache_name = getattr(settings, 'SOLO_CACHE', solo_settings.SOLO_CACHE) + if not cache_name: + return None + cache = caches[cache_name] + cache_key = self.get_cache_key() + timeout = getattr(settings, 'SOLO_CACHE_TIMEOUT', solo_settings.SOLO_CACHE_TIMEOUT) + cache.set(cache_key, self, timeout) + + @classmethod + def get_cache_key(cls) -> str: + prefix = getattr(settings, 'SOLO_CACHE_PREFIX', solo_settings.SOLO_CACHE_PREFIX) + return '%s:%s' % (prefix, cls.__name__.lower()) + + @classmethod + def get_solo(cls) -> Self: + cache_name = getattr(settings, 'SOLO_CACHE', solo_settings.SOLO_CACHE) + if not cache_name: + obj, _ = cls.objects.get_or_create(pk=cls.singleton_instance_id) + return obj # type: ignore[return-value] + cache = caches[cache_name] + cache_key = cls.get_cache_key() + obj = cache.get(cache_key) + if not obj: + obj, _ = cls.objects.get_or_create(pk=cls.singleton_instance_id) + obj.set_to_cache() + return obj # type: ignore[return-value] diff --git a/server/proj/config_site/solo_settings.py b/server/proj/config_site/solo_settings.py new file mode 100644 index 0000000..ecff936 --- /dev/null +++ b/server/proj/config_site/solo_settings.py @@ -0,0 +1,21 @@ +from __future__ import annotations + +from django.conf import settings + +# template parameters +GET_SOLO_TEMPLATE_TAG_NAME: str = getattr( + settings, 'GET_SOLO_TEMPLATE_TAG_NAME', 'get_solo' +) + +SOLO_ADMIN_SKIP_OBJECT_LIST_PAGE: bool = getattr( + settings, 'SOLO_ADMIN_SKIP_OBJECT_LIST_PAGE', True +) + +# The cache that should be used, e.g. 'default'. Refers to Django CACHES setting. +# Set to None to disable caching. +SOLO_CACHE: str | None = None + +SOLO_CACHE_TIMEOUT = 60 * 5 + +SOLO_CACHE_PREFIX = 'solo' +DEFAULT_AUTO_FIELD = 'django.db.models.AutoField' diff --git a/server/proj/config_site/templates/admin/solo/change_form.html b/server/proj/config_site/templates/admin/solo/change_form.html new file mode 100644 index 0000000..364f3d3 --- /dev/null +++ b/server/proj/config_site/templates/admin/solo/change_form.html @@ -0,0 +1,22 @@ +{% extends "admin/change_form.html" %} +{% load i18n %} +{% load admin_urls %} + +{% block breadcrumbs %} +{% if skip_object_list_page %} + +{% else %} +{{ block.super }} +{% endif %} +{% endblock %} + +{% block object-tools-items %} +
  • {% trans "History" %}
  • +{% if has_absolute_url %} +
  • {% trans "View on site" %}
  • +{% endif %} +{% endblock %} diff --git a/server/proj/config_site/templates/admin/solo/object_history.html b/server/proj/config_site/templates/admin/solo/object_history.html new file mode 100644 index 0000000..49c4f6f --- /dev/null +++ b/server/proj/config_site/templates/admin/solo/object_history.html @@ -0,0 +1,15 @@ +{% extends "admin/object_history.html" %} +{% load i18n %} + +{% block breadcrumbs %} +{% if skip_object_list_page %} + +{% else %} +{{ block.super }} +{% endif %} +{% endblock %} diff --git a/server/proj/config_site/views.py b/server/proj/config_site/views.py index 0f5be9c..df50a1e 100644 --- a/server/proj/config_site/views.py +++ b/server/proj/config_site/views.py @@ -1,8 +1,6 @@ from rest_framework.viewsets import GenericViewSet from rest_framework.response import Response from rest_framework import status - - from .models import Team, SupportInfo from .serializers import TeamSerializer, SupportInfoSerializer @@ -17,11 +15,11 @@ class TeamViewSet(GenericViewSet): return Response(serializer.data, status=status.HTTP_200_OK) class SupportInfoViewSet(GenericViewSet): - queryset = SupportInfo + queryset = SupportInfo.objects.all() serializer_class = SupportInfoSerializer def list(self, request): - queryset = self.get_queryset().objects.all() - serializer = self.get_serializer(queryset, many=True) + queryset = self.get_queryset().first() + serializer = self.get_serializer(queryset) return Response(serializer.data, status=status.HTTP_200_OK) \ No newline at end of file