django-nepkit 0.1.0__py3-none-any.whl → 0.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- django_nepkit/__init__.py +20 -0
- django_nepkit/admin.py +243 -93
- django_nepkit/conf.py +34 -0
- django_nepkit/constants.py +129 -0
- django_nepkit/filters.py +113 -0
- django_nepkit/forms.py +9 -9
- django_nepkit/lang_utils.py +52 -0
- django_nepkit/models.py +138 -161
- django_nepkit/serializers.py +127 -28
- django_nepkit/static/django_nepkit/js/address-chaining.js +129 -29
- django_nepkit/static/django_nepkit/js/nepal-data.js +1 -0
- django_nepkit/static/django_nepkit/js/nepali-datepicker-init.js +55 -46
- django_nepkit/utils.py +270 -46
- django_nepkit/validators.py +1 -1
- django_nepkit/views.py +127 -10
- django_nepkit/widgets.py +100 -31
- django_nepkit-0.2.1.dist-info/METADATA +308 -0
- django_nepkit-0.2.1.dist-info/RECORD +37 -0
- example/demo/admin.py +45 -21
- example/demo/models.py +41 -4
- example/demo/serializers.py +39 -0
- example/demo/urls.py +13 -2
- example/demo/views.py +125 -3
- example/example_project/settings.py +10 -0
- example/example_project/urls.py +1 -1
- example/manage.py +2 -2
- django_nepkit/templatetags/__init__.py +0 -0
- django_nepkit/templatetags/nepali.py +0 -74
- django_nepkit-0.1.0.dist-info/METADATA +0 -377
- django_nepkit-0.1.0.dist-info/RECORD +0 -39
- example/demo/migrations/0001_initial.py +0 -2113
- example/demo/migrations/0002_alter_person_phone_number.py +0 -18
- example/demo/migrations/0003_person_created_at_person_updated_at.py +0 -27
- example/demo/migrations/0004_alter_person_created_at_alter_person_updated_at.py +0 -23
- example/demo/migrations/0005_alter_person_created_at_alter_person_updated_at.py +0 -27
- example/demo/migrations/__init__.py +0 -0
- {django_nepkit-0.1.0.dist-info → django_nepkit-0.2.1.dist-info}/WHEEL +0 -0
- {django_nepkit-0.1.0.dist-info → django_nepkit-0.2.1.dist-info}/licenses/LICENSE +0 -0
- {django_nepkit-0.1.0.dist-info → django_nepkit-0.2.1.dist-info}/top_level.txt +0 -0
django_nepkit/filters.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from django.db.models import QuerySet
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
from django_filters import rest_framework as filters
|
|
9
|
+
except ImportError as e:
|
|
10
|
+
raise ModuleNotFoundError(
|
|
11
|
+
"django-nepkit filter support is optional. Install with `django-nepkit[drf]` "
|
|
12
|
+
"to use `django_nepkit.filters`."
|
|
13
|
+
) from e
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class NepaliDateYearFilter(filters.NumberFilter):
|
|
17
|
+
"""
|
|
18
|
+
A filter for `NepaliDateField` that allows filtering by Bikram Sambat Year.
|
|
19
|
+
Expects an integer year (e.g., 2080).
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def filter(self, qs: QuerySet, value: Any) -> QuerySet:
|
|
23
|
+
if value:
|
|
24
|
+
from django_nepkit.utils import BS_DATE_FORMAT
|
|
25
|
+
|
|
26
|
+
# Find the date separator (e.g., '-' in '2080-01-01')
|
|
27
|
+
if BS_DATE_FORMAT.startswith("%Y"):
|
|
28
|
+
separator = BS_DATE_FORMAT[2] if len(BS_DATE_FORMAT) > 2 else "-"
|
|
29
|
+
return qs.filter(
|
|
30
|
+
**{f"{self.field_name}__startswith": f"{value}{separator}"}
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
# Fallback if the format is unusual
|
|
34
|
+
return qs.filter(**{f"{self.field_name}__icontains": str(value)})
|
|
35
|
+
return qs
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class NepaliDateMonthFilter(filters.NumberFilter):
|
|
39
|
+
"""
|
|
40
|
+
A filter for `NepaliDateField` that allows filtering by Bikram Sambat Month.
|
|
41
|
+
Expects an integer month (1-12).
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def filter(self, qs: QuerySet, value: Any) -> QuerySet:
|
|
45
|
+
if value:
|
|
46
|
+
from django_nepkit.utils import BS_DATE_FORMAT
|
|
47
|
+
|
|
48
|
+
month_str = f"{int(value):02d}"
|
|
49
|
+
|
|
50
|
+
# Standard format: look for '-01-' for Baisakh
|
|
51
|
+
if BS_DATE_FORMAT == "%Y-%m-%d":
|
|
52
|
+
return qs.filter(**{f"{self.field_name}__contains": f"-{month_str}-"})
|
|
53
|
+
|
|
54
|
+
# Adaptive check for other separators
|
|
55
|
+
separator = BS_DATE_FORMAT[2] if len(BS_DATE_FORMAT) > 2 else "-"
|
|
56
|
+
return qs.filter(
|
|
57
|
+
**{f"{self.field_name}__contains": f"{separator}{month_str}{separator}"}
|
|
58
|
+
)
|
|
59
|
+
return qs
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class NepaliDateRangeFilter(filters.CharFilter):
|
|
63
|
+
"""
|
|
64
|
+
A filter for `NepaliDateField` that allows filtering by a range of BS dates.
|
|
65
|
+
Supports:
|
|
66
|
+
- "start,end" -> range
|
|
67
|
+
- "start," -> greater than or equal
|
|
68
|
+
- ",end" -> less than or equal
|
|
69
|
+
- "date" -> exact match
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
def filter(self, qs: QuerySet, value: Any) -> QuerySet:
|
|
73
|
+
if value:
|
|
74
|
+
parts = [p.strip() for p in str(value).split(",")]
|
|
75
|
+
if len(parts) == 1:
|
|
76
|
+
return qs.filter(**{self.field_name: parts[0]})
|
|
77
|
+
if len(parts) == 2:
|
|
78
|
+
if parts[0] and parts[1]:
|
|
79
|
+
return qs.filter(
|
|
80
|
+
**{f"{self.field_name}__range": (parts[0], parts[1])}
|
|
81
|
+
)
|
|
82
|
+
if parts[0]:
|
|
83
|
+
return qs.filter(**{f"{self.field_name}__gte": parts[0]})
|
|
84
|
+
if parts[1]:
|
|
85
|
+
return qs.filter(**{f"{self.field_name}__lte": parts[1]})
|
|
86
|
+
return qs
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class NepaliCurrencyRangeFilter(filters.CharFilter):
|
|
90
|
+
"""
|
|
91
|
+
A filter for `NepaliCurrencyField` that allows range filtering.
|
|
92
|
+
Supports:
|
|
93
|
+
- "min,max" -> range
|
|
94
|
+
- "min," -> greater than or equal
|
|
95
|
+
- ",max" -> less than or equal
|
|
96
|
+
- "value" -> exact match
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
def filter(self, qs: QuerySet, value: Any) -> QuerySet:
|
|
100
|
+
if value:
|
|
101
|
+
parts = [p.strip() for p in str(value).split(",")]
|
|
102
|
+
if len(parts) == 1:
|
|
103
|
+
return qs.filter(**{self.field_name: parts[0]})
|
|
104
|
+
if len(parts) == 2:
|
|
105
|
+
if parts[0] and parts[1]:
|
|
106
|
+
return qs.filter(
|
|
107
|
+
**{f"{self.field_name}__range": (parts[0], parts[1])}
|
|
108
|
+
)
|
|
109
|
+
if parts[0]:
|
|
110
|
+
return qs.filter(**{f"{self.field_name}__gte": parts[0]})
|
|
111
|
+
if parts[1]:
|
|
112
|
+
return qs.filter(**{f"{self.field_name}__lte": parts[1]})
|
|
113
|
+
return qs
|
django_nepkit/forms.py
CHANGED
|
@@ -6,19 +6,18 @@ from nepali.datetime import nepalidate
|
|
|
6
6
|
|
|
7
7
|
from django_nepkit.utils import try_parse_nepali_date
|
|
8
8
|
from django_nepkit.validators import validate_nepali_phone_number
|
|
9
|
+
from django_nepkit.widgets import NepaliDatePickerWidget
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
class NepaliDateFormField(forms.DateField):
|
|
12
|
-
"""
|
|
13
|
-
A Django Form Field for Nepali Date (Bikram Sambat).
|
|
14
|
-
"""
|
|
15
|
-
|
|
16
|
-
from .widgets import NepaliDatePickerWidget
|
|
13
|
+
"""A form field for entering Nepali dates."""
|
|
17
14
|
|
|
18
15
|
widget = NepaliDatePickerWidget
|
|
19
16
|
|
|
20
17
|
def __init__(self, *args, **kwargs):
|
|
21
18
|
kwargs.pop("max_length", None)
|
|
19
|
+
# Clean up arguments not needed for base Field
|
|
20
|
+
kwargs.pop("empty_value", None)
|
|
22
21
|
super().__init__(*args, **kwargs)
|
|
23
22
|
|
|
24
23
|
def to_python(self, value):
|
|
@@ -34,16 +33,17 @@ class NepaliDateFormField(forms.DateField):
|
|
|
34
33
|
return parsed
|
|
35
34
|
raise ValueError("Invalid BS date format")
|
|
36
35
|
except Exception:
|
|
36
|
+
from django_nepkit.utils import BS_DATE_FORMAT
|
|
37
|
+
|
|
37
38
|
raise forms.ValidationError(
|
|
38
|
-
_("Enter a valid Nepali date in
|
|
39
|
+
_("Enter a valid Nepali date in %(format)s format.")
|
|
40
|
+
% {"format": BS_DATE_FORMAT},
|
|
39
41
|
code="invalid",
|
|
40
42
|
)
|
|
41
43
|
|
|
42
44
|
|
|
43
45
|
class NepaliPhoneNumberFormField(forms.CharField):
|
|
44
|
-
"""
|
|
45
|
-
A Django Form Field for Nepali Phone Numbers.
|
|
46
|
-
"""
|
|
46
|
+
"""A form field for entering Nepali phone numbers."""
|
|
47
47
|
|
|
48
48
|
def __init__(self, *args, **kwargs):
|
|
49
49
|
super().__init__(*args, **kwargs)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Language parameter utilities for django-nepkit.
|
|
3
|
+
Helper functions to resolve Nepali/English language parameters consistently.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from django_nepkit.conf import nepkit_settings
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def resolve_language_params(ne=None, en=None, **kwargs):
|
|
10
|
+
"""
|
|
11
|
+
Resolve language parameters with fallback to default settings.
|
|
12
|
+
"""
|
|
13
|
+
default_lang = nepkit_settings.DEFAULT_LANGUAGE
|
|
14
|
+
|
|
15
|
+
# Extract from kwargs if provided
|
|
16
|
+
if ne is None and "ne" in kwargs:
|
|
17
|
+
ne = kwargs.get("ne")
|
|
18
|
+
if en is None and "en" in kwargs:
|
|
19
|
+
en = kwargs.get("en")
|
|
20
|
+
|
|
21
|
+
# Set defaults
|
|
22
|
+
if ne is None:
|
|
23
|
+
ne = default_lang == "ne"
|
|
24
|
+
|
|
25
|
+
# Handle en parameter logic
|
|
26
|
+
explicit_en = en is not None
|
|
27
|
+
if en is None:
|
|
28
|
+
en = not ne
|
|
29
|
+
|
|
30
|
+
# If ne is True and en wasn't explicitly set, en should be False
|
|
31
|
+
if ne and not explicit_en:
|
|
32
|
+
en = False
|
|
33
|
+
|
|
34
|
+
return ne, en
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def pop_language_params(kwargs):
|
|
38
|
+
"""
|
|
39
|
+
Pop ne/en parameters from kwargs dict and return resolved values.
|
|
40
|
+
"""
|
|
41
|
+
default_lang = nepkit_settings.DEFAULT_LANGUAGE
|
|
42
|
+
ne = kwargs.pop("ne", default_lang == "ne")
|
|
43
|
+
|
|
44
|
+
explicit_en = "en" in kwargs
|
|
45
|
+
en_value = kwargs.pop("en", not ne)
|
|
46
|
+
|
|
47
|
+
if ne and not explicit_en:
|
|
48
|
+
en = False
|
|
49
|
+
else:
|
|
50
|
+
en = en_value
|
|
51
|
+
|
|
52
|
+
return ne, en
|
django_nepkit/models.py
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
from datetime import date as python_date
|
|
2
|
+
from datetime import datetime as python_datetime
|
|
2
3
|
|
|
3
4
|
from django.db import models
|
|
5
|
+
from django.utils import timezone
|
|
4
6
|
from django.utils.translation import gettext_lazy as _
|
|
5
7
|
from nepali.datetime import nepalidate, nepalidatetime
|
|
8
|
+
from nepali.locations import districts, municipalities, provinces
|
|
6
9
|
|
|
7
10
|
from django_nepkit.utils import (
|
|
8
11
|
BS_DATE_FORMAT,
|
|
@@ -11,6 +14,43 @@ from django_nepkit.utils import (
|
|
|
11
14
|
try_parse_nepali_datetime,
|
|
12
15
|
)
|
|
13
16
|
from django_nepkit.validators import validate_nepali_phone_number
|
|
17
|
+
from django_nepkit.widgets import (
|
|
18
|
+
DistrictSelectWidget,
|
|
19
|
+
MunicipalitySelectWidget,
|
|
20
|
+
NepaliDatePickerWidget,
|
|
21
|
+
ProvinceSelectWidget,
|
|
22
|
+
)
|
|
23
|
+
from django_nepkit.conf import nepkit_settings
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class NepaliFieldMixin:
|
|
27
|
+
"""Adds Nepali 'ne' and 'en' support to any field."""
|
|
28
|
+
|
|
29
|
+
def __init__(self, *args, **kwargs):
|
|
30
|
+
default_lang = nepkit_settings.DEFAULT_LANGUAGE
|
|
31
|
+
self.ne = kwargs.pop("ne", default_lang == "ne")
|
|
32
|
+
|
|
33
|
+
explicit_en = "en" in kwargs
|
|
34
|
+
en_value = kwargs.pop("en", not self.ne)
|
|
35
|
+
|
|
36
|
+
if self.ne and not explicit_en:
|
|
37
|
+
self.en = False
|
|
38
|
+
else:
|
|
39
|
+
self.en = en_value
|
|
40
|
+
|
|
41
|
+
self.htmx = kwargs.pop("htmx", False)
|
|
42
|
+
|
|
43
|
+
super().__init__(*args, **kwargs)
|
|
44
|
+
|
|
45
|
+
def deconstruct(self):
|
|
46
|
+
name, path, args, kwargs = super().deconstruct()
|
|
47
|
+
if self.ne:
|
|
48
|
+
kwargs["ne"] = True
|
|
49
|
+
if not self.en:
|
|
50
|
+
kwargs["en"] = False
|
|
51
|
+
if self.htmx:
|
|
52
|
+
kwargs["htmx"] = True
|
|
53
|
+
return name, path, args, kwargs
|
|
14
54
|
|
|
15
55
|
|
|
16
56
|
class NepaliPhoneNumberField(models.CharField):
|
|
@@ -22,77 +62,89 @@ class NepaliPhoneNumberField(models.CharField):
|
|
|
22
62
|
self.validators.append(validate_nepali_phone_number)
|
|
23
63
|
|
|
24
64
|
|
|
25
|
-
class
|
|
26
|
-
"""
|
|
27
|
-
A Django Model Field that stores Nepali (Bikram Sambat) Date.
|
|
28
|
-
Internally it stores the date as BS string (YYYY-MM-DD) in the database.
|
|
29
|
-
"""
|
|
30
|
-
|
|
31
|
-
description = _("Nepali Date (Bikram Sambat)")
|
|
65
|
+
class BaseNepaliBSField(NepaliFieldMixin, models.CharField):
|
|
66
|
+
"""Base class for Nepali date and datetime fields."""
|
|
32
67
|
|
|
33
68
|
def __init__(self, *args, **kwargs):
|
|
34
69
|
self.auto_now = kwargs.pop("auto_now", False)
|
|
35
70
|
self.auto_now_add = kwargs.pop("auto_now_add", False)
|
|
36
71
|
|
|
37
|
-
# Match Django's DateField behavior
|
|
38
72
|
if self.auto_now or self.auto_now_add:
|
|
39
73
|
kwargs.setdefault("editable", False)
|
|
40
74
|
kwargs.setdefault("blank", True)
|
|
41
75
|
|
|
42
|
-
kwargs.setdefault("max_length",
|
|
76
|
+
kwargs.setdefault("max_length", getattr(self, "default_max_length", 20))
|
|
43
77
|
super().__init__(*args, **kwargs)
|
|
44
78
|
|
|
45
79
|
def pre_save(self, model_instance, add):
|
|
46
80
|
if self.auto_now or (self.auto_now_add and add):
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
81
|
+
now = timezone.now()
|
|
82
|
+
if timezone.is_aware(now):
|
|
83
|
+
now = timezone.localtime(now)
|
|
84
|
+
|
|
85
|
+
nepali_cls = getattr(self, "nepali_cls", nepalidate)
|
|
86
|
+
format_str = getattr(self, "format_str", BS_DATE_FORMAT)
|
|
87
|
+
|
|
88
|
+
value = (
|
|
89
|
+
nepali_cls.from_date(now).strftime(format_str)
|
|
90
|
+
if nepali_cls == nepalidate
|
|
91
|
+
else nepali_cls.from_datetime(now).strftime(format_str)
|
|
92
|
+
)
|
|
50
93
|
setattr(model_instance, self.attname, value)
|
|
51
94
|
return value
|
|
52
95
|
return super().pre_save(model_instance, add)
|
|
53
96
|
|
|
54
97
|
def from_db_value(self, value, expression, connection):
|
|
55
|
-
parsed =
|
|
98
|
+
parsed = self.parse_func(value)
|
|
56
99
|
return parsed if parsed is not None else value
|
|
57
100
|
|
|
58
101
|
def to_python(self, value):
|
|
59
|
-
if value is None or isinstance(value,
|
|
102
|
+
if value is None or isinstance(value, self.nepali_cls):
|
|
60
103
|
return value
|
|
61
|
-
if isinstance(value, python_date):
|
|
62
|
-
# If we MUST handle AD date object, we convert it to BS.
|
|
63
|
-
# But we should prefer strings or nepalidate.
|
|
104
|
+
if isinstance(value, (python_date, python_datetime)):
|
|
64
105
|
try:
|
|
65
|
-
|
|
106
|
+
if isinstance(value, python_datetime) and timezone.is_aware(value):
|
|
107
|
+
value = timezone.localtime(value)
|
|
108
|
+
return (
|
|
109
|
+
self.nepali_cls.from_date(value)
|
|
110
|
+
if self.nepali_cls == nepalidate
|
|
111
|
+
else self.nepali_cls.from_datetime(value)
|
|
112
|
+
)
|
|
66
113
|
except (ValueError, TypeError):
|
|
67
114
|
return str(value)
|
|
68
115
|
if isinstance(value, str):
|
|
69
|
-
parsed =
|
|
116
|
+
parsed = self.parse_func(value)
|
|
70
117
|
return parsed if parsed is not None else value
|
|
71
118
|
return super().to_python(value)
|
|
72
119
|
|
|
120
|
+
def _get_string_value(self, value):
|
|
121
|
+
if isinstance(value, (nepalidate, nepalidatetime)):
|
|
122
|
+
return value.strftime(self.format_str)
|
|
123
|
+
return value
|
|
124
|
+
|
|
73
125
|
def validate(self, value, model_instance):
|
|
74
|
-
|
|
75
|
-
value = value.strftime(BS_DATE_FORMAT)
|
|
76
|
-
super().validate(value, model_instance)
|
|
126
|
+
super().validate(self._get_string_value(value), model_instance)
|
|
77
127
|
|
|
78
128
|
def run_validators(self, value):
|
|
79
|
-
|
|
80
|
-
value = value.strftime(BS_DATE_FORMAT)
|
|
81
|
-
super().run_validators(value)
|
|
129
|
+
super().run_validators(self._get_string_value(value))
|
|
82
130
|
|
|
83
131
|
def get_prep_value(self, value):
|
|
84
132
|
if value is None:
|
|
85
133
|
return value
|
|
86
|
-
if isinstance(value,
|
|
87
|
-
return value.strftime(
|
|
88
|
-
if isinstance(value, python_date):
|
|
134
|
+
if isinstance(value, self.nepali_cls):
|
|
135
|
+
return value.strftime(self.format_str)
|
|
136
|
+
if isinstance(value, (python_date, python_datetime)):
|
|
89
137
|
try:
|
|
90
|
-
|
|
138
|
+
if isinstance(value, python_datetime) and timezone.is_aware(value):
|
|
139
|
+
value = timezone.localtime(value)
|
|
140
|
+
nepali_obj = (
|
|
141
|
+
self.nepali_cls.from_date(value)
|
|
142
|
+
if self.nepali_cls == nepalidate
|
|
143
|
+
else self.nepali_cls.from_datetime(value)
|
|
144
|
+
)
|
|
145
|
+
return nepali_obj.strftime(self.format_str)
|
|
91
146
|
except (ValueError, TypeError):
|
|
92
147
|
return str(value)
|
|
93
|
-
if isinstance(value, str):
|
|
94
|
-
return value
|
|
95
|
-
# Fallback: convert to string
|
|
96
148
|
return str(value)
|
|
97
149
|
|
|
98
150
|
def deconstruct(self):
|
|
@@ -104,166 +156,91 @@ class NepaliDateField(models.CharField):
|
|
|
104
156
|
return name, path, args, kwargs
|
|
105
157
|
|
|
106
158
|
def formfield(self, **kwargs):
|
|
107
|
-
from .forms import NepaliDateFormField
|
|
108
|
-
from .widgets import NepaliDatePickerWidget
|
|
109
|
-
|
|
110
159
|
defaults = {
|
|
111
|
-
"
|
|
112
|
-
"widget": NepaliDatePickerWidget,
|
|
160
|
+
"widget": NepaliDatePickerWidget(ne=self.ne, en=self.en),
|
|
113
161
|
}
|
|
114
162
|
defaults.update(kwargs)
|
|
115
163
|
return super().formfield(**defaults)
|
|
116
164
|
|
|
117
165
|
|
|
118
|
-
class
|
|
119
|
-
""
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
description = _("Nepali Time")
|
|
166
|
+
class NepaliDateField(BaseNepaliBSField):
|
|
167
|
+
description = _("Nepali Date (Bikram Sambat)")
|
|
168
|
+
default_max_length = 10
|
|
169
|
+
nepali_cls = nepalidate
|
|
170
|
+
format_str = BS_DATE_FORMAT
|
|
171
|
+
parse_func = staticmethod(try_parse_nepali_date)
|
|
125
172
|
|
|
173
|
+
def formfield(self, **kwargs):
|
|
174
|
+
# Import here to avoid circular dependency
|
|
175
|
+
from django_nepkit.forms import NepaliDateFormField
|
|
126
176
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
A Django Model Field that stores Nepali (Bikram Sambat) DateTime.
|
|
130
|
-
Internally it stores the datetime as BS string (YYYY-MM-DD HH:MM:SS) in the database.
|
|
131
|
-
"""
|
|
177
|
+
kwargs.setdefault("form_class", NepaliDateFormField)
|
|
178
|
+
return super().formfield(**kwargs)
|
|
132
179
|
|
|
133
|
-
description = _("Nepali DateTime (Bikram Sambat)")
|
|
134
180
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
self.auto_now_add = kwargs.pop("auto_now_add", False)
|
|
181
|
+
class NepaliTimeField(NepaliFieldMixin, models.TimeField):
|
|
182
|
+
description = _("Nepali Time")
|
|
138
183
|
|
|
139
|
-
# Match Django's DateTimeField behavior
|
|
140
|
-
if self.auto_now or self.auto_now_add:
|
|
141
|
-
kwargs.setdefault("editable", False)
|
|
142
|
-
kwargs.setdefault("blank", True)
|
|
143
184
|
|
|
144
|
-
|
|
145
|
-
|
|
185
|
+
class NepaliDateTimeField(BaseNepaliBSField):
|
|
186
|
+
description = _("Nepali DateTime (Bikram Sambat)")
|
|
187
|
+
default_max_length = 19
|
|
188
|
+
nepali_cls = nepalidatetime
|
|
189
|
+
format_str = BS_DATETIME_FORMAT
|
|
190
|
+
parse_func = staticmethod(try_parse_nepali_datetime)
|
|
146
191
|
|
|
147
|
-
def pre_save(self, model_instance, add):
|
|
148
|
-
if self.auto_now or (self.auto_now_add and add):
|
|
149
|
-
# Using nepalidatetime.now() to get current BS datetime
|
|
150
|
-
value = nepalidatetime.now().strftime(BS_DATETIME_FORMAT)
|
|
151
|
-
setattr(model_instance, self.attname, value)
|
|
152
|
-
return value
|
|
153
|
-
return super().pre_save(model_instance, add)
|
|
154
192
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
return parsed if parsed is not None else value
|
|
193
|
+
class BaseLocationField(NepaliFieldMixin, models.CharField):
|
|
194
|
+
"""Base class for Province, District, and Municipality fields."""
|
|
158
195
|
|
|
159
|
-
def
|
|
160
|
-
|
|
161
|
-
return value
|
|
162
|
-
if isinstance(value, str):
|
|
163
|
-
parsed = try_parse_nepali_datetime(value)
|
|
164
|
-
return parsed if parsed is not None else value
|
|
165
|
-
return super().to_python(value)
|
|
196
|
+
def __init__(self, *args, **kwargs):
|
|
197
|
+
kwargs.setdefault("max_length", 100)
|
|
166
198
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
value = value.strftime(BS_DATETIME_FORMAT)
|
|
170
|
-
super().validate(value, model_instance)
|
|
199
|
+
default_lang = nepkit_settings.DEFAULT_LANGUAGE
|
|
200
|
+
ne = kwargs.get("ne", default_lang == "ne")
|
|
171
201
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
super().run_validators(value)
|
|
202
|
+
# Load choices from the library (provinces, districts, etc.)
|
|
203
|
+
kwargs.setdefault("choices", self.get_choices_from_source(ne))
|
|
204
|
+
super().__init__(*args, **kwargs)
|
|
176
205
|
|
|
177
|
-
def
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
if isinstance(value, nepalidatetime):
|
|
181
|
-
return value.strftime(BS_DATETIME_FORMAT)
|
|
182
|
-
if isinstance(value, str):
|
|
183
|
-
return value
|
|
184
|
-
# Fallback: convert to string
|
|
185
|
-
return str(value)
|
|
206
|
+
def get_choices_from_source(self, ne):
|
|
207
|
+
source = getattr(self, "source", [])
|
|
208
|
+
return [(self._get_name(item, ne), self._get_name(item, ne)) for item in source]
|
|
186
209
|
|
|
187
|
-
def
|
|
188
|
-
|
|
189
|
-
if self.auto_now:
|
|
190
|
-
kwargs["auto_now"] = True
|
|
191
|
-
if self.auto_now_add:
|
|
192
|
-
kwargs["auto_now_add"] = True
|
|
193
|
-
return name, path, args, kwargs
|
|
210
|
+
def _get_name(self, item, ne):
|
|
211
|
+
return getattr(item, "name_nepali", item.name) if ne else item.name
|
|
194
212
|
|
|
195
213
|
def formfield(self, **kwargs):
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
return super().formfield(**defaults)
|
|
203
|
-
|
|
214
|
+
widget_cls = getattr(self, "widget_class", None)
|
|
215
|
+
if widget_cls:
|
|
216
|
+
defaults = {"widget": widget_cls(ne=self.ne, en=self.en, htmx=self.htmx)}
|
|
217
|
+
defaults.update(kwargs)
|
|
218
|
+
return super(models.CharField, self).formfield(**defaults)
|
|
219
|
+
return super().formfield(**kwargs)
|
|
204
220
|
|
|
205
|
-
class ProvinceField(models.CharField):
|
|
206
|
-
"""
|
|
207
|
-
A Django Model Field for Nepali Provinces.
|
|
208
|
-
"""
|
|
209
221
|
|
|
222
|
+
class ProvinceField(BaseLocationField):
|
|
210
223
|
description = _("Nepali Province")
|
|
224
|
+
source = provinces
|
|
225
|
+
widget_class = ProvinceSelectWidget
|
|
211
226
|
|
|
212
|
-
def __init__(self, *args, **kwargs):
|
|
213
|
-
from nepali.locations import provinces
|
|
214
|
-
|
|
215
|
-
kwargs.setdefault("max_length", 100)
|
|
216
|
-
kwargs.setdefault("choices", [(p.name, p.name) for p in provinces])
|
|
217
|
-
super().__init__(*args, **kwargs)
|
|
218
|
-
|
|
219
|
-
def formfield(self, **kwargs):
|
|
220
|
-
from .widgets import ProvinceSelectWidget
|
|
221
|
-
|
|
222
|
-
defaults = {"widget": ProvinceSelectWidget}
|
|
223
|
-
defaults.update(kwargs)
|
|
224
|
-
return super().formfield(**defaults)
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
class DistrictField(models.CharField):
|
|
228
|
-
"""
|
|
229
|
-
A Django Model Field for Nepali Districts.
|
|
230
|
-
"""
|
|
231
227
|
|
|
228
|
+
class DistrictField(BaseLocationField):
|
|
232
229
|
description = _("Nepali District")
|
|
230
|
+
source = districts
|
|
231
|
+
widget_class = DistrictSelectWidget
|
|
233
232
|
|
|
234
|
-
def __init__(self, *args, **kwargs):
|
|
235
|
-
from nepali.locations import districts
|
|
236
|
-
|
|
237
|
-
kwargs.setdefault("max_length", 100)
|
|
238
|
-
kwargs.setdefault("choices", [(d.name, d.name) for d in districts])
|
|
239
|
-
super().__init__(*args, **kwargs)
|
|
240
|
-
|
|
241
|
-
def formfield(self, **kwargs):
|
|
242
|
-
from .widgets import DistrictSelectWidget
|
|
243
|
-
|
|
244
|
-
defaults = {"widget": DistrictSelectWidget}
|
|
245
|
-
defaults.update(kwargs)
|
|
246
|
-
return super().formfield(**defaults)
|
|
247
233
|
|
|
234
|
+
class MunicipalityField(BaseLocationField):
|
|
235
|
+
description = _("Nepali Municipality")
|
|
236
|
+
source = municipalities
|
|
237
|
+
widget_class = MunicipalitySelectWidget
|
|
248
238
|
|
|
249
|
-
class MunicipalityField(models.CharField):
|
|
250
|
-
"""
|
|
251
|
-
A Django Model Field for Nepali Municipalities.
|
|
252
|
-
Includes Metropolitan, Sub-Metropolitan, Municipality, and Rural Municipality.
|
|
253
|
-
"""
|
|
254
239
|
|
|
255
|
-
|
|
240
|
+
class NepaliCurrencyField(models.DecimalField):
|
|
241
|
+
description = _("Nepali Currency")
|
|
256
242
|
|
|
257
243
|
def __init__(self, *args, **kwargs):
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
kwargs.setdefault("max_length", 100)
|
|
261
|
-
kwargs.setdefault("choices", [(m.name, m.name) for m in municipalities])
|
|
244
|
+
kwargs.setdefault("max_digits", 19)
|
|
245
|
+
kwargs.setdefault("decimal_places", 2)
|
|
262
246
|
super().__init__(*args, **kwargs)
|
|
263
|
-
|
|
264
|
-
def formfield(self, **kwargs):
|
|
265
|
-
from .widgets import MunicipalitySelectWidget
|
|
266
|
-
|
|
267
|
-
defaults = {"widget": MunicipalitySelectWidget}
|
|
268
|
-
defaults.update(kwargs)
|
|
269
|
-
return super().formfield(**defaults)
|