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.
Files changed (37) hide show
  1. django_nepkit/__init__.py +20 -0
  2. django_nepkit/admin.py +243 -93
  3. django_nepkit/conf.py +34 -0
  4. django_nepkit/filters.py +113 -0
  5. django_nepkit/forms.py +9 -9
  6. django_nepkit/models.py +143 -162
  7. django_nepkit/serializers.py +127 -28
  8. django_nepkit/static/django_nepkit/js/address-chaining.js +105 -18
  9. django_nepkit/static/django_nepkit/js/nepal-data.js +1 -0
  10. django_nepkit/static/django_nepkit/js/nepali-datepicker-init.js +55 -46
  11. django_nepkit/utils.py +396 -46
  12. django_nepkit/validators.py +1 -1
  13. django_nepkit/views.py +66 -3
  14. django_nepkit/widgets.py +100 -31
  15. django_nepkit-0.2.0.dist-info/METADATA +308 -0
  16. django_nepkit-0.2.0.dist-info/RECORD +35 -0
  17. example/demo/admin.py +45 -21
  18. example/demo/models.py +41 -4
  19. example/demo/serializers.py +39 -0
  20. example/demo/urls.py +13 -2
  21. example/demo/views.py +125 -3
  22. example/example_project/settings.py +10 -0
  23. example/example_project/urls.py +1 -1
  24. example/manage.py +2 -2
  25. django_nepkit/templatetags/__init__.py +0 -0
  26. django_nepkit/templatetags/nepali.py +0 -74
  27. django_nepkit-0.1.0.dist-info/METADATA +0 -377
  28. django_nepkit-0.1.0.dist-info/RECORD +0 -39
  29. example/demo/migrations/0001_initial.py +0 -2113
  30. example/demo/migrations/0002_alter_person_phone_number.py +0 -18
  31. example/demo/migrations/0003_person_created_at_person_updated_at.py +0 -27
  32. example/demo/migrations/0004_alter_person_created_at_alter_person_updated_at.py +0 -23
  33. example/demo/migrations/0005_alter_person_created_at_alter_person_updated_at.py +0 -27
  34. example/demo/migrations/__init__.py +0 -0
  35. {django_nepkit-0.1.0.dist-info → django_nepkit-0.2.0.dist-info}/WHEEL +0 -0
  36. {django_nepkit-0.1.0.dist-info → django_nepkit-0.2.0.dist-info}/licenses/LICENSE +0 -0
  37. {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 NepaliDateField(models.CharField):
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", 10)
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
- # Using nepalidate.today() to get current BS date.
48
- # This is BS-native as much as possible.
49
- value = nepalidate.today().strftime(BS_DATE_FORMAT)
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 = try_parse_nepali_date(value)
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, nepalidate):
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
- return nepalidate.from_date(value)
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 = try_parse_nepali_date(value)
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
- if isinstance(value, nepalidate):
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
- if isinstance(value, nepalidate):
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, nepalidate):
87
- return value.strftime(BS_DATE_FORMAT)
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
- return nepalidate.from_date(value).strftime(BS_DATE_FORMAT)
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
- "form_class": NepaliDateFormField,
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 NepaliTimeField(models.TimeField):
119
- """
120
- A Django Model Field for Time with Nepali localization support.
121
- Supports auto_now and auto_now_add like standard Django TimeField.
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
- description = _("Nepali DateTime (Bikram Sambat)")
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
- # 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)
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
- 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
-
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 to_python(self, value):
160
- if value is None or isinstance(value, nepalidatetime):
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
- def validate(self, value, model_instance):
168
- if isinstance(value, nepalidatetime):
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
- def run_validators(self, value):
173
- if isinstance(value, nepalidatetime):
174
- value = value.strftime(BS_DATETIME_FORMAT)
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 get_prep_value(self, value):
178
- if value is None:
179
- return value
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 deconstruct(self):
188
- name, path, args, kwargs = super().deconstruct()
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
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
- from .widgets import NepaliDatePickerWidget
197
-
198
- defaults = {
199
- "widget": NepaliDatePickerWidget,
200
- }
201
- defaults.update(kwargs)
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
- description = _("Nepali Municipality")
244
+ class NepaliCurrencyField(models.DecimalField):
245
+ description = _("Nepali Currency")
256
246
 
257
247
  def __init__(self, *args, **kwargs):
258
- from nepali.locations import municipalities
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)
@@ -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.utils import try_parse_nepali_date, try_parse_nepali_datetime
16
-
17
- # --------------------------------------------------
18
- # Base Serializer Field
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
- Base DRF field for Nepali (BS) Date / DateTime.
25
-
26
- - **Input**: BS string, or an already-parsed `nepalidate`/`nepalidatetime`
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__(self, *, format: Optional[str] = None, **kwargs: Any) -> None:
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 value.strftime(self.format) # type: ignore[attr-defined]
90
+ return self._format_value(value)
61
91
 
62
- # If DB returns string, try to normalize it.
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 parsed.strftime(self.format) # type: ignore[attr-defined]
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
- # Nepali Date (BS)
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
- class NepaliDateSerializerField(BaseNepaliBSField):
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
- DRF field for Nepali BS Date (YYYY-MM-DD)
133
+ API field for Nepali Currency.
134
+ Formats decimal values with Nepali-style commas.
96
135
  """
97
136
 
98
- format = "%Y-%m-%d"
99
- nepali_type = nepalidate
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
- # Nepali DateTime (BS)
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
- class NepaliDateTimeSerializerField(BaseNepaliBSField):
159
+
160
+ class NepaliLocalizedSerializerMixin:
108
161
  """
109
- DRF field for Nepali BS DateTime (YYYY-MM-DD HH:MM:SS)
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
- format = "%Y-%m-%d %H:%M:%S"
113
- nepali_type = nepalidatetime
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