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/__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.
|
|
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
|
|
13
|
-
"""
|
|
14
|
-
|
|
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 =
|
|
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(
|
|
35
|
-
|
|
36
|
-
format_string
|
|
37
|
-
)
|
|
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(
|
|
37
|
+
return str(value) if value else ""
|
|
42
38
|
|
|
43
39
|
|
|
44
|
-
def
|
|
40
|
+
def format_nepali_date(date_value, format_string="%B %d, %Y", ne=False):
|
|
45
41
|
"""
|
|
46
|
-
Format a
|
|
47
|
-
|
|
48
|
-
Default output uses **12-hour time with AM/PM**.
|
|
42
|
+
Format a nepalidate object with Nepali month names.
|
|
49
43
|
"""
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
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}
|
|
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
|
-
|
|
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(
|
|
82
|
+
"selected": self.used_parameters.get(self.parameter_name) == str(value),
|
|
90
83
|
"query_string": changelist.get_query_string(
|
|
91
|
-
{self.parameter_name: str(
|
|
84
|
+
{self.parameter_name: str(value)}
|
|
92
85
|
),
|
|
93
|
-
"display":
|
|
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
|
-
|
|
100
|
-
|
|
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
|
-
|
|
111
|
-
|
|
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}
|
|
116
|
+
**{f"{self.field_path}__startswith": f"{value}{separator}"}
|
|
115
117
|
)
|
|
116
|
-
|
|
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
|
-
#
|
|
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
|
-
|
|
130
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
212
|
+
self,
|
|
213
|
+
datetime_value,
|
|
214
|
+
format_string=None,
|
|
215
|
+
ne=None,
|
|
216
|
+
field_name=None,
|
|
142
217
|
):
|
|
143
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
178
|
-
|
|
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)
|
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)
|