django-nepkit 0.1.0__py3-none-any.whl → 0.2.0__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/filters.py +113 -0
- django_nepkit/forms.py +9 -9
- django_nepkit/models.py +143 -162
- django_nepkit/serializers.py +127 -28
- django_nepkit/static/django_nepkit/js/address-chaining.js +105 -18
- 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 +396 -46
- django_nepkit/validators.py +1 -1
- django_nepkit/views.py +66 -3
- django_nepkit/widgets.py +100 -31
- django_nepkit-0.2.0.dist-info/METADATA +308 -0
- django_nepkit-0.2.0.dist-info/RECORD +35 -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.0.dist-info}/WHEEL +0 -0
- {django_nepkit-0.1.0.dist-info → django_nepkit-0.2.0.dist-info}/licenses/LICENSE +0 -0
- {django_nepkit-0.1.0.dist-info → django_nepkit-0.2.0.dist-info}/top_level.txt +0 -0
django_nepkit/models.py
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
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
|
|
|
10
|
+
from django_nepkit.forms import NepaliDateFormField
|
|
7
11
|
from django_nepkit.utils import (
|
|
8
12
|
BS_DATE_FORMAT,
|
|
9
13
|
BS_DATETIME_FORMAT,
|
|
@@ -11,6 +15,43 @@ from django_nepkit.utils import (
|
|
|
11
15
|
try_parse_nepali_datetime,
|
|
12
16
|
)
|
|
13
17
|
from django_nepkit.validators import validate_nepali_phone_number
|
|
18
|
+
from django_nepkit.widgets import (
|
|
19
|
+
DistrictSelectWidget,
|
|
20
|
+
MunicipalitySelectWidget,
|
|
21
|
+
NepaliDatePickerWidget,
|
|
22
|
+
ProvinceSelectWidget,
|
|
23
|
+
)
|
|
24
|
+
from django_nepkit.conf import nepkit_settings
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class NepaliFieldMixin:
|
|
28
|
+
"""Adds Nepali 'ne' and 'en' support to any field."""
|
|
29
|
+
|
|
30
|
+
def __init__(self, *args, **kwargs):
|
|
31
|
+
default_lang = nepkit_settings.DEFAULT_LANGUAGE
|
|
32
|
+
self.ne = kwargs.pop("ne", default_lang == "ne")
|
|
33
|
+
|
|
34
|
+
explicit_en = "en" in kwargs
|
|
35
|
+
en_value = kwargs.pop("en", not self.ne)
|
|
36
|
+
|
|
37
|
+
if self.ne and not explicit_en:
|
|
38
|
+
self.en = False
|
|
39
|
+
else:
|
|
40
|
+
self.en = en_value
|
|
41
|
+
|
|
42
|
+
self.htmx = kwargs.pop("htmx", False)
|
|
43
|
+
|
|
44
|
+
super().__init__(*args, **kwargs)
|
|
45
|
+
|
|
46
|
+
def deconstruct(self):
|
|
47
|
+
name, path, args, kwargs = super().deconstruct()
|
|
48
|
+
if self.ne:
|
|
49
|
+
kwargs["ne"] = True
|
|
50
|
+
if not self.en:
|
|
51
|
+
kwargs["en"] = False
|
|
52
|
+
if self.htmx:
|
|
53
|
+
kwargs["htmx"] = True
|
|
54
|
+
return name, path, args, kwargs
|
|
14
55
|
|
|
15
56
|
|
|
16
57
|
class NepaliPhoneNumberField(models.CharField):
|
|
@@ -22,77 +63,89 @@ class NepaliPhoneNumberField(models.CharField):
|
|
|
22
63
|
self.validators.append(validate_nepali_phone_number)
|
|
23
64
|
|
|
24
65
|
|
|
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)")
|
|
66
|
+
class BaseNepaliBSField(NepaliFieldMixin, models.CharField):
|
|
67
|
+
"""Base class for Nepali date and datetime fields."""
|
|
32
68
|
|
|
33
69
|
def __init__(self, *args, **kwargs):
|
|
34
70
|
self.auto_now = kwargs.pop("auto_now", False)
|
|
35
71
|
self.auto_now_add = kwargs.pop("auto_now_add", False)
|
|
36
72
|
|
|
37
|
-
# Match Django's DateField behavior
|
|
38
73
|
if self.auto_now or self.auto_now_add:
|
|
39
74
|
kwargs.setdefault("editable", False)
|
|
40
75
|
kwargs.setdefault("blank", True)
|
|
41
76
|
|
|
42
|
-
kwargs.setdefault("max_length",
|
|
77
|
+
kwargs.setdefault("max_length", getattr(self, "default_max_length", 20))
|
|
43
78
|
super().__init__(*args, **kwargs)
|
|
44
79
|
|
|
45
80
|
def pre_save(self, model_instance, add):
|
|
46
81
|
if self.auto_now or (self.auto_now_add and add):
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
82
|
+
now = timezone.now()
|
|
83
|
+
if timezone.is_aware(now):
|
|
84
|
+
now = timezone.localtime(now)
|
|
85
|
+
|
|
86
|
+
nepali_cls = getattr(self, "nepali_cls", nepalidate)
|
|
87
|
+
format_str = getattr(self, "format_str", BS_DATE_FORMAT)
|
|
88
|
+
|
|
89
|
+
value = (
|
|
90
|
+
nepali_cls.from_date(now).strftime(format_str)
|
|
91
|
+
if nepali_cls == nepalidate
|
|
92
|
+
else nepali_cls.from_datetime(now).strftime(format_str)
|
|
93
|
+
)
|
|
50
94
|
setattr(model_instance, self.attname, value)
|
|
51
95
|
return value
|
|
52
96
|
return super().pre_save(model_instance, add)
|
|
53
97
|
|
|
54
98
|
def from_db_value(self, value, expression, connection):
|
|
55
|
-
parsed =
|
|
99
|
+
parsed = self.parse_func(value)
|
|
56
100
|
return parsed if parsed is not None else value
|
|
57
101
|
|
|
58
102
|
def to_python(self, value):
|
|
59
|
-
if value is None or isinstance(value,
|
|
103
|
+
if value is None or isinstance(value, self.nepali_cls):
|
|
60
104
|
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.
|
|
105
|
+
if isinstance(value, (python_date, python_datetime)):
|
|
64
106
|
try:
|
|
65
|
-
|
|
107
|
+
if isinstance(value, python_datetime) and timezone.is_aware(value):
|
|
108
|
+
value = timezone.localtime(value)
|
|
109
|
+
return (
|
|
110
|
+
self.nepali_cls.from_date(value)
|
|
111
|
+
if self.nepali_cls == nepalidate
|
|
112
|
+
else self.nepali_cls.from_datetime(value)
|
|
113
|
+
)
|
|
66
114
|
except (ValueError, TypeError):
|
|
67
115
|
return str(value)
|
|
68
116
|
if isinstance(value, str):
|
|
69
|
-
parsed =
|
|
117
|
+
parsed = self.parse_func(value)
|
|
70
118
|
return parsed if parsed is not None else value
|
|
71
119
|
return super().to_python(value)
|
|
72
120
|
|
|
121
|
+
def _get_string_value(self, value):
|
|
122
|
+
if isinstance(value, (nepalidate, nepalidatetime)):
|
|
123
|
+
return value.strftime(self.format_str)
|
|
124
|
+
return value
|
|
125
|
+
|
|
73
126
|
def validate(self, value, model_instance):
|
|
74
|
-
|
|
75
|
-
value = value.strftime(BS_DATE_FORMAT)
|
|
76
|
-
super().validate(value, model_instance)
|
|
127
|
+
super().validate(self._get_string_value(value), model_instance)
|
|
77
128
|
|
|
78
129
|
def run_validators(self, value):
|
|
79
|
-
|
|
80
|
-
value = value.strftime(BS_DATE_FORMAT)
|
|
81
|
-
super().run_validators(value)
|
|
130
|
+
super().run_validators(self._get_string_value(value))
|
|
82
131
|
|
|
83
132
|
def get_prep_value(self, value):
|
|
84
133
|
if value is None:
|
|
85
134
|
return value
|
|
86
|
-
if isinstance(value,
|
|
87
|
-
return value.strftime(
|
|
88
|
-
if isinstance(value, python_date):
|
|
135
|
+
if isinstance(value, self.nepali_cls):
|
|
136
|
+
return value.strftime(self.format_str)
|
|
137
|
+
if isinstance(value, (python_date, python_datetime)):
|
|
89
138
|
try:
|
|
90
|
-
|
|
139
|
+
if isinstance(value, python_datetime) and timezone.is_aware(value):
|
|
140
|
+
value = timezone.localtime(value)
|
|
141
|
+
nepali_obj = (
|
|
142
|
+
self.nepali_cls.from_date(value)
|
|
143
|
+
if self.nepali_cls == nepalidate
|
|
144
|
+
else self.nepali_cls.from_datetime(value)
|
|
145
|
+
)
|
|
146
|
+
return nepali_obj.strftime(self.format_str)
|
|
91
147
|
except (ValueError, TypeError):
|
|
92
148
|
return str(value)
|
|
93
|
-
if isinstance(value, str):
|
|
94
|
-
return value
|
|
95
|
-
# Fallback: convert to string
|
|
96
149
|
return str(value)
|
|
97
150
|
|
|
98
151
|
def deconstruct(self):
|
|
@@ -104,166 +157,94 @@ class NepaliDateField(models.CharField):
|
|
|
104
157
|
return name, path, args, kwargs
|
|
105
158
|
|
|
106
159
|
def formfield(self, **kwargs):
|
|
107
|
-
from .forms import NepaliDateFormField
|
|
108
|
-
from .widgets import NepaliDatePickerWidget
|
|
109
|
-
|
|
110
160
|
defaults = {
|
|
111
|
-
"
|
|
112
|
-
"widget": NepaliDatePickerWidget,
|
|
161
|
+
"widget": NepaliDatePickerWidget(ne=self.ne, en=self.en),
|
|
113
162
|
}
|
|
114
163
|
defaults.update(kwargs)
|
|
115
164
|
return super().formfield(**defaults)
|
|
116
165
|
|
|
117
166
|
|
|
118
|
-
class
|
|
119
|
-
""
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
description = _("Nepali Time")
|
|
167
|
+
class NepaliDateField(BaseNepaliBSField):
|
|
168
|
+
description = _("Nepali Date (Bikram Sambat)")
|
|
169
|
+
default_max_length = 10
|
|
170
|
+
nepali_cls = nepalidate
|
|
171
|
+
format_str = BS_DATE_FORMAT
|
|
172
|
+
parse_func = staticmethod(try_parse_nepali_date)
|
|
125
173
|
|
|
174
|
+
def formfield(self, **kwargs):
|
|
175
|
+
kwargs.setdefault("form_class", NepaliDateFormField)
|
|
176
|
+
return super().formfield(**kwargs)
|
|
126
177
|
|
|
127
|
-
class NepaliDateTimeField(models.CharField):
|
|
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
|
-
"""
|
|
132
178
|
|
|
133
|
-
|
|
179
|
+
class NepaliTimeField(NepaliFieldMixin, models.TimeField):
|
|
180
|
+
description = _("Nepali Time")
|
|
134
181
|
|
|
135
|
-
def __init__(self, *args, **kwargs):
|
|
136
|
-
self.auto_now = kwargs.pop("auto_now", False)
|
|
137
|
-
self.auto_now_add = kwargs.pop("auto_now_add", False)
|
|
138
182
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
183
|
+
class NepaliDateTimeField(BaseNepaliBSField):
|
|
184
|
+
description = _("Nepali DateTime (Bikram Sambat)")
|
|
185
|
+
default_max_length = 19
|
|
186
|
+
nepali_cls = nepalidatetime
|
|
187
|
+
format_str = BS_DATETIME_FORMAT
|
|
188
|
+
parse_func = staticmethod(try_parse_nepali_datetime)
|
|
143
189
|
|
|
144
|
-
kwargs.setdefault("max_length", 19) # YYYY-MM-DD HH:MM:SS
|
|
145
|
-
super().__init__(*args, **kwargs)
|
|
146
190
|
|
|
147
|
-
|
|
148
|
-
|
|
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
|
-
|
|
155
|
-
def from_db_value(self, value, expression, connection):
|
|
156
|
-
parsed = try_parse_nepali_datetime(value)
|
|
157
|
-
return parsed if parsed is not None else value
|
|
191
|
+
class BaseLocationField(NepaliFieldMixin, models.CharField):
|
|
192
|
+
"""Base class for Province, District, and Municipality fields."""
|
|
158
193
|
|
|
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)
|
|
194
|
+
def __init__(self, *args, **kwargs):
|
|
195
|
+
kwargs.setdefault("max_length", 100)
|
|
166
196
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
value = value.strftime(BS_DATETIME_FORMAT)
|
|
170
|
-
super().validate(value, model_instance)
|
|
197
|
+
default_lang = nepkit_settings.DEFAULT_LANGUAGE
|
|
198
|
+
ne = kwargs.get("ne", default_lang == "ne")
|
|
171
199
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
super().run_validators(value)
|
|
200
|
+
# Load choices from the library (provinces, districts, etc.)
|
|
201
|
+
kwargs.setdefault("choices", self.get_choices_from_source(ne))
|
|
202
|
+
super().__init__(*args, **kwargs)
|
|
176
203
|
|
|
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)
|
|
204
|
+
def get_choices_from_source(self, ne):
|
|
205
|
+
source = getattr(self, "source", [])
|
|
206
|
+
return [(self._get_name(item, ne), self._get_name(item, ne)) for item in source]
|
|
186
207
|
|
|
187
|
-
def
|
|
188
|
-
name
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
208
|
+
def _get_name(self, item, ne):
|
|
209
|
+
name = getattr(item, "name_nepali", item.name) if ne else item.name
|
|
210
|
+
# Handle name variations and Koshi Province mapping
|
|
211
|
+
if name == "Province 1":
|
|
212
|
+
return "Koshi Province"
|
|
213
|
+
if name == "प्रदेश नं. १":
|
|
214
|
+
return "कोशी प्रदेश"
|
|
215
|
+
return name
|
|
194
216
|
|
|
195
217
|
def formfield(self, **kwargs):
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
return super().formfield(**defaults)
|
|
203
|
-
|
|
218
|
+
widget_cls = getattr(self, "widget_class", None)
|
|
219
|
+
if widget_cls:
|
|
220
|
+
defaults = {"widget": widget_cls(ne=self.ne, en=self.en, htmx=self.htmx)}
|
|
221
|
+
defaults.update(kwargs)
|
|
222
|
+
return super(models.CharField, self).formfield(**defaults)
|
|
223
|
+
return super().formfield(**kwargs)
|
|
204
224
|
|
|
205
|
-
class ProvinceField(models.CharField):
|
|
206
|
-
"""
|
|
207
|
-
A Django Model Field for Nepali Provinces.
|
|
208
|
-
"""
|
|
209
225
|
|
|
226
|
+
class ProvinceField(BaseLocationField):
|
|
210
227
|
description = _("Nepali Province")
|
|
228
|
+
source = provinces
|
|
229
|
+
widget_class = ProvinceSelectWidget
|
|
211
230
|
|
|
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
231
|
|
|
232
|
+
class DistrictField(BaseLocationField):
|
|
232
233
|
description = _("Nepali District")
|
|
234
|
+
source = districts
|
|
235
|
+
widget_class = DistrictSelectWidget
|
|
233
236
|
|
|
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
237
|
|
|
238
|
+
class MunicipalityField(BaseLocationField):
|
|
239
|
+
description = _("Nepali Municipality")
|
|
240
|
+
source = municipalities
|
|
241
|
+
widget_class = MunicipalitySelectWidget
|
|
248
242
|
|
|
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
243
|
|
|
255
|
-
|
|
244
|
+
class NepaliCurrencyField(models.DecimalField):
|
|
245
|
+
description = _("Nepali Currency")
|
|
256
246
|
|
|
257
247
|
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])
|
|
248
|
+
kwargs.setdefault("max_digits", 19)
|
|
249
|
+
kwargs.setdefault("decimal_places", 2)
|
|
262
250
|
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)
|
django_nepkit/serializers.py
CHANGED
|
@@ -12,19 +12,21 @@ except ModuleNotFoundError as e:
|
|
|
12
12
|
"to use `django_nepkit.serializers`."
|
|
13
13
|
) from e
|
|
14
14
|
|
|
15
|
-
from django_nepkit.
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
from django_nepkit.conf import nepkit_settings
|
|
16
|
+
from django_nepkit.utils import (
|
|
17
|
+
BS_DATE_FORMAT,
|
|
18
|
+
BS_DATETIME_FORMAT,
|
|
19
|
+
format_nepali_currency,
|
|
20
|
+
try_parse_nepali_date,
|
|
21
|
+
try_parse_nepali_datetime,
|
|
22
|
+
)
|
|
20
23
|
|
|
21
24
|
|
|
22
25
|
class BaseNepaliBSField(serializers.Field):
|
|
23
26
|
"""
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
-
|
|
27
|
-
- **Output**: formatted BS string (configurable)
|
|
27
|
+
Handles Nepali (BS) dates.
|
|
28
|
+
- Input: A date string (like '2080-01-01') or a date object.
|
|
29
|
+
- Output: A formatted string for your API.
|
|
28
30
|
"""
|
|
29
31
|
|
|
30
32
|
format: str = ""
|
|
@@ -35,14 +37,36 @@ class BaseNepaliBSField(serializers.Field):
|
|
|
35
37
|
"invalid_type": "Invalid type. Expected a string.",
|
|
36
38
|
}
|
|
37
39
|
|
|
38
|
-
def __init__(
|
|
40
|
+
def __init__(
|
|
41
|
+
self,
|
|
42
|
+
*,
|
|
43
|
+
format: Optional[str] = None,
|
|
44
|
+
ne: Optional[bool] = None,
|
|
45
|
+
en: Optional[bool] = None,
|
|
46
|
+
**kwargs: Any,
|
|
47
|
+
) -> None:
|
|
39
48
|
"""
|
|
40
49
|
Args:
|
|
41
50
|
format: Optional `strftime` format used for representation.
|
|
42
51
|
If not provided, uses the class default.
|
|
52
|
+
ne: If True, output in Devanagari script. If None, uses DEFAULT_LANGUAGE.
|
|
53
|
+
en: If True, output in English. If None, derived from ne.
|
|
43
54
|
"""
|
|
44
55
|
if format is not None:
|
|
45
56
|
self.format = format
|
|
57
|
+
|
|
58
|
+
default_lang = nepkit_settings.DEFAULT_LANGUAGE
|
|
59
|
+
|
|
60
|
+
if ne is None:
|
|
61
|
+
self.ne = default_lang == "ne"
|
|
62
|
+
else:
|
|
63
|
+
self.ne = ne
|
|
64
|
+
|
|
65
|
+
if en is None:
|
|
66
|
+
self.en = not self.ne
|
|
67
|
+
else:
|
|
68
|
+
self.en = en
|
|
69
|
+
|
|
46
70
|
super().__init__(**kwargs)
|
|
47
71
|
|
|
48
72
|
def _parse(self, value: str):
|
|
@@ -52,20 +76,25 @@ class BaseNepaliBSField(serializers.Field):
|
|
|
52
76
|
return try_parse_nepali_datetime(value)
|
|
53
77
|
return None
|
|
54
78
|
|
|
79
|
+
def _format_value(self, value: Any) -> str:
|
|
80
|
+
"""Format value using strftime or strftime_ne (Devanagari) based on self.ne."""
|
|
81
|
+
if self.ne and hasattr(value, "strftime_ne"):
|
|
82
|
+
return value.strftime_ne(self.format) # type: ignore[attr-defined]
|
|
83
|
+
return value.strftime(self.format) # type: ignore[attr-defined]
|
|
84
|
+
|
|
55
85
|
def to_representation(self, value: Any) -> Optional[str]:
|
|
56
86
|
if value is None:
|
|
57
87
|
return None
|
|
58
88
|
|
|
59
89
|
if isinstance(value, self.nepali_type):
|
|
60
|
-
return
|
|
90
|
+
return self._format_value(value)
|
|
61
91
|
|
|
62
|
-
#
|
|
92
|
+
# Convert string dates to Nepali date objects
|
|
63
93
|
if isinstance(value, str):
|
|
64
94
|
parsed = self._parse(value)
|
|
65
95
|
if parsed is not None:
|
|
66
|
-
return
|
|
96
|
+
return self._format_value(parsed)
|
|
67
97
|
|
|
68
|
-
# Fallback: best-effort stringify (keeps behavior non-breaking)
|
|
69
98
|
return str(value)
|
|
70
99
|
|
|
71
100
|
def to_internal_value(self, data: Any):
|
|
@@ -85,29 +114,99 @@ class BaseNepaliBSField(serializers.Field):
|
|
|
85
114
|
self.fail("invalid", format=self.format)
|
|
86
115
|
|
|
87
116
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
# --------------------------------------------------
|
|
117
|
+
class NepaliDateSerializerField(BaseNepaliBSField):
|
|
118
|
+
"""API field for Nepali Dates."""
|
|
91
119
|
|
|
120
|
+
format = BS_DATE_FORMAT
|
|
121
|
+
nepali_type = nepalidate
|
|
92
122
|
|
|
93
|
-
|
|
123
|
+
|
|
124
|
+
class NepaliDateTimeSerializerField(BaseNepaliBSField):
|
|
125
|
+
"""API field for Nepali Date and Time."""
|
|
126
|
+
|
|
127
|
+
format = BS_DATETIME_FORMAT
|
|
128
|
+
nepali_type = nepalidatetime
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class NepaliCurrencySerializerField(serializers.Field):
|
|
94
132
|
"""
|
|
95
|
-
|
|
133
|
+
API field for Nepali Currency.
|
|
134
|
+
Formats decimal values with Nepali-style commas.
|
|
96
135
|
"""
|
|
97
136
|
|
|
98
|
-
|
|
99
|
-
|
|
137
|
+
def __init__(self, currency_symbol="Rs.", ne=None, **kwargs):
|
|
138
|
+
self.currency_symbol = currency_symbol
|
|
139
|
+
self.ne = ne
|
|
140
|
+
super().__init__(**kwargs)
|
|
141
|
+
|
|
142
|
+
def to_representation(self, value):
|
|
143
|
+
if value is None:
|
|
144
|
+
return None
|
|
100
145
|
|
|
146
|
+
ne = self.ne
|
|
147
|
+
if ne is None:
|
|
148
|
+
ne = self.context.get("ne", nepkit_settings.DEFAULT_LANGUAGE == "ne")
|
|
101
149
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
150
|
+
return format_nepali_currency(
|
|
151
|
+
value, currency_symbol=self.currency_symbol, ne=ne
|
|
152
|
+
)
|
|
105
153
|
|
|
154
|
+
def to_internal_value(self, data):
|
|
155
|
+
# We don't support converting back from formatted string to decimal here
|
|
156
|
+
# Users should send raw decimal/float values for input.
|
|
157
|
+
return data
|
|
106
158
|
|
|
107
|
-
|
|
159
|
+
|
|
160
|
+
class NepaliLocalizedSerializerMixin:
|
|
108
161
|
"""
|
|
109
|
-
|
|
162
|
+
A mixin for ModelSerializer that automatically adds localized counterparts
|
|
163
|
+
for eligible fields if `ne=True` is passed in the context.
|
|
164
|
+
Eligible fields: NepaliDateField, NepaliDateTimeField, NepaliCurrencyField.
|
|
165
|
+
|
|
166
|
+
Usage:
|
|
167
|
+
class MySerializer(NepaliLocalizedSerializerMixin, serializers.ModelSerializer):
|
|
168
|
+
...
|
|
110
169
|
"""
|
|
111
170
|
|
|
112
|
-
|
|
113
|
-
|
|
171
|
+
def to_representation(self, instance):
|
|
172
|
+
ret = super().to_representation(instance) # type: ignore[misc]
|
|
173
|
+
ne = self.context.get("ne", False)
|
|
174
|
+
if not ne:
|
|
175
|
+
return ret
|
|
176
|
+
|
|
177
|
+
from django_nepkit.models import (
|
|
178
|
+
NepaliCurrencyField,
|
|
179
|
+
NepaliDateField,
|
|
180
|
+
NepaliDateTimeField,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
model = getattr(self.Meta, "model", None) # type: ignore[attr-defined]
|
|
184
|
+
if not model:
|
|
185
|
+
return ret
|
|
186
|
+
|
|
187
|
+
for field_name, value in list(ret.items()):
|
|
188
|
+
try:
|
|
189
|
+
model_field = model._meta.get_field(field_name)
|
|
190
|
+
localized_name = f"{field_name}_ne"
|
|
191
|
+
|
|
192
|
+
if localized_name in ret:
|
|
193
|
+
continue
|
|
194
|
+
|
|
195
|
+
if isinstance(model_field, NepaliDateField):
|
|
196
|
+
raw_val = getattr(instance, field_name)
|
|
197
|
+
if hasattr(raw_val, "strftime_ne"):
|
|
198
|
+
ret[localized_name] = raw_val.strftime_ne(BS_DATE_FORMAT)
|
|
199
|
+
|
|
200
|
+
elif isinstance(model_field, NepaliDateTimeField):
|
|
201
|
+
raw_val = getattr(instance, field_name)
|
|
202
|
+
if hasattr(raw_val, "strftime_ne"):
|
|
203
|
+
ret[localized_name] = raw_val.strftime_ne(BS_DATETIME_FORMAT)
|
|
204
|
+
|
|
205
|
+
elif isinstance(model_field, NepaliCurrencyField):
|
|
206
|
+
ret[localized_name] = format_nepali_currency(
|
|
207
|
+
value, currency_symbol="", ne=True
|
|
208
|
+
)
|
|
209
|
+
except Exception:
|
|
210
|
+
continue
|
|
211
|
+
|
|
212
|
+
return ret
|