drop DRF and django-filter to make django_nepkit framework‑agnostic, simplify dependencies, and align with v0.2.2 breaking change.
378 lines
13 KiB
Python
378 lines
13 KiB
Python
import logging
|
|
|
|
from django.contrib import admin
|
|
from django.core.exceptions import FieldDoesNotExist
|
|
from django.utils.translation import gettext_lazy as _
|
|
from nepali.datetime import nepalidate, nepalidatetime
|
|
from nepali.number import english_to_nepali
|
|
|
|
from django_nepkit.conf import nepkit_settings
|
|
from django_nepkit.models import (
|
|
NepaliCurrencyField,
|
|
NepaliDateField,
|
|
NepaliDateTimeField,
|
|
NepaliTimeField,
|
|
)
|
|
from django_nepkit.utils import (
|
|
format_nepali_currency,
|
|
format_nepali_time,
|
|
try_parse_nepali_date,
|
|
try_parse_nepali_datetime,
|
|
)
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def _format_nepali_common(value, try_parse_func, format_string, ne, cls_type):
|
|
"""Format a Nepali date/time for admin display, falling back gracefully."""
|
|
if value is None:
|
|
return ""
|
|
|
|
try:
|
|
if isinstance(value, str):
|
|
parsed = try_parse_func(value)
|
|
if parsed is not None:
|
|
if ne and hasattr(parsed, "strftime_ne"):
|
|
return parsed.strftime_ne(format_string)
|
|
return parsed.strftime(format_string)
|
|
if isinstance(value, cls_type):
|
|
if ne and hasattr(value, "strftime_ne"):
|
|
return value.strftime_ne(format_string)
|
|
return value.strftime(format_string)
|
|
except (ValueError, TypeError, AttributeError) as exc:
|
|
logger.warning("nepkit admin formatter: cannot format %r: %s", value, exc)
|
|
return ""
|
|
|
|
|
|
def format_nepali_date(date_value, format_string="%B %d, %Y", ne=False):
|
|
"""Format a nepalidate object with Nepali month names."""
|
|
return _format_nepali_common(
|
|
date_value, try_parse_nepali_date, format_string, ne, nepalidate
|
|
)
|
|
|
|
|
|
def format_nepali_datetime(datetime_value, format_string=None, ne=False):
|
|
"""Format a nepalidatetime object with Nepali month names."""
|
|
if format_string is None:
|
|
if nepkit_settings.TIME_FORMAT == 24:
|
|
format_string = "%B %d, %Y %H:%M"
|
|
else:
|
|
format_string = "%B %d, %Y %I:%M %p"
|
|
return _format_nepali_common(
|
|
datetime_value, try_parse_nepali_datetime, format_string, ne, nepalidatetime
|
|
)
|
|
|
|
|
|
def format_nepali_time_admin(time_value, format_string=None, ne=False):
|
|
"""Format a ``time``/``datetime`` for the admin with Devanagari support."""
|
|
if format_string is None:
|
|
format_string = nepkit_settings.BS_TIME_FORMAT
|
|
formatted = format_nepali_time(time_value, format_string)
|
|
if ne and formatted:
|
|
formatted = english_to_nepali(formatted)
|
|
return formatted
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Admin list filters
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def _separator_from_format(fmt):
|
|
"""Pick the first non-% character from a strftime format string.
|
|
|
|
Returns the separator for ``%Y/%m/%d``-style formats, falling back to
|
|
``"-"`` when the format has no separator (e.g. ``%Y%m%d``).
|
|
"""
|
|
if not fmt or len(fmt) < 3:
|
|
return "-"
|
|
return fmt[2] if not fmt[2].startswith("%") else "-"
|
|
|
|
|
|
class BaseNepaliDateFilter(admin.FieldListFilter):
|
|
"""Base class for date filters (Year/Month)."""
|
|
|
|
def __init__(self, field, request, params, model, model_admin, field_path):
|
|
self.parameter_name = f"{field_path}_{self.suffix}"
|
|
super().__init__(field, request, params, model, model_admin, field_path)
|
|
|
|
def expected_parameters(self):
|
|
return [self.parameter_name]
|
|
|
|
def choices(self, changelist):
|
|
yield {
|
|
"selected": self.used_parameters.get(self.parameter_name) is None,
|
|
"query_string": changelist.get_query_string(remove=[self.parameter_name]),
|
|
"display": _("All"),
|
|
}
|
|
for value, display in self.get_filter_options():
|
|
yield {
|
|
"selected": self.used_parameters.get(self.parameter_name) == str(value),
|
|
"query_string": changelist.get_query_string(
|
|
{self.parameter_name: str(value)}
|
|
),
|
|
"display": display,
|
|
}
|
|
|
|
def queryset(self, request, queryset):
|
|
value = self.used_parameters.get(self.parameter_name)
|
|
if value:
|
|
return self.apply_filter(queryset, value)
|
|
return queryset
|
|
|
|
def get_filter_options(self):
|
|
raise NotImplementedError
|
|
|
|
def apply_filter(self, queryset, value):
|
|
raise NotImplementedError
|
|
|
|
|
|
class NepaliDateFilter(BaseNepaliDateFilter):
|
|
"""Filter by Nepali Year (e.g. 2080)."""
|
|
|
|
suffix = "bs_year"
|
|
title = _("Nepali Date (Year)")
|
|
|
|
def get_filter_options(self):
|
|
current_year = nepalidate.today().year
|
|
return [(y, str(y)) for y in range(current_year - 10, current_year + 2)]
|
|
|
|
def apply_filter(self, queryset, value):
|
|
fmt = nepkit_settings.BS_DATE_FORMAT
|
|
if fmt.startswith("%Y"):
|
|
separator = _separator_from_format(fmt)
|
|
return queryset.filter(
|
|
**{f"{self.field_path}__startswith": f"{value}{separator}"}
|
|
)
|
|
return queryset.filter(**{f"{self.field_path}__icontains": f"{value}"})
|
|
|
|
|
|
class NepaliMonthFilter(BaseNepaliDateFilter):
|
|
"""Filter by Nepali Month (e.g. Baisakh)."""
|
|
|
|
suffix = "bs_month"
|
|
title = _("Nepali Date (Month)")
|
|
|
|
def get_filter_options(self):
|
|
ne = nepkit_settings.DEFAULT_LANGUAGE == "ne"
|
|
names = [
|
|
("बैशाख", "Baisakh"),
|
|
("जेठ", "Jestha"),
|
|
("असार", "Ashad"),
|
|
("साउन", "Shrawan"),
|
|
("भदौ", "Bhadra"),
|
|
("असोज", "Ashwin"),
|
|
("कात्तिक", "Kartik"),
|
|
("मंसिर", "Mangsir"),
|
|
("पुष", "Poush"),
|
|
("माघ", "Magh"),
|
|
("फागुन", "Falgun"),
|
|
("चैत", "Chaitra"),
|
|
]
|
|
return [(f"{i:02d}", n[0] if ne else n[1]) for i, n in enumerate(names, 1)]
|
|
|
|
def apply_filter(self, queryset, value):
|
|
fmt = nepkit_settings.BS_DATE_FORMAT
|
|
if fmt == "%Y-%m-%d":
|
|
return queryset.filter(**{f"{self.field_path}__contains": f"-{value}-"})
|
|
separator = _separator_from_format(fmt)
|
|
return queryset.filter(
|
|
**{f"{self.field_path}__contains": f"{separator}{value}{separator}"}
|
|
)
|
|
|
|
|
|
# Register the year filter as the default for NepaliDateField.
|
|
admin.FieldListFilter.register(
|
|
lambda f: isinstance(f, NepaliDateField),
|
|
NepaliDateFilter,
|
|
take_priority=True,
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# ModelAdmin mixin
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class NepaliAdminMixin:
|
|
"""Provides date formatting helpers for Admin classes."""
|
|
|
|
def _get_field_ne_setting(self, field_name):
|
|
if not hasattr(self, "model"):
|
|
return False
|
|
try:
|
|
field = self.model._meta.get_field(field_name)
|
|
if hasattr(field, "ne"):
|
|
return field.ne
|
|
except (FieldDoesNotExist, AttributeError):
|
|
return False
|
|
return False
|
|
|
|
def format_nepali_date(
|
|
self, date_value, format_string="%B %d, %Y", ne=None, field_name=None
|
|
):
|
|
if ne is None and field_name:
|
|
ne = self._get_field_ne_setting(field_name)
|
|
elif ne is None:
|
|
ne = nepkit_settings.DEFAULT_LANGUAGE == "ne"
|
|
return format_nepali_date(date_value, format_string, ne=ne)
|
|
|
|
def format_nepali_datetime(
|
|
self,
|
|
datetime_value,
|
|
format_string=None,
|
|
ne=None,
|
|
field_name=None,
|
|
):
|
|
if ne is None and field_name:
|
|
ne = self._get_field_ne_setting(field_name)
|
|
elif ne is None:
|
|
ne = nepkit_settings.DEFAULT_LANGUAGE == "ne"
|
|
return format_nepali_datetime(datetime_value, format_string, ne=ne)
|
|
|
|
def format_nepali_currency(self, value, currency_symbol="Rs.", ne=False, **kwargs):
|
|
return format_nepali_currency(value, currency_symbol=currency_symbol, ne=ne)
|
|
|
|
|
|
class NepaliModelAdmin(NepaliAdminMixin, admin.ModelAdmin):
|
|
"""
|
|
Standard Admin class that automatically formats Nepali dates in lists.
|
|
|
|
Example::
|
|
|
|
@admin.register(MyModel)
|
|
class MyModelAdmin(NepaliModelAdmin):
|
|
list_display = ("name", "birth_date", "created_at")
|
|
list_filter = (("birth_date", NepaliDateFilter),)
|
|
"""
|
|
|
|
NepaliDateFilter = NepaliDateFilter
|
|
NepaliMonthFilter = NepaliMonthFilter
|
|
|
|
def _make_nepali_display(self, field_name, formatter_method):
|
|
admin_instance = self
|
|
try:
|
|
field = self.model._meta.get_field(field_name)
|
|
short_description = getattr(
|
|
field, "verbose_name", field_name.replace("_", " ").title()
|
|
)
|
|
except (FieldDoesNotExist, AttributeError):
|
|
short_description = field_name.replace("_", " ").title()
|
|
|
|
def display(obj):
|
|
val = getattr(obj, field_name, None)
|
|
if val is None:
|
|
return admin_instance.get_empty_value_display()
|
|
return formatter_method(val, field_name=field_name)
|
|
|
|
display.short_description = short_description
|
|
display.admin_order_field = field_name
|
|
return display
|
|
|
|
def _make_nepali_date_display(self, field_name):
|
|
return self._make_nepali_display(field_name, self.format_nepali_date)
|
|
|
|
def _make_nepali_datetime_display(self, field_name):
|
|
return self._make_nepali_display(field_name, self.format_nepali_datetime)
|
|
|
|
def _make_nepali_currency_display(self, field_name):
|
|
return self._make_nepali_display(field_name, self.format_nepali_currency)
|
|
|
|
def get_list_display(self, request):
|
|
list_display = super().get_list_display(request)
|
|
result = []
|
|
for item in list_display:
|
|
if not isinstance(item, str):
|
|
result.append(item)
|
|
continue
|
|
try:
|
|
field = self.model._meta.get_field(item)
|
|
except (FieldDoesNotExist, AttributeError):
|
|
result.append(item)
|
|
continue
|
|
if isinstance(field, NepaliDateField):
|
|
result.append(self._make_nepali_date_display(item))
|
|
elif isinstance(field, NepaliDateTimeField):
|
|
result.append(self._make_nepali_datetime_display(item))
|
|
elif isinstance(field, NepaliTimeField):
|
|
result.append(self._make_nepali_time_display(item))
|
|
elif isinstance(field, NepaliCurrencyField):
|
|
result.append(self._make_nepali_currency_display(item))
|
|
else:
|
|
result.append(item)
|
|
return result
|
|
|
|
def _make_nepali_time_display(self, field_name):
|
|
def display(obj):
|
|
val = getattr(obj, field_name, None)
|
|
if val is None:
|
|
return self.get_empty_value_display()
|
|
ne = self._get_field_ne_setting(field_name)
|
|
if ne is False:
|
|
ne = nepkit_settings.DEFAULT_LANGUAGE == "ne"
|
|
return format_nepali_time_admin(val, ne=ne)
|
|
|
|
try:
|
|
field = self.model._meta.get_field(field_name)
|
|
display.short_description = getattr(
|
|
field, "verbose_name", field_name.replace("_", " ").title()
|
|
)
|
|
except (FieldDoesNotExist, AttributeError):
|
|
display.short_description = field_name.replace("_", " ").title()
|
|
display.admin_order_field = field_name
|
|
return display
|
|
|
|
def formfield_for_dbfield(self, db_field, request, **kwargs):
|
|
"""Auto-wire NepaliDatePicker / NepaliTimeWidget in the admin form."""
|
|
from django_nepkit.widgets import NepaliDatePickerWidget, NepaliTimeWidget
|
|
|
|
if (
|
|
isinstance(db_field, (NepaliDateField, NepaliDateTimeField))
|
|
and nepkit_settings.ADMIN_DATEPICKER
|
|
):
|
|
widget_kwargs = {}
|
|
if hasattr(db_field, "ne"):
|
|
widget_kwargs["ne"] = db_field.ne
|
|
if hasattr(db_field, "en"):
|
|
widget_kwargs["en"] = db_field.en
|
|
kwargs.setdefault("widget", NepaliDatePickerWidget(**widget_kwargs))
|
|
|
|
if isinstance(db_field, NepaliTimeField):
|
|
widget_kwargs = {}
|
|
if hasattr(db_field, "ne"):
|
|
widget_kwargs["ne"] = db_field.ne
|
|
if hasattr(db_field, "en"):
|
|
widget_kwargs["en"] = db_field.en
|
|
if hasattr(db_field, "htmx"):
|
|
widget_kwargs["htmx"] = db_field.htmx
|
|
kwargs.setdefault("widget", NepaliTimeWidget(**widget_kwargs))
|
|
|
|
return super().formfield_for_dbfield(db_field, request, **kwargs)
|
|
|
|
class Media:
|
|
"""Loads the Nepali Datepicker and bridging scripts."""
|
|
|
|
css = {
|
|
"all": (
|
|
"https://nepalidatepicker.sajanmaharjan.com.np/v5/nepali.datepicker/css/nepali.datepicker.v5.0.6.min.css",
|
|
"django_nepkit/css/admin-nepali-datepicker.css",
|
|
)
|
|
}
|
|
js = (
|
|
"django_nepkit/js/admin-jquery-bridge.js",
|
|
"https://nepalidatepicker.sajanmaharjan.com.np/v5/nepali.datepicker/js/nepali.datepicker.v5.0.6.min.js",
|
|
"django_nepkit/js/nepali-datepicker-init.js",
|
|
"django_nepkit/js/nepal-data.js",
|
|
"django_nepkit/js/address-chaining.js",
|
|
)
|
|
|
|
|
|
__all__ = [
|
|
"NepaliDateFilter",
|
|
"NepaliMonthFilter",
|
|
"format_nepali_date",
|
|
"format_nepali_datetime",
|
|
"NepaliAdminMixin",
|
|
"NepaliModelAdmin",
|
|
]
|