mirror of
https://github.com/eitchtee/WYGIWYH.git
synced 2026-04-19 23:31:27 +02:00
changes
This commit is contained in:
0
app/apps/common/decorators/__init__.py
Normal file
0
app/apps/common/decorators/__init__.py
Normal file
14
app/apps/common/decorators/htmx.py
Normal file
14
app/apps/common/decorators/htmx.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from functools import wraps
|
||||
|
||||
from django.core.exceptions import PermissionDenied
|
||||
|
||||
|
||||
def only_htmx(view):
|
||||
@wraps(view)
|
||||
def _view(request, *args, **kwargs):
|
||||
if not request.META.get("HTTP_HX_REQUEST"):
|
||||
raise PermissionDenied
|
||||
|
||||
return view(request, *args, **kwargs)
|
||||
|
||||
return _view
|
||||
0
app/apps/common/fields/__init__.py
Normal file
0
app/apps/common/fields/__init__.py
Normal file
0
app/apps/common/fields/forms/__init__.py
Normal file
0
app/apps/common/fields/forms/__init__.py
Normal file
205
app/apps/common/fields/forms/dynamic_select.py
Normal file
205
app/apps/common/fields/forms/dynamic_select.py
Normal file
@@ -0,0 +1,205 @@
|
||||
from django import forms
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import transaction
|
||||
|
||||
from apps.common.widgets.tom_select import TomSelect, TomSelectMultiple
|
||||
|
||||
|
||||
# class DynamicModelChoiceField(forms.ModelChoiceField):
|
||||
# def __init__(self, model, *args, **kwargs):
|
||||
# self.model = model
|
||||
# self.queryset = kwargs.pop("queryset", model.objects.all())
|
||||
# super().__init__(queryset=self.queryset, *args, **kwargs)
|
||||
# self._created_instance = None
|
||||
#
|
||||
# self.widget = TomSelect(clear_button=True, create=True)
|
||||
#
|
||||
# def to_python(self, value):
|
||||
# if value in self.empty_values:
|
||||
# return None
|
||||
# try:
|
||||
# key = self.to_field_name or "pk"
|
||||
# return self.model.objects.get(**{key: value})
|
||||
# except (ValueError, TypeError, self.model.DoesNotExist):
|
||||
# return value # Return the raw value; we'll handle creation in clean()
|
||||
#
|
||||
# def clean(self, value):
|
||||
# if isinstance(value, self.model):
|
||||
# return value
|
||||
# if isinstance(value, str):
|
||||
# try:
|
||||
# if value.isdigit():
|
||||
# return self.model.objects.get(id=value)
|
||||
# else:
|
||||
# raise self.model.DoesNotExist
|
||||
# except self.model.DoesNotExist:
|
||||
# try:
|
||||
# with transaction.atomic():
|
||||
# instance = self.model.objects.create(name=value)
|
||||
# self._created_instance = instance
|
||||
# return instance
|
||||
# except Exception as e:
|
||||
# raise ValidationError(
|
||||
# self.error_messages["invalid_choice"], code="invalid_choice"
|
||||
# )
|
||||
# return super().clean(value)
|
||||
#
|
||||
# def bound_data(self, data, initial):
|
||||
# if self._created_instance and isinstance(data, str):
|
||||
# if data == self._created_instance.name:
|
||||
# return self._created_instance.pk
|
||||
# return super().bound_data(data, initial)
|
||||
|
||||
|
||||
class DynamicModelChoiceField(forms.ModelChoiceField):
|
||||
def __init__(self, model, *args, **kwargs):
|
||||
self.model = model
|
||||
self.queryset = kwargs.pop("queryset", model.objects.all())
|
||||
super().__init__(queryset=self.queryset, *args, **kwargs)
|
||||
self._created_instance = None
|
||||
|
||||
self.widget = TomSelect(clear_button=True, create=True)
|
||||
|
||||
def to_python(self, value):
|
||||
if value in self.empty_values:
|
||||
return None
|
||||
try:
|
||||
key = self.to_field_name or "pk"
|
||||
return self.model.objects.get(**{key: value})
|
||||
except (ValueError, TypeError, self.model.DoesNotExist):
|
||||
return value # Return the raw value; we'll handle creation in clean()
|
||||
|
||||
def clean(self, value):
|
||||
if value in self.empty_values:
|
||||
if self.required:
|
||||
raise ValidationError(self.error_messages["required"], code="required")
|
||||
return None
|
||||
|
||||
if isinstance(value, self.model):
|
||||
return value
|
||||
|
||||
if isinstance(value, str):
|
||||
value = value.strip()
|
||||
if not value:
|
||||
if self.required:
|
||||
raise ValidationError(
|
||||
self.error_messages["required"], code="required"
|
||||
)
|
||||
return None
|
||||
|
||||
try:
|
||||
if value.isdigit():
|
||||
return self.model.objects.get(id=value)
|
||||
else:
|
||||
raise self.model.DoesNotExist
|
||||
except self.model.DoesNotExist:
|
||||
try:
|
||||
with transaction.atomic():
|
||||
instance = self.model.objects.create(name=value)
|
||||
self._created_instance = instance
|
||||
return instance
|
||||
except Exception as e:
|
||||
raise ValidationError(
|
||||
self.error_messages["invalid_choice"], code="invalid_choice"
|
||||
)
|
||||
return super().clean(value)
|
||||
|
||||
def bound_data(self, data, initial):
|
||||
if self._created_instance and isinstance(data, str):
|
||||
if data == self._created_instance.name:
|
||||
return self._created_instance.pk
|
||||
return super().bound_data(data, initial)
|
||||
|
||||
|
||||
class DynamicModelMultipleChoiceField(forms.ModelMultipleChoiceField):
|
||||
"""
|
||||
A custom ModelMultipleChoiceField that creates new entries if they don't exist.
|
||||
|
||||
This field allows users to select multiple existing options or add new ones.
|
||||
If a selected option doesn't exist, it will be created in the database.
|
||||
|
||||
Attributes:
|
||||
create_field (str): The name of the field to use when creating new instances.
|
||||
"""
|
||||
|
||||
def __init__(self, model, **kwargs):
|
||||
"""
|
||||
Initialize the CreateIfNotExistsModelMultipleChoiceField.
|
||||
|
||||
Args:
|
||||
create_field (str): The name of the field to use when creating new instances.
|
||||
*args: Variable length argument list.
|
||||
**kwargs: Arbitrary keyword arguments.
|
||||
"""
|
||||
self.create_field = kwargs.pop("create_field", None)
|
||||
if not self.create_field:
|
||||
raise ValueError("The 'create_field' parameter is required.")
|
||||
self.model = model
|
||||
self.queryset = kwargs.pop("queryset", model.objects.all())
|
||||
super().__init__(queryset=self.queryset, **kwargs)
|
||||
|
||||
self.widget = TomSelectMultiple(
|
||||
remove_button=True, clear_button=True, create=True, checkboxes=True
|
||||
)
|
||||
|
||||
def _create_new_instance(self, value):
|
||||
"""
|
||||
Create a new instance of the model with the given value.
|
||||
|
||||
Args:
|
||||
value: The value to use for creating the new instance.
|
||||
|
||||
Returns:
|
||||
Model: The newly created model instance.
|
||||
|
||||
Raises:
|
||||
ValidationError: If there's an error creating the new instance.
|
||||
"""
|
||||
try:
|
||||
with transaction.atomic():
|
||||
new_instance = self.queryset.model(**{self.create_field: value})
|
||||
new_instance.full_clean()
|
||||
new_instance.save()
|
||||
return new_instance
|
||||
except Exception as e:
|
||||
raise ValidationError(f"Error creating new instance: {str(e)}")
|
||||
|
||||
def clean(self, value):
|
||||
"""
|
||||
Clean and validate the field value.
|
||||
|
||||
This method checks if each selected choice exists in the database.
|
||||
If a choice doesn't exist, it creates a new instance of the model.
|
||||
|
||||
Args:
|
||||
value (list): List of selected values.
|
||||
|
||||
Returns:
|
||||
list: A list containing all selected and newly created model instances.
|
||||
|
||||
Raises:
|
||||
ValidationError: If there's an error during the cleaning process.
|
||||
"""
|
||||
if not value:
|
||||
return []
|
||||
|
||||
print(value)
|
||||
|
||||
string_values = set(str(v) for v in value)
|
||||
existing_objects = list(
|
||||
self.queryset.filter(**{f"{self.create_field}__in": string_values})
|
||||
)
|
||||
existing_values = set(
|
||||
str(getattr(obj, self.create_field)) for obj in existing_objects
|
||||
)
|
||||
|
||||
new_values = string_values - existing_values
|
||||
new_objects = []
|
||||
|
||||
for new_value in new_values:
|
||||
try:
|
||||
new_objects.append(self._create_new_instance(new_value))
|
||||
except ValidationError as e:
|
||||
raise ValidationError(f"Error creating '{new_value}': {str(e)}")
|
||||
|
||||
return existing_objects + new_objects
|
||||
0
app/apps/common/middleware/__init__.py
Normal file
0
app/apps/common/middleware/__init__.py
Normal file
16
app/apps/common/middleware/timezone.py
Normal file
16
app/apps/common/middleware/timezone.py
Normal file
@@ -0,0 +1,16 @@
|
||||
import zoneinfo
|
||||
from django.utils import timezone
|
||||
from django.shortcuts import render
|
||||
|
||||
|
||||
class TimezoneMiddleware:
|
||||
def __init__(self, get_response):
|
||||
self.get_response = get_response
|
||||
|
||||
def __call__(self, request):
|
||||
tz = request.COOKIES.get("mytz")
|
||||
if tz:
|
||||
timezone.activate(zoneinfo.ZoneInfo(tz))
|
||||
else:
|
||||
timezone.activate(zoneinfo.ZoneInfo("UTC"))
|
||||
return self.get_response(request)
|
||||
41
app/apps/common/templatetags/active_link.py
Normal file
41
app/apps/common/templatetags/active_link.py
Normal file
@@ -0,0 +1,41 @@
|
||||
from django import template
|
||||
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
# https://github.com/valerymelou/django-active-link/
|
||||
@register.simple_tag(takes_context=True)
|
||||
def active_link(
|
||||
context, views="", namespaces="", css_class="active", inactive_class=""
|
||||
):
|
||||
"""
|
||||
Renders the given CSS class if the request path matches the path of the view.
|
||||
:param context: The context where the tag was called. Used to access the request object.
|
||||
:param views: The name of the view or views separated by || (include namespaces if any).
|
||||
:param namespaces: The name of the namespace or namespaces separated by ||.
|
||||
:param css_class: The CSS class to render.
|
||||
:param inactive_class: The CSS class to render if the views is not active.
|
||||
:return:
|
||||
"""
|
||||
request = context.get("request")
|
||||
if request is None:
|
||||
# Can't work without the request object.
|
||||
return ""
|
||||
|
||||
if views:
|
||||
views = views.split("||")
|
||||
current_view = request.resolver_match.view_name
|
||||
|
||||
if current_view in views:
|
||||
return css_class
|
||||
elif namespaces:
|
||||
namespaces = namespaces.split("||")
|
||||
current_namespace = request.resolver_match.namespaces
|
||||
|
||||
if any(item in current_namespace for item in namespaces):
|
||||
return css_class
|
||||
else:
|
||||
return ""
|
||||
|
||||
return inactive_class
|
||||
60
app/apps/common/templatetags/natural.py
Normal file
60
app/apps/common/templatetags/natural.py
Normal file
@@ -0,0 +1,60 @@
|
||||
from django import template
|
||||
from django.utils import timezone
|
||||
from datetime import date
|
||||
from django.utils.translation import gettext_lazy as _, ngettext_lazy
|
||||
|
||||
from django.utils.formats import date_format
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.filter(name="customnaturaldate")
|
||||
def naturaldate(value):
|
||||
if not isinstance(value, date):
|
||||
return value
|
||||
|
||||
today = timezone.localdate(timezone.now())
|
||||
delta = value - today
|
||||
|
||||
if delta.days == 0:
|
||||
return _("today")
|
||||
elif delta.days == 1:
|
||||
return _("tomorrow")
|
||||
elif delta.days == -1:
|
||||
return _("yesterday")
|
||||
elif -7 <= delta.days < 0:
|
||||
return _("last 7 days")
|
||||
elif 0 < delta.days <= 7:
|
||||
return _("in the next 7 days")
|
||||
elif delta.days < -365:
|
||||
years = abs(delta.days) // 365
|
||||
return ngettext_lazy("%(years)s year ago", "%(years)s years ago", years) % {
|
||||
"years": years
|
||||
}
|
||||
elif delta.days < -30:
|
||||
months = abs(delta.days) // 30
|
||||
return ngettext_lazy(
|
||||
"%(months)s month ago", "%(months)s months ago", months
|
||||
) % {"months": months}
|
||||
elif delta.days < -7:
|
||||
weeks = abs(delta.days) // 7
|
||||
return ngettext_lazy("%(weeks)s week ago", "%(weeks)s weeks ago", weeks) % {
|
||||
"weeks": weeks
|
||||
}
|
||||
elif delta.days > 365:
|
||||
years = delta.days // 365
|
||||
return ngettext_lazy("in %(years)s year", "in %(years)s years", years) % {
|
||||
"years": years
|
||||
}
|
||||
elif delta.days > 30:
|
||||
months = delta.days // 30
|
||||
return ngettext_lazy("in %(months)s month", "in %(months)s months", months) % {
|
||||
"months": months
|
||||
}
|
||||
elif delta.days > 7:
|
||||
weeks = delta.days // 7
|
||||
return ngettext_lazy("in %(weeks)s week", "in %(weeks)s weeks", weeks) % {
|
||||
"weeks": weeks
|
||||
}
|
||||
else:
|
||||
return date_format(value, format="DATE_FORMAT")
|
||||
13
app/apps/common/templatetags/title.py
Normal file
13
app/apps/common/templatetags/title.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from django import template
|
||||
from django.conf import settings
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.filter
|
||||
def site_title(value):
|
||||
value = value.strip()
|
||||
if value:
|
||||
return f"{value} {settings.TITLE_SEPARATOR or '::'} {settings.SITE_TITLE or "SITE_TITLE NOT SET"}"
|
||||
else:
|
||||
return settings.SITE_TITLE or "SITE_TITLE NOT SET"
|
||||
0
app/apps/common/widgets/crispy/__init__.py
Normal file
0
app/apps/common/widgets/crispy/__init__.py
Normal file
65
app/apps/common/widgets/crispy/submit.py
Normal file
65
app/apps/common/widgets/crispy/submit.py
Normal file
@@ -0,0 +1,65 @@
|
||||
from crispy_forms.layout import BaseInput
|
||||
|
||||
|
||||
class NoClassSubmit(BaseInput):
|
||||
"""
|
||||
Used to create a Submit button descriptor for the {% crispy %} template tag.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
template: str
|
||||
The default template which this Layout Object will be rendered
|
||||
with.
|
||||
field_classes: str
|
||||
CSS classes to be applied to the ``<input>``.
|
||||
input_type: str
|
||||
The ``type`` attribute of the ``<input>``.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
name : str
|
||||
The name attribute of the button.
|
||||
value : str
|
||||
The value attribute of the button.
|
||||
css_id : str, optional
|
||||
A custom DOM id for the layout object. If not provided the name
|
||||
argument is slugified and turned into the id for the submit button.
|
||||
By default None.
|
||||
css_class : str, optional
|
||||
Additional CSS classes to be applied to the ``<input>``. By default
|
||||
None.
|
||||
template : str, optional
|
||||
Overrides the default template, if provided. By default None.
|
||||
**kwargs : dict, optional
|
||||
Additional attributes are passed to `flatatt` and converted into
|
||||
key="value", pairs. These attributes are added to the ``<input>``.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Note: ``form`` arg to ``render()`` is not required for ``BaseInput``
|
||||
inherited objects.
|
||||
|
||||
>>> submit = Submit('Search the Site', 'search this site')
|
||||
>>> submit.render("", "", Context())
|
||||
'<input type="submit" name="search-the-site" value="search this site" '
|
||||
'class="btn btn-primary" id="submit-id-search-the-site"/>'
|
||||
|
||||
>>> submit = Submit('Search the Site', 'search this site', css_id="custom-id",
|
||||
css_class="custom class", my_attr=True, data="my-data")
|
||||
>>> submit.render("", "", Context())
|
||||
'<input type="submit" name="search-the-site" value="search this site" '
|
||||
'class="btn btn-primary custom class" id="custom-id" data="my-data" my-attr/>'
|
||||
|
||||
Usually you will not call the render method on the object directly. Instead
|
||||
add it to your ``Layout`` manually or use the `add_input` method::
|
||||
|
||||
class ExampleForm(forms.Form):
|
||||
[...]
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.helper = FormHelper()
|
||||
self.helper.add_input(Submit('submit', 'Submit'))
|
||||
"""
|
||||
|
||||
input_type = "submit"
|
||||
field_classes = ""
|
||||
54
app/apps/common/widgets/tom_select.py
Normal file
54
app/apps/common/widgets/tom_select.py
Normal file
@@ -0,0 +1,54 @@
|
||||
from django.forms import widgets, SelectMultiple
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
class TomSelect(widgets.Select):
|
||||
def __init__(
|
||||
self,
|
||||
attrs=None,
|
||||
remove_button=False,
|
||||
remove_button_text=_("Remove"),
|
||||
create=False,
|
||||
create_text=_("Add"),
|
||||
clear_button=True,
|
||||
clear_text=_("Clear"),
|
||||
no_results_text=_("No results..."),
|
||||
checkboxes=False,
|
||||
*args,
|
||||
**kwargs
|
||||
):
|
||||
super().__init__(attrs, *args, **kwargs)
|
||||
self.remove_button = remove_button
|
||||
self.remove_button_text = remove_button_text
|
||||
self.clear_button = clear_button
|
||||
self.create = create
|
||||
self.create_text = create_text
|
||||
self.clear_text = clear_text
|
||||
self.no_results_text = no_results_text
|
||||
self.checkboxes = checkboxes
|
||||
|
||||
def build_attrs(self, base_attrs, extra_attrs=None):
|
||||
attrs = super().build_attrs(base_attrs, extra_attrs)
|
||||
|
||||
attrs["data-txt-no-results"] = self.no_results_text
|
||||
|
||||
if self.remove_button:
|
||||
attrs["data-remove-button"] = "true"
|
||||
attrs["data-txt-remove"] = self.remove_button_text
|
||||
|
||||
if self.create:
|
||||
attrs["data-create"] = "true"
|
||||
attrs["data-txt-create"] = self.create_text
|
||||
|
||||
if self.clear_button:
|
||||
attrs["data-clear-button"] = "true"
|
||||
attrs["data-txt-clear"] = self.clear_text
|
||||
|
||||
if self.checkboxes:
|
||||
attrs["data-checkboxes"] = "true"
|
||||
|
||||
return attrs
|
||||
|
||||
|
||||
class TomSelectMultiple(SelectMultiple, TomSelect):
|
||||
pass
|
||||
Reference in New Issue
Block a user