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)