270 lines
8.8 KiB
Python
270 lines
8.8 KiB
Python
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, nepalidatetime
|
|
|
|
from django_nepkit.utils import (
|
|
BS_DATE_FORMAT,
|
|
BS_DATETIME_FORMAT,
|
|
try_parse_nepali_date,
|
|
try_parse_nepali_datetime,
|
|
)
|
|
from django_nepkit.validators import validate_nepali_phone_number
|
|
|
|
|
|
class NepaliPhoneNumberField(models.CharField):
|
|
description = _("Nepali Phone Number")
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
kwargs.setdefault("max_length", 10)
|
|
super().__init__(*args, **kwargs)
|
|
self.validators.append(validate_nepali_phone_number)
|
|
|
|
|
|
class NepaliDateField(models.CharField):
|
|
"""
|
|
A Django Model Field that stores Nepali (Bikram Sambat) Date.
|
|
Internally it stores the date as BS string (YYYY-MM-DD) in the database.
|
|
"""
|
|
|
|
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)
|
|
|
|
# Match Django's DateField behavior
|
|
if self.auto_now or self.auto_now_add:
|
|
kwargs.setdefault("editable", False)
|
|
kwargs.setdefault("blank", True)
|
|
|
|
kwargs.setdefault("max_length", 10)
|
|
super().__init__(*args, **kwargs)
|
|
|
|
def pre_save(self, model_instance, add):
|
|
if self.auto_now or (self.auto_now_add and add):
|
|
# Using nepalidate.today() to get current BS date.
|
|
# This is BS-native as much as possible.
|
|
value = nepalidate.today().strftime(BS_DATE_FORMAT)
|
|
setattr(model_instance, self.attname, value)
|
|
return value
|
|
return super().pre_save(model_instance, add)
|
|
|
|
def from_db_value(self, value, expression, connection):
|
|
parsed = try_parse_nepali_date(value)
|
|
return parsed if parsed is not None else value
|
|
|
|
def to_python(self, value):
|
|
if value is None or isinstance(value, nepalidate):
|
|
return value
|
|
if isinstance(value, python_date):
|
|
# If we MUST handle AD date object, we convert it to BS.
|
|
# But we should prefer strings or nepalidate.
|
|
try:
|
|
return nepalidate.from_date(value)
|
|
except (ValueError, TypeError):
|
|
return str(value)
|
|
if isinstance(value, str):
|
|
parsed = try_parse_nepali_date(value)
|
|
return parsed if parsed is not None else value
|
|
return super().to_python(value)
|
|
|
|
def validate(self, value, model_instance):
|
|
if isinstance(value, nepalidate):
|
|
value = value.strftime(BS_DATE_FORMAT)
|
|
super().validate(value, model_instance)
|
|
|
|
def run_validators(self, value):
|
|
if isinstance(value, nepalidate):
|
|
value = value.strftime(BS_DATE_FORMAT)
|
|
super().run_validators(value)
|
|
|
|
def get_prep_value(self, value):
|
|
if value is None:
|
|
return value
|
|
if isinstance(value, nepalidate):
|
|
return value.strftime(BS_DATE_FORMAT)
|
|
if isinstance(value, python_date):
|
|
try:
|
|
return nepalidate.from_date(value).strftime(BS_DATE_FORMAT)
|
|
except (ValueError, TypeError):
|
|
return str(value)
|
|
if isinstance(value, str):
|
|
return value
|
|
# Fallback: convert to string
|
|
return str(value)
|
|
|
|
def deconstruct(self):
|
|
name, path, args, kwargs = super().deconstruct()
|
|
if self.auto_now:
|
|
kwargs["auto_now"] = True
|
|
if self.auto_now_add:
|
|
kwargs["auto_now_add"] = True
|
|
return name, path, args, kwargs
|
|
|
|
def formfield(self, **kwargs):
|
|
from .forms import NepaliDateFormField
|
|
from .widgets import NepaliDatePickerWidget
|
|
|
|
defaults = {
|
|
"form_class": NepaliDateFormField,
|
|
"widget": NepaliDatePickerWidget,
|
|
}
|
|
defaults.update(kwargs)
|
|
return super().formfield(**defaults)
|
|
|
|
|
|
class NepaliTimeField(models.TimeField):
|
|
"""
|
|
A Django Model Field for Time with Nepali localization support.
|
|
Supports auto_now and auto_now_add like standard Django TimeField.
|
|
"""
|
|
|
|
description = _("Nepali Time")
|
|
|
|
|
|
class NepaliDateTimeField(models.CharField):
|
|
"""
|
|
A Django Model Field that stores Nepali (Bikram Sambat) DateTime.
|
|
Internally it stores the datetime as BS string (YYYY-MM-DD HH:MM:SS) in the database.
|
|
"""
|
|
|
|
description = _("Nepali DateTime (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)
|
|
|
|
# Match Django's DateTimeField behavior
|
|
if self.auto_now or self.auto_now_add:
|
|
kwargs.setdefault("editable", False)
|
|
kwargs.setdefault("blank", True)
|
|
|
|
kwargs.setdefault("max_length", 19) # YYYY-MM-DD HH:MM:SS
|
|
super().__init__(*args, **kwargs)
|
|
|
|
def pre_save(self, model_instance, add):
|
|
if self.auto_now or (self.auto_now_add and add):
|
|
# Using nepalidatetime.now() to get current BS datetime
|
|
value = nepalidatetime.now().strftime(BS_DATETIME_FORMAT)
|
|
setattr(model_instance, self.attname, value)
|
|
return value
|
|
return super().pre_save(model_instance, add)
|
|
|
|
def from_db_value(self, value, expression, connection):
|
|
parsed = try_parse_nepali_datetime(value)
|
|
return parsed if parsed is not None else value
|
|
|
|
def to_python(self, value):
|
|
if value is None or isinstance(value, nepalidatetime):
|
|
return value
|
|
if isinstance(value, str):
|
|
parsed = try_parse_nepali_datetime(value)
|
|
return parsed if parsed is not None else value
|
|
return super().to_python(value)
|
|
|
|
def validate(self, value, model_instance):
|
|
if isinstance(value, nepalidatetime):
|
|
value = value.strftime(BS_DATETIME_FORMAT)
|
|
super().validate(value, model_instance)
|
|
|
|
def run_validators(self, value):
|
|
if isinstance(value, nepalidatetime):
|
|
value = value.strftime(BS_DATETIME_FORMAT)
|
|
super().run_validators(value)
|
|
|
|
def get_prep_value(self, value):
|
|
if value is None:
|
|
return value
|
|
if isinstance(value, nepalidatetime):
|
|
return value.strftime(BS_DATETIME_FORMAT)
|
|
if isinstance(value, str):
|
|
return value
|
|
# Fallback: convert to string
|
|
return str(value)
|
|
|
|
def deconstruct(self):
|
|
name, path, args, kwargs = super().deconstruct()
|
|
if self.auto_now:
|
|
kwargs["auto_now"] = True
|
|
if self.auto_now_add:
|
|
kwargs["auto_now_add"] = True
|
|
return name, path, args, kwargs
|
|
|
|
def formfield(self, **kwargs):
|
|
from .widgets import NepaliDatePickerWidget
|
|
|
|
defaults = {
|
|
"widget": NepaliDatePickerWidget,
|
|
}
|
|
defaults.update(kwargs)
|
|
return super().formfield(**defaults)
|
|
|
|
|
|
class ProvinceField(models.CharField):
|
|
"""
|
|
A Django Model Field for Nepali Provinces.
|
|
"""
|
|
|
|
description = _("Nepali Province")
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
from nepali.locations import provinces
|
|
|
|
kwargs.setdefault("max_length", 100)
|
|
kwargs.setdefault("choices", [(p.name, p.name) for p in provinces])
|
|
super().__init__(*args, **kwargs)
|
|
|
|
def formfield(self, **kwargs):
|
|
from .widgets import ProvinceSelectWidget
|
|
|
|
defaults = {"widget": ProvinceSelectWidget}
|
|
defaults.update(kwargs)
|
|
return super().formfield(**defaults)
|
|
|
|
|
|
class DistrictField(models.CharField):
|
|
"""
|
|
A Django Model Field for Nepali Districts.
|
|
"""
|
|
|
|
description = _("Nepali District")
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
from nepali.locations import districts
|
|
|
|
kwargs.setdefault("max_length", 100)
|
|
kwargs.setdefault("choices", [(d.name, d.name) for d in districts])
|
|
super().__init__(*args, **kwargs)
|
|
|
|
def formfield(self, **kwargs):
|
|
from .widgets import DistrictSelectWidget
|
|
|
|
defaults = {"widget": DistrictSelectWidget}
|
|
defaults.update(kwargs)
|
|
return super().formfield(**defaults)
|
|
|
|
|
|
class MunicipalityField(models.CharField):
|
|
"""
|
|
A Django Model Field for Nepali Municipalities.
|
|
Includes Metropolitan, Sub-Metropolitan, Municipality, and Rural Municipality.
|
|
"""
|
|
|
|
description = _("Nepali Municipality")
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
from nepali.locations import municipalities
|
|
|
|
kwargs.setdefault("max_length", 100)
|
|
kwargs.setdefault("choices", [(m.name, m.name) for m in municipalities])
|
|
super().__init__(*args, **kwargs)
|
|
|
|
def formfield(self, **kwargs):
|
|
from .widgets import MunicipalitySelectWidget
|
|
|
|
defaults = {"widget": MunicipalitySelectWidget}
|
|
defaults.update(kwargs)
|
|
return super().formfield(**defaults)
|