diff --git a/docker-compose.yml b/docker-compose.yml index c31e7a9d..4a0b5dc2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -217,21 +217,6 @@ services: - --web.config.file=/etc/prometheus/web.yml logging: *default-logging -# supervisor: -# container_name: supervisor -# build: -# context: . -# dockerfile: ./Dockerfiles/supervisor.Dockerfile -# restart: always -# env_file: -# - .env -# ports: -# - "7777:7777" -# depends_on: -# app: -# condition: service_started -# logging: *default-logging - volumes: postgres-data: diff --git a/engine/core/admin.py b/engine/core/admin.py index b5a6b79d..900a7bb2 100644 --- a/engine/core/admin.py +++ b/engine/core/admin.py @@ -993,6 +993,6 @@ class ConstanceConfig: site.unregister([Config]) # noinspection PyTypeChecker site.register([ConstanceConfig], BaseConstanceAdmin) # type: ignore [list-item] -site.site_title = settings.CONSTANCE_CONFIG["PROJECT_NAME"][0] # type: ignore [assignment] +site.site_title = settings.PROJECT_NAME site.site_header = "eVibes" -site.index_title = settings.CONSTANCE_CONFIG["PROJECT_NAME"][0] # type: ignore [assignment] +site.index_title = settings.PROJECT_NAME diff --git a/engine/core/graphene/object_types.py b/engine/core/graphene/object_types.py index 9efe1081..aab912c2 100644 --- a/engine/core/graphene/object_types.py +++ b/engine/core/graphene/object_types.py @@ -2,7 +2,6 @@ import logging from contextlib import suppress from typing import Any -from constance import config from django.conf import settings from django.core.cache import cache from django.db.models import Max, Min, QuerySet @@ -139,7 +138,7 @@ class BrandType(DjangoObjectType): # type: ignore [misc] lang = graphene_current_lang() base = f"https://{settings.BASE_DOMAIN}" canonical = f"{base}/{lang}/brand/{self.slug}" - title = f"{self.name} | {config.PROJECT_NAME}" + title = f"{self.name} | {settings.PROJECT_NAME}" description = (self.description or "")[:180] logo_url = None @@ -265,7 +264,7 @@ class CategoryType(DjangoObjectType): # type: ignore [misc] lang = graphene_current_lang() base = f"https://{settings.BASE_DOMAIN}" canonical = f"{base}/{lang}/catalog/{self.slug}" - title = f"{self.name} | {config.PROJECT_NAME}" + title = f"{self.name} | {settings.PROJECT_NAME}" description = (self.description or "")[:180] og_image = graphene_abs(info.context, self.image.url) if getattr(self, "image", None) else "" @@ -537,7 +536,7 @@ class ProductType(DjangoObjectType): # type: ignore [misc] lang = graphene_current_lang() base = f"https://{settings.BASE_DOMAIN}" canonical = f"{base}/{lang}/product/{self.slug}" - title = f"{self.name} | {config.PROJECT_NAME}" + title = f"{self.name} | {settings.PROJECT_NAME}" description = (self.description or "")[:180] first_img = self.images.order_by("priority").first() diff --git a/engine/core/tests/test_drf.py b/engine/core/tests/test_drf.py index 3900c36a..bc2388cb 100644 --- a/engine/core/tests/test_drf.py +++ b/engine/core/tests/test_drf.py @@ -30,4 +30,5 @@ class DRFCoreViewsTests(TestCase): serializer.is_valid(raise_exception=True) return serializer.validated_data["access_token"] + # TODO: create tests for every possible HTTP method in core module with DRF stack diff --git a/engine/core/utils/emailing.py b/engine/core/utils/emailing.py index ae1f6eb5..9e28ad28 100644 --- a/engine/core/utils/emailing.py +++ b/engine/core/utils/emailing.py @@ -24,7 +24,7 @@ def contact_us_email(contact_info) -> tuple[bool, str]: ) email = EmailMessage( - _(f"{config.PROJECT_NAME} | contact us initiated"), + _(f"{settings.PROJECT_NAME} | contact us initiated"), render_to_string( "../templates/contact_us_email.html", { @@ -37,7 +37,7 @@ def contact_us_email(contact_info) -> tuple[bool, str]: }, ), to=[config.EMAIL_FROM], - from_email=f"{config.PROJECT_NAME} <{config.EMAIL_FROM}>", + from_email=f"{settings.PROJECT_NAME} <{config.EMAIL_FROM}>", connection=get_dynamic_email_connection(), ) email.content_subtype = "html" @@ -70,7 +70,7 @@ def send_order_created_email(order_pk: str) -> tuple[bool, str]: if not order.is_whole_digital: email = EmailMessage( - _(f"{config.PROJECT_NAME} | order confirmation"), + _(f"{settings.PROJECT_NAME} | order confirmation"), render_to_string( "digital_order_created_email.html" if order.is_whole_digital else "shipped_order_created_email.html", { @@ -81,7 +81,7 @@ def send_order_created_email(order_pk: str) -> tuple[bool, str]: }, ), to=[recipient], - from_email=f"{config.PROJECT_NAME} <{config.EMAIL_FROM}>", + from_email=f"{settings.PROJECT_NAME} <{config.EMAIL_FROM}>", connection=get_dynamic_email_connection(), ) email.content_subtype = "html" @@ -102,14 +102,14 @@ def send_order_finished_email(order_pk: str) -> tuple[bool, str]: activate(order.user.language) email = EmailMessage( - _(f"{config.PROJECT_NAME} | order delivered"), + _(f"{settings.PROJECT_NAME} | order delivered"), render_to_string( template_name="../templates/digital_order_delivered_email.html", context={ "order_uuid": order.human_readable_id, "user_first_name": "" or order.user.first_name, "order_products": ops, - "project_name": config.PROJECT_NAME, + "project_name": settings.PROJECT_NAME, "contact_email": config.EMAIL_FROM, "total_price": round(sum(0.0 or op.buy_price for op in ops), 2), # type: ignore [misc] "display_system_attributes": order.user.has_perm("core.view_order"), @@ -117,7 +117,7 @@ def send_order_finished_email(order_pk: str) -> tuple[bool, str]: }, ), to=[order.user.email], - from_email=f"{config.PROJECT_NAME} <{config.EMAIL_FROM}>", + from_email=f"{settings.PROJECT_NAME} <{config.EMAIL_FROM}>", connection=get_dynamic_email_connection(), ) email.content_subtype = "html" @@ -185,20 +185,20 @@ def send_promocode_created_email(promocode_pk: str) -> tuple[bool, str]: activate(promocode.user.language) email = EmailMessage( - _(f"{config.PROJECT_NAME} | promocode granted"), + _(f"{settings.PROJECT_NAME} | promocode granted"), render_to_string( template_name="../templates/promocode_granted_email.html", context={ "promocode": promocode, "user_first_name": "" or promocode.user.first_name, - "project_name": config.PROJECT_NAME, + "project_name": settings.PROJECT_NAME, "contact_email": config.EMAIL_FROM, "today": datetime.today(), "currency": settings.CURRENCY_CODE, }, ), to=[promocode.user.email], - from_email=f"{config.PROJECT_NAME} <{config.EMAIL_FROM}>", + from_email=f"{settings.PROJECT_NAME} <{config.EMAIL_FROM}>", connection=get_dynamic_email_connection(), ) email.content_subtype = "html" diff --git a/engine/core/utils/seo_builders.py b/engine/core/utils/seo_builders.py index 6567b54a..94a769b8 100644 --- a/engine/core/utils/seo_builders.py +++ b/engine/core/utils/seo_builders.py @@ -18,7 +18,7 @@ def website_schema(): return { "@context": "https://schema.org", "@type": "WebSite", - "name": config.PROJECT_NAME, + "name": settings.PROJECT_NAME, "url": f"https://{settings.BASE_DOMAIN}/", "potentialAction": { "@type": "SearchAction", diff --git a/engine/core/viewsets.py b/engine/core/viewsets.py index dea0b3b7..bce34d89 100644 --- a/engine/core/viewsets.py +++ b/engine/core/viewsets.py @@ -3,9 +3,8 @@ import uuid from typing import Type from uuid import UUID -from constance import config from django.conf import settings -from django.db.models import Prefetch, Q, OuterRef, Exists +from django.db.models import Exists, OuterRef, Prefetch, Q from django.http import Http404 from django.shortcuts import get_object_or_404 from django.utils.decorators import method_decorator @@ -270,7 +269,7 @@ class CategoryViewSet(EvibesViewSet): def seo_meta(self, request: Request, *args, **kwargs) -> Response: category = self.get_object() - title = f"{category.name} | {config.PROJECT_NAME}" + title = f"{category.name} | {settings.PROJECT_NAME}" description = (category.description or "")[:180] canonical = f"https://{settings.BASE_DOMAIN}/{settings.LANGUAGE_CODE}/catalog/{category.slug}" og_image = request.build_absolute_uri(category.image.url) if getattr(category, "image", None) else "" @@ -387,7 +386,7 @@ class BrandViewSet(EvibesViewSet): def seo_meta(self, request: Request, *args, **kwargs) -> Response: brand = self.get_object() - title = f"{brand.name} | {config.PROJECT_NAME}" + title = f"{brand.name} | {settings.PROJECT_NAME}" description = (brand.description or "")[:180] canonical = f"https://{settings.BASE_DOMAIN}/{settings.LANGUAGE_CODE}/brand/{brand.slug}" @@ -529,7 +528,7 @@ class ProductViewSet(EvibesViewSet): p = self.get_object() images = list(p.images.all()[:6]) rating = {"value": p.rating, "count": p.feedbacks_count} - title = f"{p.name} | {config.PROJECT_NAME}" + title = f"{p.name} | {settings.PROJECT_NAME}" description = (p.description or "")[:180] canonical = f"https://{settings.BASE_DOMAIN}/{settings.LANGUAGE_CODE}/product/{p.slug}" og = { diff --git a/engine/payments/utils/emailing.py b/engine/payments/utils/emailing.py index 3e5af9d4..08e35089 100644 --- a/engine/payments/utils/emailing.py +++ b/engine/payments/utils/emailing.py @@ -2,6 +2,7 @@ from datetime import datetime from celery.app import shared_task from constance import config +from django.conf import settings from django.core.mail import EmailMessage from django.template.loader import render_to_string from django.utils.translation import activate @@ -24,20 +25,20 @@ def balance_deposit_email(transaction_pk: str) -> tuple[bool, str]: activate(transaction.balance.user.language) email = EmailMessage( - _(f"{config.PROJECT_NAME} | balance deposit"), + _(f"{settings.PROJECT_NAME} | balance deposit"), render_to_string( template_name="../templates/balance_deposit_email.html", context={ "amount": transaction.amount, "balance": transaction.balance.amount, "user_first_name": transaction.balance.user.first_name, - "project_name": config.PROJECT_NAME, + "project_name": settings.PROJECT_NAME, "contact_email": config.EMAIL_FROM, "today": datetime.today(), }, ), to=[transaction.balance.user.email], - from_email=f"{config.PROJECT_NAME} <{config.EMAIL_FROM}>", + from_email=f"{settings.PROJECT_NAME} <{config.EMAIL_FROM}>", connection=get_dynamic_email_connection(), ) email.content_subtype = "html" diff --git a/engine/vibes_auth/utils/emailing.py b/engine/vibes_auth/utils/emailing.py index 71bf33b8..ce8a9d1c 100644 --- a/engine/vibes_auth/utils/emailing.py +++ b/engine/vibes_auth/utils/emailing.py @@ -21,21 +21,21 @@ def send_verification_email_task(user_pk: str) -> tuple[bool, str]: activate(user.language) - email_subject = _(f"{config.PROJECT_NAME} | Activate Account") + email_subject = _(f"{settings.PROJECT_NAME} | Activate Account") email_body = render_to_string( "../templates/user_verification_email.html", { "user_first_name": user.first_name, "activation_link": f"https://{settings.STOREFRONT_DOMAIN}/{user.language}/activate-user?uid={urlsafe_base64_encode(force_bytes(user.uuid))}" f"&token={urlsafe_base64_encode(force_bytes(user.activation_token))}", - "project_name": config.PROJECT_NAME, + "project_name": settings.PROJECT_NAME, }, ) email = EmailMessage( subject=email_subject, body=email_body, - from_email=f"{config.PROJECT_NAME} <{config.EMAIL_FROM}>", + from_email=f"{settings.PROJECT_NAME} <{config.EMAIL_FROM}>", to=[user.email], connection=get_dynamic_email_connection(), ) @@ -60,7 +60,7 @@ def send_reset_password_email_task(user_pk: str) -> tuple[bool, str]: activate(user.language) - email_subject = _(f"{config.PROJECT_NAME} | Reset Password") + email_subject = _(f"{settings.PROJECT_NAME} | Reset Password") email_body = render_to_string( "../templates/user_reset_password_email.html", { @@ -68,14 +68,14 @@ def send_reset_password_email_task(user_pk: str) -> tuple[bool, str]: "reset_link": f"https://{settings.STOREFRONT_DOMAIN}/{user.language}/reset-password?uid=" f"{urlsafe_base64_encode(force_bytes(user.pk))}" f"&token={PasswordResetTokenGenerator().make_token(user)}", - "project_name": config.PROJECT_NAME, + "project_name": settings.PROJECT_NAME, }, ) email = EmailMessage( subject=email_subject, body=email_body, - from_email=f"{config.PROJECT_NAME} <{config.EMAIL_FROM}>", + from_email=f"{settings.PROJECT_NAME} <{config.EMAIL_FROM}>", to=[user.email], connection=get_dynamic_email_connection(), ) diff --git a/evibes/settings/__init__.py b/evibes/settings/__init__.py index 86adbb63..08a3c856 100644 --- a/evibes/settings/__init__.py +++ b/evibes/settings/__init__.py @@ -9,6 +9,6 @@ from .elasticsearch import * # noqa: F403 from .emailing import * # noqa: F403 from .extensions import * # noqa: F403 from .graphene import * # noqa: F403 -from .jazzmin import * # noqa: F403 +from .unfold import * # noqa: F403 from .logconfig import * # noqa: F403 from .summernote import * # noqa: F403 diff --git a/evibes/settings/base.py b/evibes/settings/base.py index 76e6a0a2..aae74e7f 100644 --- a/evibes/settings/base.py +++ b/evibes/settings/base.py @@ -9,6 +9,8 @@ from django.core.exceptions import ImproperlyConfigured EVIBES_VERSION = "2025.4" RELEASE_DATE = datetime(2025, 11, 9) +PROJECT_NAME = getenv("EVIBES_PROJECT_NAME", "eVibes") + BASE_DIR: Path = Path(__file__).resolve().parent.parent.parent INITIALIZED: bool = (BASE_DIR / ".initialized").exists() @@ -103,7 +105,9 @@ SITE_ID: int = 1 INSTALLED_APPS: list[str] = [ "django_prometheus", "constance", - "jazzmin", + "unfold", + "unfold.contrib.filters", + "unfold.contrib.forms", "modeltranslation", "django.contrib.admin", "django.contrib.admindocs", diff --git a/evibes/settings/constance.py b/evibes/settings/constance.py index 5181f7ea..b511e3d7 100644 --- a/evibes/settings/constance.py +++ b/evibes/settings/constance.py @@ -1,8 +1,7 @@ from collections import OrderedDict from os import getenv -from django.utils.translation import gettext_lazy as _ -from django.utils.translation import gettext_noop +from django.utils.translation import gettext_noop as _ CONSTANCE_BACKEND = "constance.backends.database.DatabaseBackend" CONSTANCE_SUPERUSER_ONLY = False @@ -19,11 +18,11 @@ CONSTANCE_ADDITIONAL_FIELDS = { CONSTANCE_CONFIG = OrderedDict( [ - ### General Options ### - ("PROJECT_NAME", (getenv("EVIBES_PROJECT_NAME"), _("Name of the project"))), + ### Legal Options ### ("COMPANY_NAME", (getenv("COMPANY_NAME"), _("Name of the company"))), ("COMPANY_ADDRESS", (getenv("COMPANY_ADDRESS"), _("Address of the company"))), ("COMPANY_PHONE_NUMBER", (getenv("COMPANY_PHONE_NUMBER"), _("Phone number of the company"))), + ("TAX_RATE", (0, _("Tax rate in jurisdiction of your company. Leave 0 if you don't want to process taxes."))), ("EXCHANGE_RATE_API_KEY", (getenv("EXCHANGE_RATE_API_KEY", "example token"), _("Exchange rate API key"))), ### Email Options ### ("EMAIL_BACKEND", ("django.core.mail.backends.smtp.EmailBackend", _("!!!DO NOT CHANGE!!!"))), @@ -52,14 +51,14 @@ CONSTANCE_CONFIG = OrderedDict( CONSTANCE_CONFIG_FIELDSETS = OrderedDict( { - gettext_noop("General Options"): ( - "PROJECT_NAME", + _("Legal Options"): ( "COMPANY_NAME", "COMPANY_ADDRESS", "COMPANY_PHONE_NUMBER", + "TAX_RATE", "EXCHANGE_RATE_API_KEY", ), - gettext_noop("Email Options"): ( + _("Email Options"): ( "EMAIL_BACKEND", "EMAIL_HOST", "EMAIL_PORT", @@ -69,7 +68,7 @@ CONSTANCE_CONFIG_FIELDSETS = OrderedDict( "EMAIL_HOST_PASSWORD", "EMAIL_FROM", ), - gettext_noop("Features Options"): ( + _("Features Options"): ( "DAYS_TO_STORE_ANON_MSGS", "DAYS_TO_STORE_AUTH_MSGS", "DISABLED_COMMERCE", @@ -78,16 +77,15 @@ CONSTANCE_CONFIG_FIELDSETS = OrderedDict( "ABSTRACT_API_KEY", "HTTP_PROXY", ), - gettext_noop("SEO Options"): ( + _("SEO Options"): ( "ADVERTSIMENT", "ANALYTICS", ), - gettext_noop("Debugging Options"): ("SAVE_VENDORS_RESPONSES",), + _("Debugging Options"): ("SAVE_VENDORS_RESPONSES",), } ) EXPOSABLE_KEYS = [ - "PROJECT_NAME", "COMPANY_NAME", "COMPANY_ADDRESS", "COMPANY_PHONE_NUMBER", diff --git a/evibes/settings/drf.py b/evibes/settings/drf.py index 10710ce8..42a7f6c5 100644 --- a/evibes/settings/drf.py +++ b/evibes/settings/drf.py @@ -3,8 +3,7 @@ from os import getenv from django.utils.translation import gettext_lazy as _ -from evibes.settings.base import DEBUG, EVIBES_VERSION, SECRET_KEY, BASE_DOMAIN -from evibes.settings.constance import CONSTANCE_CONFIG +from evibes.settings.base import BASE_DOMAIN, DEBUG, EVIBES_VERSION, PROJECT_NAME, SECRET_KEY REST_FRAMEWORK: dict[str, str | int | list[str] | tuple[str, ...] | dict[str, bool]] = { "DEFAULT_PAGINATION_CLASS": "evibes.pagination.CustomPagination", @@ -99,7 +98,7 @@ Current API version: {EVIBES_VERSION} SPECTACULAR_SETTINGS = { "DEFAULT_GENERATOR_CLASS": "drf_spectacular_websocket.schemas.WsSchemaGenerator", - "TITLE": f"{CONSTANCE_CONFIG.get('PROJECT_NAME')[0]} API", # type: ignore [index] + "TITLE": f"{PROJECT_NAME} API", # type: ignore [index] "DESCRIPTION": SPECTACULAR_DESCRIPTION, "VERSION": EVIBES_VERSION, # noqa: F405 "TOS": "https://evibes.wiseless.xyz/terms-of-service", diff --git a/evibes/settings/jazzmin.py b/evibes/settings/jazzmin.py index 13adca84..44539e91 100644 --- a/evibes/settings/jazzmin.py +++ b/evibes/settings/jazzmin.py @@ -1,84 +1,11 @@ -from django.utils.translation import gettext_lazy as _ +""" +Deprecated: Jazzmin settings (removed in favor of django-unfold). -from evibes.settings.base import EVIBES_VERSION, STOREFRONT_DOMAIN -from evibes.settings.constance import CONSTANCE_CONFIG +This file is intentionally left as a stub to avoid accidental imports. +If imported, raise an explicit error guiding developers to Unfold. +""" -JAZZMIN_SETTINGS = { - "site_title": f"{CONSTANCE_CONFIG.get('PROJECT_NAME')[0]} Admin", # type: ignore [index] - "site_header": str(CONSTANCE_CONFIG.get("PROJECT_NAME")[0]), # type: ignore [index] - "site_brand": str(CONSTANCE_CONFIG.get("PROJECT_NAME")[0]), # type: ignore [index] - "site_logo": "logo.png", - "login_logo": "logo.png", - "login_logo_dark": "logo.png", - "site_logo_classes": "", - "site_icon": "favicon.ico", - "welcome_sign": _("Whoa! Only admins allowed here!"), - "copyright": f"eVibes {EVIBES_VERSION} by Wiseless", - "search_model": None, - "user_avatar": "avatar", - "topmenu_links": [ - { - "name": _("Home"), - "url": "admin:index", - "new_window": False, - }, - { - "name": _("Storefront"), - "url": f"https://{STOREFRONT_DOMAIN}", - "new_window": False, - }, - { - "name": "GraphQL Docs", - "url": "graphql-platform", - "new_window": False, - }, - { - "name": "Swagger", - "url": "swagger-ui-platform", - "new_window": False, - }, - { - "name": "Redoc", - "url": "redoc-ui-platform", - "new_window": False, - }, - { - "name": _("Taskboard"), - "url": "https://plane.wiseless.xyz/spaces/issues/dd33cb0ab9b04ef08a10f7eefae6d90c/?board=kanban", - "new_window": True, - }, - { - "name": "GitLab", - "url": "https://gitlab.com/wiseless/evibes", - "new_window": True, - }, - { - "name": _("Support"), - "url": "https://t.me/fureunoir", - "new_window": True, - }, - ], - "usermenu_links": [], - "show_sidebar": True, - "navigation_expanded": True, - "hide_apps": ["django_celery_results", ""], - "hide_models": [], - "order_with_respect_to": ["vibes_auth", "core", "payments", "blog"], - "icons": { - "vibes_auth": "fas fa-users-cog", - "vibes_auth.user": "fas fa-user", - "vibes_auth.Group": "fas fa-users", - }, - "default_icon_parents": "fas fa-chevron-circle-right", - "default_icon_children": "fas fa-circle", - "related_modal_active": False, - "use_google_fonts_cdn": True, - "show_ui_builder": True, - "changeform_format": "horizontal_tabs", - "language_chooser": True, -} - -JAZZMIN_UI_TWEAKS = { - "theme": "flatly", - "dark_mode_theme": "darkly", -} +raise ImportError( + "Jazzmin configuration has been removed. Use django-unfold instead. " + "See evibes/settings/unfold.py and INSTALLED_APPS in evibes/settings/base.py." +) diff --git a/evibes/settings/unfold.py b/evibes/settings/unfold.py new file mode 100644 index 00000000..4fe4feaa --- /dev/null +++ b/evibes/settings/unfold.py @@ -0,0 +1,28 @@ +"""django-unfold configuration. + +This module defines branding for the Django admin using django-unfold. +It intentionally avoids database-backed configuration (e.g., Constance) +so that it is safe during initial migrations and in all environments. +""" + +from evibes.settings.base import PROJECT_NAME + +# See django-unfold documentation for all available options. +# Only minimal, production-safe branding is configured here. +UNFOLD = { + # Text shown in the browser title bar and in the admin header + "SITE_TITLE": f"{PROJECT_NAME} Admin", + "SITE_HEADER": PROJECT_NAME, + # Optional URL the header/brand links to (leave default admin index) + # "SITE_URL": "/admin/", + # Logos and favicon served via Django staticfiles + # Files are expected at: engine/core/static/logo.png, favicon.ico, favicon.png + # Refer to them by their static URL path (relative), no leading slash. + "SITE_LOGO": "logo.png", + # If you use a different logo for dark theme, set SITE_LOGO_DARK + # Otherwise Unfold will reuse SITE_LOGO + # "SITE_LOGO_DARK": "logo.png", + "SITE_ICON": "favicon.ico", + # Sidebar behavior, search etc. (keep defaults minimal) + # Unfold automatically respects user OS light/dark theme; no forcing here. +} diff --git a/pyproject.toml b/pyproject.toml index 10cf4a8c..9c213475 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,6 @@ dependencies = [ "django-extensions==4.1", "django-filter==25.2", "django-health-check==3.20.0", - "django-jazzmin==3.0.1", "django-json-widget==2.1.0", "django-mailbox==4.10.1", "django-model-utils==5.0.0", @@ -35,6 +34,7 @@ dependencies = [ "django-storages==1.14.6", "django-stubs==5.2.7", "django-summernote==0.8.20.0", + "django-unfold>=0.71.0", "django-widget-tweaks==1.5.0", "django-md-field==0.1.0", "djangorestframework==3.16.1", @@ -87,7 +87,7 @@ linting = [ "isort==7.0.0", "mypy==1.18.2", "mypy-extensions==1.1.0", - "ruff==0.14.4", + "ruff==0.14.5", "celery-stubs==0.1.3", ] openai = ["openai==2.6.1"] diff --git a/supervisor/package.json b/supervisor/package.json deleted file mode 100644 index 7ec53ad4..00000000 --- a/supervisor/package.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "supervisor", - "version": "1.0.0", - "description": "Supervisor is a custom dashboard application for eVibes", - "main": "index.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "author": "WISELESS team", - "license": "../LICENSE" -} diff --git a/uv.lock b/uv.lock index f0832e8d..28e37571 100644 --- a/uv.lock +++ b/uv.lock @@ -874,18 +874,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e1/7f/49ba63f078015b0a52e09651b94ba16b41154ac7079c83153edd14e15ca0/django_health_check-3.20.0-py3-none-any.whl", hash = "sha256:bcb2b8f36f463cead0564a028345c5b17e2a2d18e9cc88ecd611b13a26521926", size = 31788, upload-time = "2025-06-16T09:22:32.069Z" }, ] -[[package]] -name = "django-jazzmin" -version = "3.0.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "django" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/84/96/21b6255e90d92a3eb4e93bea9376635d54258e0353ebb913a55e40ae9254/django_jazzmin-3.0.1.tar.gz", hash = "sha256:67ae148bade41267a09ca8e4352ddefa6121795ebbac238bb9a6564ff841eb1b", size = 2053550, upload-time = "2024-10-08T17:40:59.771Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ad/5b/2f8c4b168e6c41bf1e4b14d787deb23d80f618f0693db913bbe208a4a907/django_jazzmin-3.0.1-py3-none-any.whl", hash = "sha256:12a0a4c1d4fd09c2eef22acf6a1f03112b515ba695c59faa8ea80efc81c1f21b", size = 2125957, upload-time = "2024-10-08T17:40:57.359Z" }, -] - [[package]] name = "django-js-asset" version = "3.1.2" @@ -1058,6 +1046,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/09/7a808392a751a24ffa62bec00e3085a9c1a151d728c323a5bab229ea0e58/django_timezone_field-7.1-py3-none-any.whl", hash = "sha256:93914713ed882f5bccda080eda388f7006349f25930b6122e9b07bf8db49c4b4", size = 13177, upload-time = "2025-01-11T17:49:52.142Z" }, ] +[[package]] +name = "django-unfold" +version = "0.71.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "django" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ec/5b/406eae1a429b15ba04f4dfaaf53aa64fb03bcfdc6bdd0753a41027aa3daa/django_unfold-0.71.0.tar.gz", hash = "sha256:995a296f1c15f172b0d8458ff12beb420ea7e9a666fa865a60ec03f70aaf4066", size = 1101347, upload-time = "2025-11-11T16:24:03.289Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/94/ad8ba84410655e0207ffcc7c6cba0875e9f79f914e3f2e2de883f706f7c9/django_unfold-0.71.0-py3-none-any.whl", hash = "sha256:76d4019aa9052ebe2e040d868be895d8581018fdf7debca943084aa0e79c2e31", size = 1213722, upload-time = "2025-11-11T16:24:01.985Z" }, +] + [[package]] name = "django-widget-tweaks" version = "1.5.0" @@ -1289,7 +1289,6 @@ dependencies = [ { name = "django-extensions" }, { name = "django-filter" }, { name = "django-health-check" }, - { name = "django-jazzmin" }, { name = "django-json-widget" }, { name = "django-mailbox" }, { name = "django-md-field" }, @@ -1302,6 +1301,7 @@ dependencies = [ { name = "django-storages" }, { name = "django-stubs" }, { name = "django-summernote" }, + { name = "django-unfold" }, { name = "django-widget-tweaks" }, { name = "djangorestframework" }, { name = "djangorestframework-camel-case" }, @@ -1394,7 +1394,6 @@ requires-dist = [ { name = "django-extensions", specifier = "==4.1" }, { name = "django-filter", specifier = "==25.2" }, { name = "django-health-check", specifier = "==3.20.0" }, - { name = "django-jazzmin", specifier = "==3.0.1" }, { name = "django-json-widget", specifier = "==2.1.0" }, { name = "django-mailbox", specifier = "==4.10.1" }, { name = "django-md-field", specifier = "==0.1.0" }, @@ -1407,6 +1406,7 @@ requires-dist = [ { name = "django-storages", specifier = "==1.14.6" }, { name = "django-stubs", specifier = "==5.2.7" }, { name = "django-summernote", specifier = "==0.8.20.0" }, + { name = "django-unfold", specifier = ">=0.71.0" }, { name = "django-widget-tweaks", specifier = "==1.5.0" }, { name = "djangorestframework", specifier = "==3.16.1" }, { name = "djangorestframework-camel-case", specifier = "==1.4.2" }, @@ -1444,7 +1444,7 @@ requires-dist = [ { name = "python-slugify", specifier = "==8.0.4" }, { name = "redis", specifier = "==7.0.1" }, { name = "requests", specifier = "==2.32.5" }, - { name = "ruff", marker = "extra == 'linting'", specifier = "==0.14.4" }, + { name = "ruff", marker = "extra == 'linting'", specifier = "==0.14.5" }, { name = "sentry-sdk", extras = ["django", "celery", "opentelemetry"], specifier = "==2.44.0" }, { name = "six", specifier = "==1.17.0" }, { name = "sphinx", marker = "extra == 'docs'", specifier = "==8.2.3" }, @@ -3345,28 +3345,28 @@ wheels = [ [[package]] name = "ruff" -version = "0.14.4" +version = "0.14.5" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/df/55/cccfca45157a2031dcbb5a462a67f7cf27f8b37d4b3b1cd7438f0f5c1df6/ruff-0.14.4.tar.gz", hash = "sha256:f459a49fe1085a749f15414ca76f61595f1a2cc8778ed7c279b6ca2e1fd19df3", size = 5587844, upload-time = "2025-11-06T22:07:45.033Z" } +sdist = { url = "https://files.pythonhosted.org/packages/82/fa/fbb67a5780ae0f704876cb8ac92d6d76da41da4dc72b7ed3565ab18f2f52/ruff-0.14.5.tar.gz", hash = "sha256:8d3b48d7d8aad423d3137af7ab6c8b1e38e4de104800f0d596990f6ada1a9fc1", size = 5615944, upload-time = "2025-11-13T19:58:51.155Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/17/b9/67240254166ae1eaa38dec32265e9153ac53645a6c6670ed36ad00722af8/ruff-0.14.4-py3-none-linux_armv6l.whl", hash = "sha256:e6604613ffbcf2297cd5dcba0e0ac9bd0c11dc026442dfbb614504e87c349518", size = 12606781, upload-time = "2025-11-06T22:07:01.841Z" }, - { url = "https://files.pythonhosted.org/packages/46/c8/09b3ab245d8652eafe5256ab59718641429f68681ee713ff06c5c549f156/ruff-0.14.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d99c0b52b6f0598acede45ee78288e5e9b4409d1ce7f661f0fa36d4cbeadf9a4", size = 12946765, upload-time = "2025-11-06T22:07:05.858Z" }, - { url = "https://files.pythonhosted.org/packages/14/bb/1564b000219144bf5eed2359edc94c3590dd49d510751dad26202c18a17d/ruff-0.14.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:9358d490ec030f1b51d048a7fd6ead418ed0826daf6149e95e30aa67c168af33", size = 11928120, upload-time = "2025-11-06T22:07:08.023Z" }, - { url = "https://files.pythonhosted.org/packages/a3/92/d5f1770e9988cc0742fefaa351e840d9aef04ec24ae1be36f333f96d5704/ruff-0.14.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81b40d27924f1f02dfa827b9c0712a13c0e4b108421665322218fc38caf615c2", size = 12370877, upload-time = "2025-11-06T22:07:10.015Z" }, - { url = "https://files.pythonhosted.org/packages/e2/29/e9282efa55f1973d109faf839a63235575519c8ad278cc87a182a366810e/ruff-0.14.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f5e649052a294fe00818650712083cddc6cc02744afaf37202c65df9ea52efa5", size = 12408538, upload-time = "2025-11-06T22:07:13.085Z" }, - { url = "https://files.pythonhosted.org/packages/8e/01/930ed6ecfce130144b32d77d8d69f5c610e6d23e6857927150adf5d7379a/ruff-0.14.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa082a8f878deeba955531f975881828fd6afd90dfa757c2b0808aadb437136e", size = 13141942, upload-time = "2025-11-06T22:07:15.386Z" }, - { url = "https://files.pythonhosted.org/packages/6a/46/a9c89b42b231a9f487233f17a89cbef9d5acd538d9488687a02ad288fa6b/ruff-0.14.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:1043c6811c2419e39011890f14d0a30470f19d47d197c4858b2787dfa698f6c8", size = 14544306, upload-time = "2025-11-06T22:07:17.631Z" }, - { url = "https://files.pythonhosted.org/packages/78/96/9c6cf86491f2a6d52758b830b89b78c2ae61e8ca66b86bf5a20af73d20e6/ruff-0.14.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a9f3a936ac27fb7c2a93e4f4b943a662775879ac579a433291a6f69428722649", size = 14210427, upload-time = "2025-11-06T22:07:19.832Z" }, - { url = "https://files.pythonhosted.org/packages/71/f4/0666fe7769a54f63e66404e8ff698de1dcde733e12e2fd1c9c6efb689cb5/ruff-0.14.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:95643ffd209ce78bc113266b88fba3d39e0461f0cbc8b55fb92505030fb4a850", size = 13658488, upload-time = "2025-11-06T22:07:22.32Z" }, - { url = "https://files.pythonhosted.org/packages/ee/79/6ad4dda2cfd55e41ac9ed6d73ef9ab9475b1eef69f3a85957210c74ba12c/ruff-0.14.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:456daa2fa1021bc86ca857f43fe29d5d8b3f0e55e9f90c58c317c1dcc2afc7b5", size = 13354908, upload-time = "2025-11-06T22:07:24.347Z" }, - { url = "https://files.pythonhosted.org/packages/b5/60/f0b6990f740bb15c1588601d19d21bcc1bd5de4330a07222041678a8e04f/ruff-0.14.4-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:f911bba769e4a9f51af6e70037bb72b70b45a16db5ce73e1f72aefe6f6d62132", size = 13587803, upload-time = "2025-11-06T22:07:26.327Z" }, - { url = "https://files.pythonhosted.org/packages/c9/da/eaaada586f80068728338e0ef7f29ab3e4a08a692f92eb901a4f06bbff24/ruff-0.14.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:76158a7369b3979fa878612c623a7e5430c18b2fd1c73b214945c2d06337db67", size = 12279654, upload-time = "2025-11-06T22:07:28.46Z" }, - { url = "https://files.pythonhosted.org/packages/66/d4/b1d0e82cf9bf8aed10a6d45be47b3f402730aa2c438164424783ac88c0ed/ruff-0.14.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f3b8f3b442d2b14c246e7aeca2e75915159e06a3540e2f4bed9f50d062d24469", size = 12357520, upload-time = "2025-11-06T22:07:31.468Z" }, - { url = "https://files.pythonhosted.org/packages/04/f4/53e2b42cc82804617e5c7950b7079d79996c27e99c4652131c6a1100657f/ruff-0.14.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c62da9a06779deecf4d17ed04939ae8b31b517643b26370c3be1d26f3ef7dbde", size = 12719431, upload-time = "2025-11-06T22:07:33.831Z" }, - { url = "https://files.pythonhosted.org/packages/a2/94/80e3d74ed9a72d64e94a7b7706b1c1ebaa315ef2076fd33581f6a1cd2f95/ruff-0.14.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5a443a83a1506c684e98acb8cb55abaf3ef725078be40237463dae4463366349", size = 13464394, upload-time = "2025-11-06T22:07:35.905Z" }, - { url = "https://files.pythonhosted.org/packages/54/1a/a49f071f04c42345c793d22f6cf5e0920095e286119ee53a64a3a3004825/ruff-0.14.4-py3-none-win32.whl", hash = "sha256:643b69cb63cd996f1fc7229da726d07ac307eae442dd8974dbc7cf22c1e18fff", size = 12493429, upload-time = "2025-11-06T22:07:38.43Z" }, - { url = "https://files.pythonhosted.org/packages/bc/22/e58c43e641145a2b670328fb98bc384e20679b5774258b1e540207580266/ruff-0.14.4-py3-none-win_amd64.whl", hash = "sha256:26673da283b96fe35fa0c939bf8411abec47111644aa9f7cfbd3c573fb125d2c", size = 13635380, upload-time = "2025-11-06T22:07:40.496Z" }, - { url = "https://files.pythonhosted.org/packages/30/bd/4168a751ddbbf43e86544b4de8b5c3b7be8d7167a2a5cb977d274e04f0a1/ruff-0.14.4-py3-none-win_arm64.whl", hash = "sha256:dd09c292479596b0e6fec8cd95c65c3a6dc68e9ad17b8f2382130f87ff6a75bb", size = 12663065, upload-time = "2025-11-06T22:07:42.603Z" }, + { url = "https://files.pythonhosted.org/packages/68/31/c07e9c535248d10836a94e4f4e8c5a31a1beed6f169b31405b227872d4f4/ruff-0.14.5-py3-none-linux_armv6l.whl", hash = "sha256:f3b8248123b586de44a8018bcc9fefe31d23dda57a34e6f0e1e53bd51fd63594", size = 13171630, upload-time = "2025-11-13T19:57:54.894Z" }, + { url = "https://files.pythonhosted.org/packages/8e/5c/283c62516dca697cd604c2796d1487396b7a436b2f0ecc3fd412aca470e0/ruff-0.14.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:f7a75236570318c7a30edd7f5491945f0169de738d945ca8784500b517163a72", size = 13413925, upload-time = "2025-11-13T19:57:59.181Z" }, + { url = "https://files.pythonhosted.org/packages/b6/f3/aa319f4afc22cb6fcba2b9cdfc0f03bbf747e59ab7a8c5e90173857a1361/ruff-0.14.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6d146132d1ee115f8802356a2dc9a634dbf58184c51bff21f313e8cd1c74899a", size = 12574040, upload-time = "2025-11-13T19:58:02.056Z" }, + { url = "https://files.pythonhosted.org/packages/f9/7f/cb5845fcc7c7e88ed57f58670189fc2ff517fe2134c3821e77e29fd3b0c8/ruff-0.14.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2380596653dcd20b057794d55681571a257a42327da8894b93bbd6111aa801f", size = 13009755, upload-time = "2025-11-13T19:58:05.172Z" }, + { url = "https://files.pythonhosted.org/packages/21/d2/bcbedbb6bcb9253085981730687ddc0cc7b2e18e8dc13cf4453de905d7a0/ruff-0.14.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2d1fa985a42b1f075a098fa1ab9d472b712bdb17ad87a8ec86e45e7fa6273e68", size = 12937641, upload-time = "2025-11-13T19:58:08.345Z" }, + { url = "https://files.pythonhosted.org/packages/a4/58/e25de28a572bdd60ffc6bb71fc7fd25a94ec6a076942e372437649cbb02a/ruff-0.14.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88f0770d42b7fa02bbefddde15d235ca3aa24e2f0137388cc15b2dcbb1f7c7a7", size = 13610854, upload-time = "2025-11-13T19:58:11.419Z" }, + { url = "https://files.pythonhosted.org/packages/7d/24/43bb3fd23ecee9861970978ea1a7a63e12a204d319248a7e8af539984280/ruff-0.14.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:3676cb02b9061fee7294661071c4709fa21419ea9176087cb77e64410926eb78", size = 15061088, upload-time = "2025-11-13T19:58:14.551Z" }, + { url = "https://files.pythonhosted.org/packages/23/44/a022f288d61c2f8c8645b24c364b719aee293ffc7d633a2ca4d116b9c716/ruff-0.14.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b595bedf6bc9cab647c4a173a61acf4f1ac5f2b545203ba82f30fcb10b0318fb", size = 14734717, upload-time = "2025-11-13T19:58:17.518Z" }, + { url = "https://files.pythonhosted.org/packages/58/81/5c6ba44de7e44c91f68073e0658109d8373b0590940efe5bd7753a2585a3/ruff-0.14.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f55382725ad0bdb2e8ee2babcbbfb16f124f5a59496a2f6a46f1d9d99d93e6e2", size = 14028812, upload-time = "2025-11-13T19:58:20.533Z" }, + { url = "https://files.pythonhosted.org/packages/ad/ef/41a8b60f8462cb320f68615b00299ebb12660097c952c600c762078420f8/ruff-0.14.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7497d19dce23976bdaca24345ae131a1d38dcfe1b0850ad8e9e6e4fa321a6e19", size = 13825656, upload-time = "2025-11-13T19:58:23.345Z" }, + { url = "https://files.pythonhosted.org/packages/7c/00/207e5de737fdb59b39eb1fac806904fe05681981b46d6a6db9468501062e/ruff-0.14.5-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:410e781f1122d6be4f446981dd479470af86537fb0b8857f27a6e872f65a38e4", size = 13959922, upload-time = "2025-11-13T19:58:26.537Z" }, + { url = "https://files.pythonhosted.org/packages/bc/7e/fa1f5c2776db4be405040293618846a2dece5c70b050874c2d1f10f24776/ruff-0.14.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c01be527ef4c91a6d55e53b337bfe2c0f82af024cc1a33c44792d6844e2331e1", size = 12932501, upload-time = "2025-11-13T19:58:29.822Z" }, + { url = "https://files.pythonhosted.org/packages/67/d8/d86bf784d693a764b59479a6bbdc9515ae42c340a5dc5ab1dabef847bfaa/ruff-0.14.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f66e9bb762e68d66e48550b59c74314168ebb46199886c5c5aa0b0fbcc81b151", size = 12927319, upload-time = "2025-11-13T19:58:32.923Z" }, + { url = "https://files.pythonhosted.org/packages/ac/de/ee0b304d450ae007ce0cb3e455fe24fbcaaedae4ebaad6c23831c6663651/ruff-0.14.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d93be8f1fa01022337f1f8f3bcaa7ffee2d0b03f00922c45c2207954f351f465", size = 13206209, upload-time = "2025-11-13T19:58:35.952Z" }, + { url = "https://files.pythonhosted.org/packages/33/aa/193ca7e3a92d74f17d9d5771a765965d2cf42c86e6f0fd95b13969115723/ruff-0.14.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:c135d4b681f7401fe0e7312017e41aba9b3160861105726b76cfa14bc25aa367", size = 13953709, upload-time = "2025-11-13T19:58:39.002Z" }, + { url = "https://files.pythonhosted.org/packages/cc/f1/7119e42aa1d3bf036ffc9478885c2e248812b7de9abea4eae89163d2929d/ruff-0.14.5-py3-none-win32.whl", hash = "sha256:c83642e6fccfb6dea8b785eb9f456800dcd6a63f362238af5fc0c83d027dd08b", size = 12925808, upload-time = "2025-11-13T19:58:42.779Z" }, + { url = "https://files.pythonhosted.org/packages/3b/9d/7c0a255d21e0912114784e4a96bf62af0618e2190cae468cd82b13625ad2/ruff-0.14.5-py3-none-win_amd64.whl", hash = "sha256:9d55d7af7166f143c94eae1db3312f9ea8f95a4defef1979ed516dbb38c27621", size = 14331546, upload-time = "2025-11-13T19:58:45.691Z" }, + { url = "https://files.pythonhosted.org/packages/e5/80/69756670caedcf3b9be597a6e12276a6cf6197076eb62aad0c608f8efce0/ruff-0.14.5-py3-none-win_arm64.whl", hash = "sha256:4b700459d4649e2594b31f20a9de33bc7c19976d4746d8d0798ad959621d64a4", size = 13433331, upload-time = "2025-11-13T19:58:48.434Z" }, ] [[package]]