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/__init__.py CHANGED
@@ -6,14 +6,26 @@ from .models import (
6
6
  ProvinceField,
7
7
  DistrictField,
8
8
  MunicipalityField,
9
+ NepaliCurrencyField,
9
10
  )
10
11
  from .admin import (
11
12
  NepaliDateFilter,
13
+ NepaliMonthFilter,
12
14
  format_nepali_date,
13
15
  format_nepali_datetime,
14
16
  NepaliModelAdmin,
15
17
  NepaliAdminMixin,
16
18
  )
19
+ from .serializers import (
20
+ NepaliCurrencySerializerField,
21
+ NepaliLocalizedSerializerMixin,
22
+ )
23
+ from .filters import (
24
+ NepaliDateYearFilter,
25
+ NepaliDateMonthFilter,
26
+ NepaliDateRangeFilter,
27
+ NepaliCurrencyRangeFilter,
28
+ )
17
29
 
18
30
  __all__ = [
19
31
  "NepaliDateField",
@@ -24,8 +36,16 @@ __all__ = [
24
36
  "DistrictField",
25
37
  "MunicipalityField",
26
38
  "NepaliDateFilter",
39
+ "NepaliMonthFilter",
27
40
  "format_nepali_date",
28
41
  "format_nepali_datetime",
29
42
  "NepaliModelAdmin",
30
43
  "NepaliAdminMixin",
44
+ "NepaliCurrencyField",
45
+ "NepaliCurrencySerializerField",
46
+ "NepaliLocalizedSerializerMixin",
47
+ "NepaliDateYearFilter",
48
+ "NepaliDateMonthFilter",
49
+ "NepaliDateRangeFilter",
50
+ "NepaliCurrencyRangeFilter",
31
51
  ]
django_nepkit/admin.py CHANGED
@@ -2,77 +2,71 @@ from django.contrib import admin
2
2
  from django.utils.translation import gettext_lazy as _
3
3
  from nepali.datetime import nepalidate, nepalidatetime
4
4
 
5
- from django_nepkit.models import NepaliDateField
5
+ from django_nepkit.conf import nepkit_settings
6
+ from django_nepkit.models import (
7
+ NepaliDateField,
8
+ NepaliDateTimeField,
9
+ NepaliCurrencyField,
10
+ )
6
11
  from django_nepkit.utils import (
7
12
  try_parse_nepali_date,
8
13
  try_parse_nepali_datetime,
14
+ format_nepali_currency,
9
15
  )
16
+ from django_nepkit.utils import BS_DATE_FORMAT
10
17
 
11
18
 
12
- def format_nepali_date(date_value, format_string="%B %d, %Y"):
13
- """
14
- Format a nepalidate object with Nepali month names.
15
-
16
- Args:
17
- date_value: A nepalidate object or string in YYYY-MM-DD format
18
- format_string: strftime format string (default: '%B %d, %Y')
19
- %B = Full month name (Baishak, Jestha, etc.)
20
- %b = Short month name
21
- %d = Day of month
22
- %Y = Year
23
-
24
- Returns:
25
- Formatted date string with Nepali month names, or empty string if invalid
26
- """
27
- if date_value is None:
19
+ def _format_nepali_common(value, try_parse_func, format_string, ne, cls_type):
20
+ """Helper to format dates/times with optional Devanagari support."""
21
+ if value is None:
28
22
  return ""
29
23
 
30
24
  try:
31
- parsed = try_parse_nepali_date(date_value)
25
+ parsed = try_parse_func(value)
32
26
  if parsed is not None:
27
+ if ne and hasattr(parsed, "strftime_ne"):
28
+ return parsed.strftime_ne(format_string)
33
29
  return parsed.strftime(format_string)
34
- if isinstance(date_value, nepalidate):
35
- return date_value.strftime(
36
- format_string
37
- ) # defensive; should be covered above
30
+ if isinstance(value, cls_type):
31
+ if ne and hasattr(value, "strftime_ne"):
32
+ return value.strftime_ne(format_string)
33
+ return value.strftime(format_string)
38
34
  except (ValueError, TypeError, AttributeError):
39
35
  pass
40
36
 
41
- return str(date_value) if date_value else ""
37
+ return str(value) if value else ""
42
38
 
43
39
 
44
- def format_nepali_datetime(datetime_value, format_string="%B %d, %Y %I:%M %p"):
40
+ def format_nepali_date(date_value, format_string="%B %d, %Y", ne=False):
45
41
  """
46
- Format a nepalidatetime object with Nepali month names.
47
-
48
- Default output uses **12-hour time with AM/PM**.
42
+ Format a nepalidate object with Nepali month names.
49
43
  """
50
- if datetime_value is None:
51
- return ""
52
-
53
- try:
54
- parsed = try_parse_nepali_datetime(datetime_value)
55
- if parsed is not None:
56
- return parsed.strftime(format_string)
57
- if isinstance(datetime_value, nepalidatetime):
58
- return datetime_value.strftime(
59
- format_string
60
- ) # defensive; should be covered above
61
- except (ValueError, TypeError, AttributeError):
62
- pass
44
+ return _format_nepali_common(
45
+ date_value, try_parse_nepali_date, format_string, ne, nepalidate
46
+ )
63
47
 
64
- return str(datetime_value) if datetime_value else ""
65
48
 
66
-
67
- class NepaliDateFilter(admin.FieldListFilter):
49
+ def format_nepali_datetime(datetime_value, format_string=None, ne=False):
68
50
  """
69
- A list filter for NepaliDateField to filter by BS Year.
51
+ Format a nepalidatetime object with Nepali month names.
70
52
  """
53
+ if format_string is None:
54
+ if nepkit_settings.TIME_FORMAT == 24:
55
+ format_string = "%B %d, %Y %H:%M"
56
+ else:
57
+ format_string = "%B %d, %Y %I:%M %p"
58
+
59
+ return _format_nepali_common(
60
+ datetime_value, try_parse_nepali_datetime, format_string, ne, nepalidatetime
61
+ )
62
+
63
+
64
+ class BaseNepaliDateFilter(admin.FieldListFilter):
65
+ """Base class for date filters (Year/Month)."""
71
66
 
72
67
  def __init__(self, field, request, params, model, model_admin, field_path):
73
- self.parameter_name = f"{field_path}_bs_year"
68
+ self.parameter_name = f"{field_path}_{self.suffix}"
74
69
  super().__init__(field, request, params, model, model_admin, field_path)
75
- self.title = _("Nepali Date (Year)")
76
70
 
77
71
  def expected_parameters(self):
78
72
  return [self.parameter_name]
@@ -83,40 +77,85 @@ class NepaliDateFilter(admin.FieldListFilter):
83
77
  "query_string": changelist.get_query_string(remove=[self.parameter_name]),
84
78
  "display": _("All"),
85
79
  }
86
- current_year = nepalidate.today().year
87
- for year in range(current_year - 10, current_year + 2):
80
+ for value, display in self.get_filter_options():
88
81
  yield {
89
- "selected": self.used_parameters.get(self.parameter_name) == str(year),
82
+ "selected": self.used_parameters.get(self.parameter_name) == str(value),
90
83
  "query_string": changelist.get_query_string(
91
- {self.parameter_name: str(year)}
84
+ {self.parameter_name: str(value)}
92
85
  ),
93
- "display": str(year),
86
+ "display": display,
94
87
  }
95
88
 
96
89
  def queryset(self, request, queryset):
97
90
  value = self.used_parameters.get(self.parameter_name)
98
91
  if value:
99
- year = int(value)
100
- # Convert BS year range to AD date range
101
- start_date_bs = nepalidate(year, 1, 1)
102
- # Find last day of the year. Some BS years end on the 30th,
103
- # so we try 31 first and then fall back to 30 if that date
104
- # is not valid.
105
- try:
106
- end_date_bs = nepalidate(year, 12, 31)
107
- except ValueError:
108
- end_date_bs = nepalidate(year, 12, 30)
92
+ return self.apply_filter(queryset, value)
93
+ return queryset
109
94
 
110
- start_date_ad = start_date_bs.to_date()
111
- end_date_ad = end_date_bs.to_date()
95
+ def get_filter_options(self):
96
+ raise NotImplementedError
112
97
 
98
+ def apply_filter(self, queryset, value):
99
+ raise NotImplementedError
100
+
101
+
102
+ class NepaliDateFilter(BaseNepaliDateFilter):
103
+ """Filter by Nepali Year (e.g., 2080)."""
104
+
105
+ suffix = "bs_year"
106
+ title = _("Nepali Date (Year)")
107
+
108
+ def get_filter_options(self):
109
+ current_year = nepalidate.today().year
110
+ return [(y, str(y)) for y in range(current_year - 10, current_year + 2)]
111
+
112
+ def apply_filter(self, queryset, value):
113
+ if BS_DATE_FORMAT.startswith("%Y"):
114
+ separator = BS_DATE_FORMAT[2] if len(BS_DATE_FORMAT) > 2 else "-"
113
115
  return queryset.filter(
114
- **{f"{self.field_path}__range": (start_date_ad, end_date_ad)}
116
+ **{f"{self.field_path}__startswith": f"{value}{separator}"}
115
117
  )
116
- return queryset
118
+
119
+ return queryset.filter(**{f"{self.field_path}__icontains": f"{value}"})
120
+
121
+
122
+ class NepaliMonthFilter(BaseNepaliDateFilter):
123
+ """Filter by Nepali Month (e.g., Baisakh)."""
124
+
125
+ suffix = "bs_month"
126
+ title = _("Nepali Date (Month)")
127
+
128
+ def get_filter_options(self):
129
+ ne = nepkit_settings.DEFAULT_LANGUAGE == "ne"
130
+ names = [
131
+ ("बैशाख", "Baisakh"),
132
+ ("जेठ", "Jestha"),
133
+ ("असार", "Ashad"),
134
+ ("साउन", "Shrawan"),
135
+ ("भदौ", "Bhadra"),
136
+ ("असोज", "Ashwin"),
137
+ ("कात्तिक", "Kartik"),
138
+ ("मंसिर", "Mangsir"),
139
+ ("पुष", "Poush"),
140
+ ("माघ", "Magh"),
141
+ ("फागुन", "Falgun"),
142
+ ("चैत", "Chaitra"),
143
+ ]
144
+ return [(f"{i:02d}", n[0] if ne else n[1]) for i, n in enumerate(names, 1)]
145
+
146
+ def apply_filter(self, queryset, value):
147
+ from django_nepkit.utils import BS_DATE_FORMAT
148
+
149
+ if BS_DATE_FORMAT == "%Y-%m-%d":
150
+ return queryset.filter(**{f"{self.field_path}__contains": f"-{value}-"})
151
+
152
+ separator = BS_DATE_FORMAT[2] if len(BS_DATE_FORMAT) > 2 else "-"
153
+ return queryset.filter(
154
+ **{f"{self.field_path}__contains": f"{separator}{value}{separator}"}
155
+ )
117
156
 
118
157
 
119
- # Register NepaliDateFilter as the default list filter for NepaliDateField
158
+ # Standard filter for any NepaliDateField in Admin
120
159
  admin.FieldListFilter.register(
121
160
  lambda f: isinstance(f, NepaliDateField),
122
161
  NepaliDateFilter,
@@ -125,65 +164,176 @@ admin.FieldListFilter.register(
125
164
 
126
165
 
127
166
  class NepaliAdminMixin:
128
- """
129
- Mixin for Django admin classes that provides Nepali date utilities.
130
- Makes format_nepali_date and NepaliDateFilter available without explicit imports.
131
- """
167
+ """Provides date formatting tools for Admin classes."""
168
+
169
+ def _get_field_ne_setting(self, field_name):
170
+ """
171
+ Get the 'ne' setting from a model field.
172
+
173
+ Args:
174
+ field_name: Name of the field in the model
175
+
176
+ Returns:
177
+ True if field has ne=True, False otherwise
178
+ """
179
+ if not hasattr(self, "model"):
180
+ return False
132
181
 
133
- def format_nepali_date(self, date_value, format_string="%B %d, %Y"):
182
+ try:
183
+ field = self.model._meta.get_field(field_name)
184
+ if hasattr(field, "ne"):
185
+ return field.ne
186
+ except (AttributeError, LookupError):
187
+ pass
188
+
189
+ return False
190
+
191
+ def format_nepali_date(
192
+ self, date_value, format_string="%B %d, %Y", ne=None, field_name=None
193
+ ):
134
194
  """
135
195
  Format a nepalidate object with Nepali month names.
136
196
  Available as a method on admin classes using this mixin.
197
+
198
+ Args:
199
+ date_value: A nepalidate object or string
200
+ format_string: strftime format string
201
+ ne: If True, format using Devanagari script. If None, auto-detect from field or global settings (default: None)
202
+ field_name: Name of the field to auto-detect 'ne' setting from (optional)
137
203
  """
138
- return format_nepali_date(date_value, format_string)
204
+ if ne is None and field_name:
205
+ ne = self._get_field_ne_setting(field_name)
206
+ elif ne is None:
207
+ ne = nepkit_settings.DEFAULT_LANGUAGE == "ne"
208
+
209
+ return format_nepali_date(date_value, format_string, ne=ne)
139
210
 
140
211
  def format_nepali_datetime(
141
- self, datetime_value, format_string="%B %d, %Y %I:%M %p"
212
+ self,
213
+ datetime_value,
214
+ format_string=None,
215
+ ne=None,
216
+ field_name=None,
142
217
  ):
143
- return format_nepali_datetime(datetime_value, format_string)
218
+ """
219
+ Format a nepalidatetime object with Nepali month names.
220
+
221
+ Args:
222
+ datetime_value: A nepalidatetime object or string
223
+ format_string: strftime format string
224
+ ne: If True, format using Devanagari script. If None, auto-detect from field or global settings (default: None)
225
+ field_name: Name of the field to auto-detect 'ne' setting from (optional)
226
+ """
227
+ if ne is None and field_name:
228
+ ne = self._get_field_ne_setting(field_name)
229
+ elif ne is None:
230
+ ne = nepkit_settings.DEFAULT_LANGUAGE == "ne"
231
+
232
+ return format_nepali_datetime(datetime_value, format_string, ne=ne)
233
+
234
+ def format_nepali_currency(self, value, currency_symbol="Rs.", ne=False, **kwargs):
235
+ """
236
+ Format a number with Nepali-style commas.
237
+ Available as a method on admin classes using this mixin.
238
+ """
239
+ return format_nepali_currency(value, currency_symbol=currency_symbol, ne=ne)
144
240
 
145
241
 
146
242
  class NepaliModelAdmin(NepaliAdminMixin, admin.ModelAdmin):
147
243
  """
148
- Base ModelAdmin class with Nepali date utilities built-in.
149
- Use this instead of admin.ModelAdmin to have format_nepali_date available.
244
+ Standard Admin class that automatically formats Nepali dates in lists.
150
245
 
151
246
  Example:
152
247
  from django_nepkit import NepaliModelAdmin, NepaliDateFilter
153
248
 
154
249
  @admin.register(MyModel)
155
250
  class MyModelAdmin(NepaliModelAdmin):
156
- list_filter = (('date_field', NepaliDateFilter),)
157
-
158
- def display_date(self, obj):
159
- return self.format_nepali_date(obj.date_field)
251
+ list_display = ("name", "birth_date", "created_at") # auto-formatted
252
+ list_filter = (("birth_date", NepaliDateFilter),)
160
253
  """
161
254
 
162
- # Make NepaliDateFilter available as a class attribute
255
+ # Make filters available as class attributes
163
256
  NepaliDateFilter = NepaliDateFilter
257
+ NepaliMonthFilter = NepaliMonthFilter
258
+
259
+ def _make_nepali_display(self, field_name, formatter_method):
260
+ """Helper to create display columns for Nepali dates."""
261
+ admin_instance = self
262
+ try:
263
+ field = self.model._meta.get_field(field_name)
264
+ short_description = getattr(
265
+ field, "verbose_name", field_name.replace("_", " ").title()
266
+ )
267
+ except Exception:
268
+ short_description = field_name.replace("_", " ").title()
269
+
270
+ def display(obj):
271
+ val = getattr(obj, field_name, None)
272
+ if val is None:
273
+ return admin_instance.get_empty_value_display()
274
+ # Call the passed formatter method (bound to self)
275
+ return formatter_method(val, field_name=field_name)
276
+
277
+ display.short_description = short_description
278
+ display.admin_order_field = field_name
279
+ return display
280
+
281
+ def _make_nepali_date_display(self, field_name):
282
+ return self._make_nepali_display(field_name, self.format_nepali_date)
283
+
284
+ def _make_nepali_datetime_display(self, field_name):
285
+ return self._make_nepali_display(field_name, self.format_nepali_datetime)
286
+
287
+ def _make_nepali_currency_display(self, field_name):
288
+ return self._make_nepali_display(field_name, self.format_nepali_currency)
289
+
290
+ def get_list_display(self, request):
291
+ list_display = super().get_list_display(request)
292
+ result = []
293
+ for item in list_display:
294
+ if not isinstance(item, str):
295
+ result.append(item)
296
+ continue
297
+ try:
298
+ field = self.model._meta.get_field(item)
299
+ if isinstance(field, NepaliDateField):
300
+ result.append(self._make_nepali_date_display(item))
301
+ continue
302
+ if isinstance(field, NepaliDateTimeField):
303
+ result.append(self._make_nepali_datetime_display(item))
304
+ continue
305
+ if isinstance(field, NepaliCurrencyField):
306
+ result.append(self._make_nepali_currency_display(item))
307
+ continue
308
+ except Exception:
309
+ pass
310
+ result.append(item)
311
+ return result
164
312
 
165
- # Ensure admin forms render Nepali fields with the proper widget,
166
- # even if a project doesn't provide custom ModelForms.
167
313
  def formfield_for_dbfield(self, db_field, request, **kwargs):
168
- """
169
- Force Nepali widgets in Django admin without requiring user forms.
170
- """
314
+ """Automatically use NepaliDatePicker in the admin form."""
171
315
  try:
172
316
  from django_nepkit.models import NepaliDateField, NepaliDateTimeField
173
317
  from django_nepkit.widgets import NepaliDatePickerWidget
174
318
  except Exception:
175
319
  return super().formfield_for_dbfield(db_field, request, **kwargs)
176
320
 
177
- if isinstance(db_field, (NepaliDateField, NepaliDateTimeField)):
178
- kwargs.setdefault("widget", NepaliDatePickerWidget)
321
+ if (
322
+ isinstance(db_field, (NepaliDateField, NepaliDateTimeField))
323
+ and nepkit_settings.ADMIN_DATEPICKER
324
+ ):
325
+ # Pass ne/en parameters from field to widget if they exist
326
+ widget_kwargs = {}
327
+ if hasattr(db_field, "ne"):
328
+ widget_kwargs["ne"] = db_field.ne
329
+ if hasattr(db_field, "en"):
330
+ widget_kwargs["en"] = db_field.en
331
+ kwargs.setdefault("widget", NepaliDatePickerWidget(**widget_kwargs))
179
332
 
180
333
  return super().formfield_for_dbfield(db_field, request, **kwargs)
181
334
 
182
335
  class Media:
183
- """
184
- Django admin ships jQuery as `django.jQuery` (not `window.jQuery`).
185
- The Nepali date picker library expects a global `jQuery`, so we bridge it.
186
- """
336
+ """Loads the Nepali Datepicker and bridging scripts."""
187
337
 
188
338
  css = {
189
339
  "all": (
@@ -201,9 +351,9 @@ class NepaliModelAdmin(NepaliAdminMixin, admin.ModelAdmin):
201
351
  )
202
352
 
203
353
 
204
- # Exporting for easy usage
205
354
  __all__ = [
206
355
  "NepaliDateFilter",
356
+ "NepaliMonthFilter",
207
357
  "format_nepali_date",
208
358
  "format_nepali_datetime",
209
359
  "NepaliAdminMixin",
django_nepkit/conf.py ADDED
@@ -0,0 +1,34 @@
1
+ from django.conf import settings
2
+
3
+ DEFAULTS = {
4
+ "DEFAULT_LANGUAGE": "en",
5
+ "DATE_INPUT_FORMATS": ["%Y-%m-%d", "%d/%m/%Y", "%d-%m-%Y"],
6
+ "ADMIN_DATEPICKER": True,
7
+ "TIME_FORMAT": 12,
8
+ "BS_DATE_FORMAT": "%Y-%m-%d",
9
+ "BS_DATETIME_FORMAT": "%Y-%m-%d %H:%M:%S",
10
+ }
11
+
12
+
13
+ class NepkitSettings:
14
+ """Handles the global NEPKIT settings from your settings.py."""
15
+
16
+ def __init__(self, user_settings=None, defaults=None):
17
+ self._user_settings = user_settings or {}
18
+ self.defaults = defaults or DEFAULTS
19
+
20
+ def __getattr__(self, attr):
21
+ if attr not in self.defaults:
22
+ raise AttributeError(f"Invalid NEPKIT setting: '{attr}'")
23
+
24
+ try:
25
+ # Check if the user has a custom setting
26
+ val = self._user_settings[attr]
27
+ except KeyError:
28
+ # Use default value if not set
29
+ val = self.defaults[attr]
30
+
31
+ return val
32
+
33
+
34
+ nepkit_settings = NepkitSettings(getattr(settings, "NEPKIT", {}), DEFAULTS)
@@ -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 YYYY-MM-DD format."),
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)