django-nepkit 0.1.0__py3-none-any.whl → 0.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- django_nepkit/__init__.py +20 -0
- django_nepkit/admin.py +243 -93
- django_nepkit/conf.py +34 -0
- django_nepkit/constants.py +129 -0
- django_nepkit/filters.py +113 -0
- django_nepkit/forms.py +9 -9
- django_nepkit/lang_utils.py +52 -0
- django_nepkit/models.py +138 -161
- django_nepkit/serializers.py +127 -28
- django_nepkit/static/django_nepkit/js/address-chaining.js +129 -29
- django_nepkit/static/django_nepkit/js/nepal-data.js +1 -0
- django_nepkit/static/django_nepkit/js/nepali-datepicker-init.js +55 -46
- django_nepkit/utils.py +270 -46
- django_nepkit/validators.py +1 -1
- django_nepkit/views.py +127 -10
- django_nepkit/widgets.py +100 -31
- django_nepkit-0.2.1.dist-info/METADATA +308 -0
- django_nepkit-0.2.1.dist-info/RECORD +37 -0
- example/demo/admin.py +45 -21
- example/demo/models.py +41 -4
- example/demo/serializers.py +39 -0
- example/demo/urls.py +13 -2
- example/demo/views.py +125 -3
- example/example_project/settings.py +10 -0
- example/example_project/urls.py +1 -1
- example/manage.py +2 -2
- django_nepkit/templatetags/__init__.py +0 -0
- django_nepkit/templatetags/nepali.py +0 -74
- django_nepkit-0.1.0.dist-info/METADATA +0 -377
- django_nepkit-0.1.0.dist-info/RECORD +0 -39
- example/demo/migrations/0001_initial.py +0 -2113
- example/demo/migrations/0002_alter_person_phone_number.py +0 -18
- example/demo/migrations/0003_person_created_at_person_updated_at.py +0 -27
- example/demo/migrations/0004_alter_person_created_at_alter_person_updated_at.py +0 -23
- example/demo/migrations/0005_alter_person_created_at_alter_person_updated_at.py +0 -27
- example/demo/migrations/__init__.py +0 -0
- {django_nepkit-0.1.0.dist-info → django_nepkit-0.2.1.dist-info}/WHEEL +0 -0
- {django_nepkit-0.1.0.dist-info → django_nepkit-0.2.1.dist-info}/licenses/LICENSE +0 -0
- {django_nepkit-0.1.0.dist-info → django_nepkit-0.2.1.dist-info}/top_level.txt +0 -0
django_nepkit/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
|
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
(function() {
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
+
// Placeholder text constants
|
|
5
|
+
const PLACEHOLDERS = {
|
|
6
|
+
province: { ne: 'प्रदेश छान्नुहोस्', en: 'Select Province' },
|
|
7
|
+
district: { ne: 'जिल्ला छान्नुहोस्', en: 'Select District' },
|
|
8
|
+
municipality: { ne: 'नगरपालिका छान्नुहोस्', en: 'Select Municipality' }
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Get placeholder text for a specific location type
|
|
13
|
+
*/
|
|
14
|
+
function getPlaceholder(type, isNepali) {
|
|
15
|
+
return PLACEHOLDERS[type][isNepali ? 'ne' : 'en'];
|
|
16
|
+
}
|
|
17
|
+
|
|
4
18
|
function updateOptions(selectElement, data, placeholder) {
|
|
5
19
|
selectElement.innerHTML = '';
|
|
6
20
|
if (placeholder) {
|
|
@@ -18,47 +32,133 @@
|
|
|
18
32
|
selectElement.dispatchEvent(new Event('change'));
|
|
19
33
|
}
|
|
20
34
|
|
|
35
|
+
function getLocalData(type, parentId, isNepali) {
|
|
36
|
+
if (!window.NEPKIT_DATA) return null;
|
|
37
|
+
const lang = isNepali ? 'ne' : 'en';
|
|
38
|
+
const data = window.NEPKIT_DATA[lang];
|
|
39
|
+
if (!data) return null;
|
|
40
|
+
|
|
41
|
+
if (type === 'districts') {
|
|
42
|
+
return data.districts[parentId] || [];
|
|
43
|
+
} else if (type === 'municipalities') {
|
|
44
|
+
return data.municipalities[parentId] || [];
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function getMatchingSelect(container, selector, isNepali) {
|
|
50
|
+
const matches = container.querySelectorAll(selector);
|
|
51
|
+
for (let el of matches) {
|
|
52
|
+
const elIsNepali = el.dataset.ne === 'true';
|
|
53
|
+
if (elIsNepali === isNepali) {
|
|
54
|
+
return el;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Fetch data from server endpoint
|
|
62
|
+
*/
|
|
63
|
+
function fetchData(selectElement, paramName, paramValue, placeholderType, isNepali) {
|
|
64
|
+
if (!selectElement.dataset.url) return;
|
|
65
|
+
|
|
66
|
+
let url = selectElement.dataset.url + '?' + paramName + '=' + encodeURIComponent(paramValue);
|
|
67
|
+
if (isNepali) url += '&ne=true';
|
|
68
|
+
if (selectElement.dataset.en === 'true') url += '&en=true';
|
|
69
|
+
|
|
70
|
+
fetch(url)
|
|
71
|
+
.then(response => response.json())
|
|
72
|
+
.then(data => {
|
|
73
|
+
updateOptions(selectElement, data, getPlaceholder(placeholderType, isNepali));
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Update dependent select element based on parent value
|
|
79
|
+
*/
|
|
80
|
+
function updateDependentSelect(childSelect, dataType, parentValue, placeholderType, isNepali, paramName) {
|
|
81
|
+
if (!childSelect) return;
|
|
82
|
+
|
|
83
|
+
if (!parentValue) {
|
|
84
|
+
updateOptions(childSelect, [], getPlaceholder(placeholderType, isNepali));
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const localData = getLocalData(dataType, parentValue, isNepali);
|
|
89
|
+
|
|
90
|
+
if (localData) {
|
|
91
|
+
updateOptions(childSelect, localData, getPlaceholder(placeholderType, isNepali));
|
|
92
|
+
} else {
|
|
93
|
+
fetchData(childSelect, paramName, parentValue, placeholderType, isNepali);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function init() {
|
|
98
|
+
// Initialize province selects
|
|
99
|
+
document.querySelectorAll('.nepkit-province-select').forEach(provinceSelect => {
|
|
100
|
+
const isNepali = provinceSelect.dataset.ne === 'true';
|
|
101
|
+
const container = provinceSelect.closest('form') || document;
|
|
102
|
+
const districtSelect = getMatchingSelect(container, '.nepkit-district-select', isNepali);
|
|
103
|
+
const municipalitySelect = getMatchingSelect(container, '.nepkit-municipality-select', isNepali);
|
|
104
|
+
|
|
105
|
+
if (!provinceSelect.value) {
|
|
106
|
+
if (districtSelect && !districtSelect.value) {
|
|
107
|
+
updateOptions(districtSelect, [], getPlaceholder('district', isNepali));
|
|
108
|
+
}
|
|
109
|
+
if (municipalitySelect && !municipalitySelect.value) {
|
|
110
|
+
updateOptions(municipalitySelect, [], getPlaceholder('municipality', isNepali));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// Initialize district selects
|
|
116
|
+
document.querySelectorAll('.nepkit-district-select').forEach(districtSelect => {
|
|
117
|
+
const isNepali = districtSelect.dataset.ne === 'true';
|
|
118
|
+
const container = districtSelect.closest('form') || document;
|
|
119
|
+
const municipalitySelect = getMatchingSelect(container, '.nepkit-municipality-select', isNepali);
|
|
120
|
+
|
|
121
|
+
if (!districtSelect.value) {
|
|
122
|
+
if (municipalitySelect && !municipalitySelect.value) {
|
|
123
|
+
updateOptions(municipalitySelect, [], getPlaceholder('municipality', isNepali));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
21
129
|
document.addEventListener('change', function(e) {
|
|
130
|
+
// Handle province select change
|
|
22
131
|
if (e.target.matches('.nepkit-province-select')) {
|
|
23
132
|
const province = e.target.value;
|
|
133
|
+
const isNepali = e.target.dataset.ne === 'true';
|
|
24
134
|
const container = e.target.closest('form') || document;
|
|
25
|
-
const districtSelect = container
|
|
26
|
-
const municipalitySelect = container
|
|
27
|
-
|
|
28
|
-
if (districtSelect) {
|
|
29
|
-
if (!province) {
|
|
30
|
-
updateOptions(districtSelect, [], 'Select District');
|
|
31
|
-
if (municipalitySelect) updateOptions(municipalitySelect, [], 'Select Municipality');
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
135
|
+
const districtSelect = getMatchingSelect(container, '.nepkit-district-select', isNepali);
|
|
136
|
+
const municipalitySelect = getMatchingSelect(container, '.nepkit-municipality-select', isNepali);
|
|
34
137
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
.then(data => {
|
|
39
|
-
updateOptions(districtSelect, data, 'Select District');
|
|
40
|
-
});
|
|
138
|
+
// Always clear municipality if province changes
|
|
139
|
+
if (municipalitySelect) {
|
|
140
|
+
updateOptions(municipalitySelect, [], getPlaceholder('municipality', isNepali));
|
|
41
141
|
}
|
|
142
|
+
|
|
143
|
+
// Update district options
|
|
144
|
+
updateDependentSelect(districtSelect, 'districts', province, 'district', isNepali, 'province');
|
|
42
145
|
}
|
|
43
146
|
|
|
147
|
+
// Handle district select change
|
|
44
148
|
if (e.target.matches('.nepkit-district-select')) {
|
|
45
149
|
const district = e.target.value;
|
|
150
|
+
const isNepali = e.target.dataset.ne === 'true';
|
|
46
151
|
const container = e.target.closest('form') || document;
|
|
47
|
-
const municipalitySelect = container
|
|
48
|
-
|
|
49
|
-
if (municipalitySelect) {
|
|
50
|
-
if (!district) {
|
|
51
|
-
updateOptions(municipalitySelect, [], 'Select Municipality');
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
152
|
+
const municipalitySelect = getMatchingSelect(container, '.nepkit-municipality-select', isNepali);
|
|
54
153
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
.then(response => response.json())
|
|
58
|
-
.then(data => {
|
|
59
|
-
updateOptions(municipalitySelect, data, 'Select Municipality');
|
|
60
|
-
});
|
|
61
|
-
}
|
|
154
|
+
// Update municipality options
|
|
155
|
+
updateDependentSelect(municipalitySelect, 'municipalities', district, 'municipality', isNepali, 'district');
|
|
62
156
|
}
|
|
63
157
|
});
|
|
158
|
+
|
|
159
|
+
if (document.readyState === 'loading') {
|
|
160
|
+
document.addEventListener('DOMContentLoaded', init);
|
|
161
|
+
} else {
|
|
162
|
+
init();
|
|
163
|
+
}
|
|
64
164
|
})();
|