feat(models): added nepalidatetime fields

This commit is contained in:
sankalp committed 2026-01-29 00:28:37 +05:45
1 parent 5ee2e8f32b
commit 2bb6458c8b
8 files changed
+628 -212

No files matched your search

View File
Whitespace-only changes.
+155
View File
@@ -0,0 +1,155 @@
from django.contrib import admin
from django.utils.translation import gettext_lazy as _
from nepali.datetime import nepalidate, nepalidatetime
from django_nepkit.models import NepaliDateField
def format_nepali_date(date_value, format_string="%B %d, %Y"):
"""
Format a nepalidate object with Nepali month names.
Args:
date_value: A nepalidate object or string in YYYY-MM-DD format
format_string: strftime format string (default: '%B %d, %Y')
%B = Full month name (Baishak, Jestha, etc.)
%b = Short month name
%d = Day of month
%Y = Year
Returns:
Formatted date string with Nepali month names, or empty string if invalid
"""
if date_value is None:
return ""
try:
if isinstance(date_value, str):
date_value = nepalidate.strptime(date_value, "%Y-%m-%d")
if isinstance(date_value, nepalidate):
return date_value.strftime(format_string)
except (ValueError, TypeError, AttributeError):
pass
return str(date_value) if date_value else ""
def format_nepali_datetime(datetime_value, format_string="%B %d, %Y %I:%M %p"):
"""
Format a nepalidatetime object with Nepali month names.
Default output uses **12-hour time with AM/PM**.
"""
if datetime_value is None:
return ""
try:
if isinstance(datetime_value, str):
datetime_value = nepalidatetime.strptime(
datetime_value, "%Y-%m-%d %H:%M:%S"
)
if isinstance(datetime_value, nepalidatetime):
return datetime_value.strftime(format_string)
except (ValueError, TypeError, AttributeError):
pass
return str(datetime_value) if datetime_value else ""
class NepaliDateFilter(admin.FieldListFilter):
"""
A list filter for NepaliDateField to filter by BS Year.
"""
def __init__(self, field, request, params, model, model_admin, field_path):
self.parameter_name = f"{field_path}_bs_year"
super().__init__(field, request, params, model, model_admin, field_path)
self.title = _("Nepali Date (Year)")
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"),
}
current_year = nepalidate.today().year
for year in range(current_year - 10, current_year + 2):
yield {
"selected": self.used_parameters.get(self.parameter_name) == str(year),
"query_string": changelist.get_query_string(
{self.parameter_name: str(year)}
),
"display": str(year),
}
def queryset(self, request, queryset):
value = self.used_parameters.get(self.parameter_name)
if value:
year = int(value)
# Convert BS year range to AD date range
start_date_bs = nepalidate(year, 1, 1)
# Find last day of the year. Some BS years end on the 30th,
# so we try 31 first and then fall back to 30 if that date
# is not valid.
try:
end_date_bs = nepalidate(year, 12, 31)
except ValueError:
end_date_bs = nepalidate(year, 12, 30)
start_date_ad = start_date_bs.to_date()
end_date_ad = end_date_bs.to_date()
return queryset.filter(
**{f"{self.field_path}__range": (start_date_ad, end_date_ad)}
)
return queryset
admin.FieldListFilter.register(
lambda f: isinstance(f, NepaliDateField), NepaliDateFilter, take_priority=True
)
class NepaliAdminMixin:
"""
Mixin for Django admin classes that provides Nepali date utilities.
Makes format_nepali_date and NepaliDateFilter available without explicit imports.
"""
def format_nepali_date(self, date_value, format_string="%B %d, %Y"):
"""
Format a nepalidate object with Nepali month names.
Available as a method on admin classes using this mixin.
"""
return format_nepali_date(date_value, format_string)
def format_nepali_datetime(
self, datetime_value, format_string="%B %d, %Y %I:%M %p"
):
return format_nepali_datetime(datetime_value, format_string)
class NepaliModelAdmin(NepaliAdminMixin, admin.ModelAdmin):
"""
Base ModelAdmin class with Nepali date utilities built-in.
Use this instead of admin.ModelAdmin to have format_nepali_date available.
Example:
from django_nepkit import NepaliModelAdmin, NepaliDateFilter
@admin.register(MyModel)
class MyModelAdmin(NepaliModelAdmin):
list_filter = (('date_field', NepaliDateFilter),)
def display_date(self, obj):
return self.format_nepali_date(obj.date_field)
"""
# Make NepaliDateFilter available as a class attribute
NepaliDateFilter = NepaliDateFilter
+103 -84
View File
@@ -1,155 +1,174 @@
from django.contrib import admin
from django.utils.translation import gettext_lazy as _
from nepali.datetime import nepalidate, nepalidatetime
from django_nepkit.models import NepaliDateField
def format_nepali_date(date_value, format_string="%B %d, %Y"):
# --------------------------------------------------
# Internal helpers
# --------------------------------------------------
def _parse_nepali_date(value):
"""
Format a nepalidate object with Nepali month names.
Args:
date_value: A nepalidate object or string in YYYY-MM-DD format
format_string: strftime format string (default: '%B %d, %Y')
%B = Full month name (Baishak, Jestha, etc.)
%b = Short month name
%d = Day of month
%Y = Year
Returns:
Formatted date string with Nepali month names, or empty string if invalid
Normalize string / nepalidate to nepalidate.
"""
if value is None:
return None
if date_value is None:
return ""
if isinstance(value, nepalidate):
return value
try:
if isinstance(date_value, str):
date_value = nepalidate.strptime(date_value, "%Y-%m-%d")
if isinstance(value, str):
try:
return nepalidate.strptime(value, "%Y-%m-%d")
except (ValueError, TypeError):
return None
if isinstance(date_value, nepalidate):
return date_value.strftime(format_string)
except (ValueError, TypeError, AttributeError):
pass
return str(date_value) if date_value else ""
return None
def format_nepali_datetime(datetime_value, format_string="%B %d, %Y %I:%M %p"):
def _parse_nepali_datetime(value):
"""
Format a nepalidatetime object with Nepali month names.
Default output uses **12-hour time with AM/PM**.
Normalize string / nepalidatetime to nepalidatetime.
"""
if datetime_value is None:
return ""
if value is None:
return None
try:
if isinstance(datetime_value, str):
datetime_value = nepalidatetime.strptime(
datetime_value, "%Y-%m-%d %H:%M:%S"
)
if isinstance(value, nepalidatetime):
return value
if isinstance(datetime_value, nepalidatetime):
return datetime_value.strftime(format_string)
except (ValueError, TypeError, AttributeError):
pass
if isinstance(value, str):
try:
return nepalidatetime.strptime(value, "%Y-%m-%d %H:%M:%S")
except (ValueError, TypeError):
return None
return str(datetime_value) if datetime_value else ""
return None
# --------------------------------------------------
# Formatting helpers
# --------------------------------------------------
def format_nepali_date(value, format_string="%B %d, %Y"):
"""
Format a Nepali (BS) date with Nepali month names.
"""
nd = _parse_nepali_date(value)
return nd.strftime(format_string) if nd else ""
def format_nepali_datetime(value, format_string="%B %d, %Y %I:%M %p"):
"""
Format a Nepali (BS) datetime (12-hour format by default).
"""
ndt = _parse_nepali_datetime(value)
return ndt.strftime(format_string) if ndt else ""
# --------------------------------------------------
# Admin List Filter (BS Year)
# --------------------------------------------------
class NepaliDateFilter(admin.FieldListFilter):
"""
A list filter for NepaliDateField to filter by BS Year.
Admin list filter for NepaliDateField by BS year.
"""
title = _("Nepali Date (Year)")
def __init__(self, field, request, params, model, model_admin, field_path):
self.field_path = field_path
self.parameter_name = f"{field_path}_bs_year"
super().__init__(field, request, params, model, model_admin, field_path)
self.title = _("Nepali Date (Year)")
def expected_parameters(self):
return [self.parameter_name]
def _year_choices(self):
current_year = nepalidate.today().year
return range(current_year - 10, current_year + 2)
def choices(self, changelist):
yield {
"selected": self.used_parameters.get(self.parameter_name) is None,
"selected": self.parameter_name not in self.used_parameters,
"query_string": changelist.get_query_string(remove=[self.parameter_name]),
"display": _("All"),
}
current_year = nepalidate.today().year
for year in range(current_year - 10, current_year + 2):
for year in self._year_choices():
yield {
"selected": self.used_parameters.get(self.parameter_name) == str(year),
"query_string": changelist.get_query_string(
{self.parameter_name: str(year)}
{self.parameter_name: year}
),
"display": str(year),
"display": year,
}
def queryset(self, request, queryset):
value = self.used_parameters.get(self.parameter_name)
if value:
if not value:
return queryset
try:
year = int(value)
# Convert BS year range to AD date range
start_date_bs = nepalidate(year, 1, 1)
# Find last day of the year. Some BS years end on the 30th,
# so we try 31 first and then fall back to 30 if that date
# is not valid.
except (TypeError, ValueError):
return queryset
start_bs = nepalidate(year, 1, 1)
# BS year length varies (30/31 days)
for last_day in (31, 30):
try:
end_date_bs = nepalidate(year, 12, 31)
end_bs = nepalidate(year, 12, last_day)
break
except ValueError:
end_date_bs = nepalidate(year, 12, 30)
continue
start_date_ad = start_date_bs.to_date()
end_date_ad = end_date_bs.to_date()
return queryset.filter(
**{f"{self.field_path}__range": (start_date_ad, end_date_ad)}
)
return queryset
return queryset.filter(
**{
f"{self.field_path}__range": (
start_bs.to_date(),
end_bs.to_date(),
)
}
)
# Register filter automatically for NepaliDateField
admin.FieldListFilter.register(
lambda f: isinstance(f, NepaliDateField), NepaliDateFilter, take_priority=True
lambda f: isinstance(f, NepaliDateField),
NepaliDateFilter,
take_priority=True,
)
# --------------------------------------------------
# Admin mixins
# --------------------------------------------------
class NepaliAdminMixin:
"""
Mixin for Django admin classes that provides Nepali date utilities.
Makes format_nepali_date and NepaliDateFilter available without explicit imports.
Adds Nepali date formatting helpers to admin classes.
"""
def format_nepali_date(self, date_value, format_string="%B %d, %Y"):
"""
Format a nepalidate object with Nepali month names.
Available as a method on admin classes using this mixin.
"""
return format_nepali_date(date_value, format_string)
def format_nepali_datetime(
self, datetime_value, format_string="%B %d, %Y %I:%M %p"
):
return format_nepali_datetime(datetime_value, format_string)
format_nepali_date = staticmethod(format_nepali_date)
format_nepali_datetime = staticmethod(format_nepali_datetime)
class NepaliModelAdmin(NepaliAdminMixin, admin.ModelAdmin):
"""
Base ModelAdmin class with Nepali date utilities built-in.
Use this instead of admin.ModelAdmin to have format_nepali_date available.
Base ModelAdmin with Nepali utilities.
Example:
from django_nepkit import NepaliModelAdmin, NepaliDateFilter
@admin.register(MyModel)
class MyModelAdmin(NepaliModelAdmin):
list_filter = (('date_field', NepaliDateFilter),)
list_filter = (('date_bs', NepaliDateFilter),)
def display_date(self, obj):
return self.format_nepali_date(obj.date_field)
def date_display(self, obj):
return self.format_nepali_date(obj.date_bs)
"""
# Make NepaliDateFilter available as a class attribute
NepaliDateFilter = NepaliDateFilter
+52 -12
View File
@@ -2,36 +2,76 @@ from datetime import date as python_date
from django import forms
from django.utils.translation import gettext_lazy as _
from nepali.datetime import nepalidate
from nepali.datetime import nepalidate
from django_nepkit.validators import validate_nepali_phone_number
from django_nepkit.widgets import NepaliDatePickerWidget
# --------------------------------------------------
# Nepali Phone Number Form Field
# --------------------------------------------------
class NepaliPhoneNumberField(forms.CharField):
"""
Form field for Nepali phone numbers.
"""
default_error_messages = {
"invalid": _("Enter a valid Nepali phone number."),
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.validators.append(validate_nepali_phone_number)
class NepaliDateFormField(forms.DateField):
# --------------------------------------------------
# Nepali Date (BS) Form Field
# --------------------------------------------------
class NepaliDateFormField(forms.Field):
"""
Form field for Nepali (Bikram Sambat) date.
Accepts:
- YYYY-MM-DD (string)
- python date
- nepalidate instance
Returns:
- nepalidate instance
"""
widget = NepaliDatePickerWidget
def __init__(self, *args, **kwargs):
kwargs.pop("max_length", None)
super().__init__(*args, **kwargs)
default_error_messages = {
"invalid": _("Enter a valid Nepali date in YYYY-MM-DD format."),
}
def to_python(self, value):
if value in self.empty_values:
return None
if isinstance(value, nepalidate):
return value
if isinstance(value, python_date):
return nepalidate.from_date(value)
try:
return nepalidate.strptime(str(value).strip(), "%Y-%m-%d")
except Exception:
raise forms.ValidationError(
_("Enter a valid Nepali date in YYYY-MM-DD format."),
code="invalid",
)
if isinstance(value, str):
try:
return nepalidate.strptime(value.strip(), "%Y-%m-%d")
except (ValueError, TypeError):
self.fail("invalid")
self.fail("invalid")
def validate(self, value):
"""
Override default validation to allow nepalidate objects.
"""
if value is None:
return
if not isinstance(value, nepalidate):
self.fail("invalid")
+126 -71
View File
@@ -2,8 +2,8 @@ from datetime import date as python_date
from django.db import models
from django.utils.translation import gettext_lazy as _
from nepali.datetime import nepalidate
from nepali.locations import districts, municipalities, provices
from nepali.datetime import nepalidate, nepalidatetime
from nepali.locations import districts, municipalities, provinces
from django_nepkit.forms import NepaliDateFormField
from django_nepkit.validators import validate_nepali_phone_number
@@ -14,6 +14,10 @@ from django_nepkit.widgets import (
ProvinceSelectWidget,
)
# --------------------------------------------------
# Phone Number
# --------------------------------------------------
class NepaliPhoneNumberField(models.CharField):
description = _("Nepali phone number")
@@ -24,119 +28,133 @@ class NepaliPhoneNumberField(models.CharField):
self.validators.append(validate_nepali_phone_number)
# --------------------------------------------------
# Location Fields
# --------------------------------------------------
class ProvinceField(models.CharField):
description = _("Nepal's Province")
description = _("Nepal Province")
def __init__(self, *args, **kwargs):
kwargs.setdefault("max_length", 100)
kwargs.setdefault("choices", [(p.name, p.name) for p in provices])
kwargs.setdefault(
"choices",
[(p.name, p.name) for p in provinces],
)
super().__init__(*args, **kwargs)
def formfield(self, **kwargs):
defaults = {"widget": ProvinceSelectWidget}
defaults.update(kwargs)
return super().formfield(**defaults)
kwargs.setdefault("widget", ProvinceSelectWidget)
return super().formfield(**kwargs)
class DistrictField(models.CharField):
description = _("Nepal's District")
description = _("Nepal District")
def __init__(self, *args, **kwargs):
kwargs.setdefault("max_length", 100)
kwargs.setdefault("choices", [(d.name, d.name) for d in districts])
kwargs.setdefault(
"choices",
[(d.name, d.name) for d in districts],
)
super().__init__(*args, **kwargs)
def formfield(self, **kwargs):
defaults = {"widget": DistrictSelectWidget}
defaults.update(kwargs)
return super().formfield(**defaults)
kwargs.setdefault("widget", DistrictSelectWidget)
return super().formfield(**kwargs)
class MunicipalityField(models.CharField):
description = _("Nepal's Municipality")
description = _("Nepal Municipality")
def __init__(self, *args, **kwargs):
kwargs.setdefault("max_length", 100)
kwargs.setdefault("choices", [(m.name, m.name) for m in municipalities])
kwargs.setdefault(
"choices",
[(m.name, m.name) for m in municipalities],
)
super().__init__(*args, **kwargs)
def formfield(self, **kwargs):
defaults = {"widget": MunicipalitySelectWidget}
defaults.update(kwargs)
return super().formfield(**defaults)
kwargs.setdefault("widget", MunicipalitySelectWidget)
return super().formfield(**kwargs)
# --------------------------------------------------
# Time Field
# --------------------------------------------------
class NepaliTimeField(models.TimeField):
"""
Time field for Nepal (standard time).
This is essentially Django's TimeField,
kept for semantic clarity and future extensions.
"""
description = _("Nepali Time")
def formfield(self, **kwargs):
return super().formfield(**kwargs)
class NepaliDateField(models.CharField):
description = _("Nepali Date (Bikram Sambat)")
def __init__(self, *args, **kwargs):
self.auto_now = kwargs.pop("auto_now", False)
self.auto_now_add = kwargs.pop("auto_now_add", False)
# --------------------------------------------------
# Base Nepali BS Date / DateTime Field
# --------------------------------------------------
if self.auto_now or self.auto_now_add:
class BaseNepaliDateCharField(models.CharField):
"""
Base class for Nepali (Bikram Sambat) Date / DateTime fields.
Stored as string in DB, returned as nepali date objects in Python.
"""
format = None
nepali_type = None
def __init__(self, *args, auto_now=False, auto_now_add=False, **kwargs):
self.auto_now = auto_now
self.auto_now_add = auto_now_add
if auto_now or auto_now_add:
kwargs.setdefault("editable", False)
kwargs.setdefault("blank", True)
kwargs.setdefault("max_length", 10)
super().__init__(*args, **kwargs)
def _now(self):
raise NotImplementedError
def pre_save(self, model_instance, add):
if self.auto_now or (self.auto_now_add and add):
value = nepalidate.today().strftime("%Y-%m-%d")
value = self._now().strftime(self.format)
setattr(model_instance, self.attname, value)
return value
return super().pre_save(model_instance, add)
def to_python(self, value):
if value is None or isinstance(value, self.nepali_type):
return value
if isinstance(value, python_date):
return self.nepali_type.from_date(value)
if isinstance(value, str):
return self.nepali_type.strptime(value.strip(), self.format)
return value
def from_db_value(self, value, expression, connection):
if value is None:
return value
try:
return nepalidate.strptime(value, "%Y-%m-%d")
except (ValueError, TypeError):
return value
def to_python(self, value):
if value is None or isinstance(value, nepalidate):
return value
if isinstance(value, python_date):
try:
return nepalidate.from_date(value)
except (ValueError, TypeError):
return str(value)
if isinstance(value, str):
try:
return nepalidate.strptime(value.strip(), "%Y-%m-%d")
except (ValueError, TypeError):
return value
return super().to_python(value)
def validate(self, value, model_instance):
if isinstance(value, nepalidate):
value = value.strftime("%Y-%m-%d")
super().validate(value, model_instance)
def run_validators(self, value):
if isinstance(value, nepalidate):
value = value.strftime("%Y-%m-%d")
super().run_validators(value)
return self.nepali_type.strptime(value, self.format)
def get_prep_value(self, value):
if value is None:
return value
if isinstance(value, nepalidate):
return value.strftime("%Y-%m-%d")
if isinstance(value, python_date):
try:
return nepalidate.from_date(value).strftime("%Y-%m-%d")
except (ValueError, TypeError):
return str(value)
if isinstance(value, str):
return value
# Fallback: convert to string
return None
if isinstance(value, self.nepali_type):
return value.strftime(self.format)
return str(value)
def deconstruct(self):
@@ -147,10 +165,47 @@ class NepaliDateField(models.CharField):
kwargs["auto_now_add"] = True
return name, path, args, kwargs
# --------------------------------------------------
# Nepali Date Field (BS)
# --------------------------------------------------
class NepaliDateField(BaseNepaliDateCharField):
description = _("Nepali Date (Bikram Sambat)")
format = "%Y-%m-%d"
nepali_type = nepalidate
def __init__(self, *args, **kwargs):
kwargs.setdefault("max_length", 10)
super().__init__(*args, **kwargs)
def _now(self):
return nepalidate.today()
def formfield(self, **kwargs):
defaults = {
"form_class": NepaliDateFormField,
"widget": NepaliDatePickerWidget,
}
defaults.update(kwargs)
return super().formfield(**defaults)
kwargs.setdefault("form_class", NepaliDateFormField)
kwargs.setdefault("widget", NepaliDatePickerWidget)
return super().formfield(**kwargs)
# --------------------------------------------------
# Nepali DateTime Field (BS)
# --------------------------------------------------
class NepaliDateTimeField(BaseNepaliDateCharField):
description = _("Nepali DateTime (Bikram Sambat)")
format = "%Y-%m-%d %H:%M:%S"
nepali_type = nepalidatetime
def __init__(self, *args, **kwargs):
kwargs.setdefault("max_length", 19)
super().__init__(*args, **kwargs)
def _now(self):
return nepalidatetime.now()
def formfield(self, **kwargs):
kwargs.setdefault("widget", NepaliDatePickerWidget)
return super().formfield(**kwargs)
+69
View File
@@ -0,0 +1,69 @@
from nepali.datetime import nepalidate, nepalidatetime
from rest_framework import serializers
# --------------------------------------------------
# Base Serializer Field
# --------------------------------------------------
class BaseNepaliBSField(serializers.Field):
"""
Base DRF field for Nepali (BS) Date / DateTime.
"""
format = None
nepali_type = None
error_messages = {"invalid": "Invalid Bikram Sambat format. Expected {format}."}
def to_representation(self, value):
if value is None:
return None
if isinstance(value, self.nepali_type):
return value.strftime(self.format)
# Fallback: DB string
return str(value)
def to_internal_value(self, data):
if data in (None, ""):
return None
if isinstance(data, self.nepali_type):
return data
if isinstance(data, str):
try:
return self.nepali_type.strptime(data.strip(), self.format)
except Exception:
self.fail("invalid", format=self.format)
self.fail("invalid", format=self.format)
# --------------------------------------------------
# Nepali Date (BS)
# --------------------------------------------------
class NepaliDateSerializerField(BaseNepaliBSField):
"""
DRF field for Nepali BS Date (YYYY-MM-DD)
"""
format = "%Y-%m-%d"
nepali_type = nepalidate
# --------------------------------------------------
# Nepali DateTime (BS)
# --------------------------------------------------
class NepaliDateTimeSerializerField(BaseNepaliBSField):
"""
DRF field for Nepali BS DateTime (YYYY-MM-DD HH:MM:SS)
"""
format = "%Y-%m-%d %H:%M:%S"
nepali_type = nepalidatetime
+61 -28
View File
@@ -1,67 +1,100 @@
import datetime
from django import template
from django.utils.safestring import mark_safe
from nepali.datetime import nepalidate, nepalihumanize
from nepali.number import nepalinumber
register = template.Library()
@register.filter
# --------------------------------------------------
# Internal helpers
# --------------------------------------------------
def _to_nepali_date(value):
"""
Normalize AD date/datetime or BS date to nepalidate.
"""
if value is None:
return None
if isinstance(value, nepalidate):
return value
if isinstance(value, (datetime.date, datetime.datetime)):
return nepalidate.from_date(value)
return None
# --------------------------------------------------
# Date filters
# --------------------------------------------------
@register.filter(is_safe=True)
def nepali_date(value, format_str="%Y-%m-%d"):
"""
Formats a date or datetime object into a Nepali date string.
Format a date/datetime as Nepali (BS) date.
"""
if value is None:
nd = _to_nepali_date(value)
if not nd:
return ""
if isinstance(value, (datetime.datetime, datetime.date)):
value = nepalidate.from_date(value)
if hasattr(value, "strftime"):
return value.strftime(format_str)
return value
return nd.strftime(format_str)
@register.filter
@register.filter(is_safe=True)
def nepali_date_ne(value, format_str="%Y-%m-%d"):
"""
Formats a date or datetime object into a Nepali date string (Devanagari).
Format a date/datetime as Nepali (BS) date in Devanagari.
"""
if value is None:
nd = _to_nepali_date(value)
if not nd:
return ""
if isinstance(value, (datetime.datetime, datetime.date)):
value = nepalidate.from_date(value)
if hasattr(value, "strftime_ne"):
return value.strftime_ne(format_str)
return value
return nd.strftime_ne(format_str)
@register.filter
# --------------------------------------------------
# Number filter
# --------------------------------------------------
@register.filter(is_safe=True)
def nepali_number(value):
"""
Converts a number to Devanagari.
Convert a number to Nepali (Devanagari) digits.
"""
if value is None:
if value in (None, ""):
return ""
return nepalinumber(value).str_ne()
try:
return nepalinumber(value).str_ne()
except Exception:
return value
@register.filter
# --------------------------------------------------
# Humanize filter
# --------------------------------------------------
@register.filter(is_safe=True)
def nepali_humanize(value, threshold=None, format_str=None):
"""
Returns a human-readable "time ago" string in Nepali.
Human-readable Nepali time difference.
"""
if value is None:
return ""
# Threshold and format_str are optional
kwargs = {}
if threshold:
if threshold is not None:
kwargs["threshold"] = threshold
if format_str:
if format_str is not None:
kwargs["format"] = format_str
return nepalihumanize(value, **kwargs)
try:
return nepalihumanize(value, **kwargs)
except Exception:
return value
+62 -17
View File
@@ -2,39 +2,82 @@ from django import forms
from django.urls import reverse_lazy
class ChainedSelectWidget(forms.Select):
# --------------------------------------------------
# Utility mixin
# --------------------------------------------------
class ClassAttrMixin:
"""
Safely append CSS classes to widget attrs.
"""
css_class = ""
def _build_attrs(self, attrs):
attrs = attrs or {}
classes = attrs.get("class", "").split()
if self.css_class:
classes.append(self.css_class)
attrs["class"] = " ".join(dict.fromkeys(classes))
return attrs
# --------------------------------------------------
# Chained Select Base
# --------------------------------------------------
class ChainedSelectWidget(ClassAttrMixin, forms.Select):
"""
Base widget for chained location selects.
"""
class Media:
js = ("django_nepkit/js/address-chaining.js",)
# --------------------------------------------------
# Province / District / Municipality Widgets
# --------------------------------------------------
class ProvinceSelectWidget(ChainedSelectWidget):
css_class = "nepkit-province-select"
def __init__(self, *args, **kwargs):
attrs = kwargs.get("attrs", {})
attrs["class"] = attrs.get("class", "") + " nepkit-province-select"
kwargs["attrs"] = attrs
kwargs["attrs"] = self._build_attrs(kwargs.get("attrs"))
super().__init__(*args, **kwargs)
class DistrictSelectWidget(ChainedSelectWidget):
css_class = "nepkit-district-select"
def __init__(self, *args, **kwargs):
attrs = kwargs.get("attrs", {})
attrs["class"] = attrs.get("class", "") + " nepkit-district-select"
attrs["data-url"] = reverse_lazy("django_nepkit:district-list")
attrs = self._build_attrs(kwargs.get("attrs"))
attrs.setdefault("data-url", reverse_lazy("django_nepkit:district-list"))
kwargs["attrs"] = attrs
super().__init__(*args, **kwargs)
class MunicipalitySelectWidget(ChainedSelectWidget):
css_class = "nepkit-municipality-select"
def __init__(self, *args, **kwargs):
attrs = kwargs.get("attrs", {})
attrs["class"] = attrs.get("class", "") + " nepkit-municipality-select"
attrs["data-url"] = reverse_lazy("django_nepkit:municipality-list")
attrs = self._build_attrs(kwargs.get("attrs"))
attrs.setdefault("data-url", reverse_lazy("django_nepkit:municipality-list"))
kwargs["attrs"] = attrs
super().__init__(*args, **kwargs)
class NepaliDatePickerWidget(forms.TextInput):
# --------------------------------------------------
# Nepali Date Picker
# --------------------------------------------------
class NepaliDatePickerWidget(ClassAttrMixin, forms.TextInput):
"""
Nepali BS date picker widget.
"""
input_type = "text"
css_class = "nepkit-datepicker"
class Media:
css = {
@@ -50,12 +93,14 @@ class NepaliDatePickerWidget(forms.TextInput):
def __init__(self, *args, **kwargs):
attrs = kwargs.get("attrs", {})
classes = attrs.get("class", "")
if "vDateField" in classes:
classes = classes.replace("vDateField", "")
attrs["class"] = (classes + " nepkit-datepicker").strip()
attrs["autocomplete"] = "off"
attrs["placeholder"] = "YYYY-MM-DD"
# Remove Django admin default class
classes = attrs.get("class", "").replace("vDateField", "").strip()
attrs["class"] = classes
attrs = self._build_attrs(attrs)
attrs.setdefault("autocomplete", "off")
attrs.setdefault("placeholder", "YYYY-MM-DD")
kwargs["attrs"] = attrs
super().__init__(*args, **kwargs)