diff --git a/sage_tools/fields/__init__.py b/sage_tools/fields/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sage_tools/fields/svg.py b/sage_tools/fields/svg.py new file mode 100644 index 0000000..635e239 --- /dev/null +++ b/sage_tools/fields/svg.py @@ -0,0 +1,53 @@ +import os + +from django.contrib.admin.widgets import AdminFileWidget +from django.core.validators import FileExtensionValidator +from django.db import models + +from sage_tools.widgets import SVGWidget + + +class SVGField(models.FileField): + """Custom field for handling SVG files.""" + + def __init__(self, *args, **kwargs): + # Add the SVG file extension validator by default + svg_validator = FileExtensionValidator(allowed_extensions=["svg"]) + validators = kwargs.pop("validators", []) + validators.append(svg_validator) + self.url = None + super().__init__(*args, validators=validators, **kwargs) + + def deconstruct(self): + name, path, args, kwargs = super().deconstruct() + if "validators" in kwargs: + validators = kwargs["validators"] + if validators and isinstance(validators[-1], FileExtensionValidator): + validators = validators[:-1] + kwargs["validators"] = validators + return name, path, args, kwargs + + def from_db_value(self, value, expression, connection): + if value and os.path.exists(value): + self.url = value + with open(value, "r") as svg_file: + return svg_file.read() + return "" + + def to_python(self, value): + """Ensure the value is converted to the SVG content.""" + if isinstance(value, str) and os.path.exists(value): + return value + return value + + def formfield(self, **kwargs): + """Override formfield to manipulate the value and ensure it behaves like a FileField.""" + kwargs["widget"] = SVGWidget(url=self.url) + return super().formfield(**kwargs) + + def value_to_string(self, obj): + """Ensure serialization returns the file path.""" + value = self.value_from_object(obj) + if value: + return self.url + return "" diff --git a/sage_tools/middlewares/time_zone.py b/sage_tools/middlewares/time_zone.py index 745d5eb..24cb56d 100644 --- a/sage_tools/middlewares/time_zone.py +++ b/sage_tools/middlewares/time_zone.py @@ -1,9 +1,9 @@ import logging -from django.conf import settings - from typing import Any -from django.utils import timezone + +from django.conf import settings from django.http import HttpRequest +from django.utils import timezone from django.utils.deprecation import MiddlewareMixin from sage_tools.handlers.session import SessionHandler diff --git a/sage_tools/mixins/forms/__init__.py b/sage_tools/mixins/forms/__init__.py index 7b8ebb1..ffcc8ca 100644 --- a/sage_tools/mixins/forms/__init__.py +++ b/sage_tools/mixins/forms/__init__.py @@ -1,9 +1,9 @@ from .user import ( - UserFormKwargsMixin, - MessageMixin, - FormValidMessageMixin, FormInvalidMessageMixin, FormMessagesMixin, + FormValidMessageMixin, + MessageMixin, + UserFormKwargsMixin, UserKwargModelFormMixin, ) diff --git a/sage_tools/mixins/models/__init__.py b/sage_tools/mixins/models/__init__.py index 23912cd..cc5c6d6 100644 --- a/sage_tools/mixins/models/__init__.py +++ b/sage_tools/mixins/models/__init__.py @@ -1,17 +1,16 @@ +from .access import SingletonModelMixin +from .address import AddressMixin from .base import ( - TimeStampMixin, - UUIDBaseModel, - StockUnitMixin, - BaseTitleSlugMixin, - TitleSlugMixin, BaseTitleSlugDescriptionMixin, + BaseTitleSlugMixin, + StockUnitMixin, + TimeStampMixin, TitleSlugDescriptionMixin, + TitleSlugMixin, + UUIDBaseModel, ) - -from .address import AddressMixin -from .access import SingletonModelMixin -from .rating import RatingMixin from .comment import CommentBaseModel +from .rating import RatingMixin __all__ = [ "TimeStampMixin", diff --git a/sage_tools/mixins/views/__init__.py b/sage_tools/mixins/views/__init__.py index 6a19a9c..7a31f9b 100644 --- a/sage_tools/mixins/views/__init__.py +++ b/sage_tools/mixins/views/__init__.py @@ -1,22 +1,19 @@ -from .http import HeaderMixin -from .locale import SetLanguageMixinView from .access import ( AccessMixin, - LoginRequiredMixin, AnonymousRequiredMixin, - PermissionRequiredMixin, - MultiplePermissionsRequiredMixin, GroupRequiredMixin, - UserPassesTestMixin, - SuperuserRequiredMixin, - StaffuserRequiredMixin, - SSLRequiredMixin, + LoginRequiredMixin, + MultiplePermissionsRequiredMixin, + PermissionRequiredMixin, RecentLoginRequiredMixin, + SSLRequiredMixin, + StaffuserRequiredMixin, + SuperuserRequiredMixin, + UserPassesTestMixin, ) -from .cache import ( - CacheControlMixin, - NeverCacheMixin, -) +from .cache import CacheControlMixin, NeverCacheMixin +from .http import HeaderMixin +from .locale import SetLanguageMixinView __all__ = [ "HeaderMixin", diff --git a/sage_tools/repository/generator/base.py b/sage_tools/repository/generator/base.py index 6c369dc..53783e7 100644 --- a/sage_tools/repository/generator/base.py +++ b/sage_tools/repository/generator/base.py @@ -1,12 +1,12 @@ import io -import secrets import random -from typing import Any, Set, List, TypeVar, Type +import secrets from datetime import datetime, timezone -from django.utils.timezone import make_aware +from typing import Any, List, Set, Type, TypeVar from django.db.models import Model from django.utils.text import slugify +from django.utils.timezone import make_aware try: from PIL import Image, ImageDraw, ImageFont @@ -16,16 +16,8 @@ ) try: + from mimesis import Address, Datetime, Finance, Food, Numeric, Person, Text from mimesis.locales import Locale - from mimesis import ( - Text, - Person, - Numeric, - Finance, - Datetime, - Address, - Food, - ) except ImportError: raise ImportError( # noqa: B904 "Install `mimesis` package. Run `pip install mimesis`." diff --git a/sage_tools/utils/linker.py b/sage_tools/utils/linker.py index 9b81761..3d6746f 100644 --- a/sage_tools/utils/linker.py +++ b/sage_tools/utils/linker.py @@ -1,21 +1,3 @@ -"""ForeignKeyLinker Module. - -This module contains a class, ForeignKeyLinker, designed to generate -a function for linking to a related object's change page in the Django admin interface. -The generated function can be used in a Django ModelAdmin class within the list_display -property to display clickable links to related objects. - -Classes: - - ForeignKeyLinker: Generates link functions for related objects in Django admin. - -Attributes: - - field_name (str): The name of the foreign key field in the model. - - short_description (str, optional): A short description for the function, - used as the column header. Defaults to the capitalized field name with - underscores replaced by spaces. - -""" - from django.urls import reverse from django.utils.html import format_html @@ -43,7 +25,7 @@ def __init__(self, field_name, short_description=None): short_description or field_name.replace("_", " ").capitalize() ) - def link_to_fk(self, obj): + def link_to_fk(self, obj, field_name): """Return an HTML link to the related object's change page in the Django admin interface. @@ -67,11 +49,14 @@ def link_to_fk(self, obj): f"admin:{related_obj._meta.app_label}_{related_obj._meta.model_name}_change", args=[related_obj.pk], ) - # Return the HTML string - return format_html('{}', url, related_obj) + if field_name: + attr_name = getattr(related_obj, field_name) + else: + attr_name = related_obj + return format_html('{}', url, attr_name) - def get_link(self): + def get_link(self, field_name=None): """Return the function for use in a Django ModelAdmin class. Returns: @@ -81,11 +66,10 @@ def get_link(self): # Define the function def func(obj): - return self.link_to_fk(obj) + return self.link_to_fk(obj, field_name) func.__name__ = self.field_name func.short_description = self.short_description func.admin_order_field = self.field_name - # Return the function return func diff --git a/sage_tools/utils/tom_reader.py b/sage_tools/utils/tom_reader.py index 0d597be..661f619 100644 --- a/sage_tools/utils/tom_reader.py +++ b/sage_tools/utils/tom_reader.py @@ -9,9 +9,10 @@ """ import logging -import tomllib from typing import Any, Dict, Optional +import tomllib + logger = logging.getLogger(__name__) diff --git a/sage_tools/validators/string.py b/sage_tools/validators/string.py index e18ecfa..4807923 100644 --- a/sage_tools/validators/string.py +++ b/sage_tools/validators/string.py @@ -1,7 +1,8 @@ +import re + from django.core.exceptions import ValidationError from django.utils.deconstruct import deconstructible from django.utils.translation import gettext_lazy as _ -import re @deconstructible diff --git a/sage_tools/widgets/__init__.py b/sage_tools/widgets/__init__.py new file mode 100644 index 0000000..a9a1994 --- /dev/null +++ b/sage_tools/widgets/__init__.py @@ -0,0 +1 @@ +from .svg import SVGWidget diff --git a/sage_tools/widgets/svg.py b/sage_tools/widgets/svg.py new file mode 100644 index 0000000..27eb5ae --- /dev/null +++ b/sage_tools/widgets/svg.py @@ -0,0 +1,13 @@ +from django.contrib.admin.widgets import AdminFileWidget + + +class SVGWidget(AdminFileWidget): + def __init__(self, url=None, attrs=None): + self.svg_field_url = url + super().__init__(attrs) + + def format_value(self, value): + """Return the file URL if available.""" + if self.svg_field_url: + return self.svg_field_url + return super().format_value(value)