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
|
@@ -45,64 +45,73 @@
|
|
|
45
45
|
if (!inputs || !inputs.length) return;
|
|
46
46
|
|
|
47
47
|
inputs.forEach(function (el) {
|
|
48
|
+
var format = el.dataset.format || 'YYYY-MM-DD';
|
|
49
|
+
// Convert strftime format to datepicker format
|
|
50
|
+
format = format.replace(/%Y/g, 'YYYY').replace(/%m/g, 'MM').replace(/%d/g, 'DD');
|
|
51
|
+
|
|
52
|
+
var options = {
|
|
53
|
+
dateFormat: format,
|
|
54
|
+
closeOnDateSelect: true
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// Determine language based on data attributes
|
|
58
|
+
var useNepali = el.dataset.ne === 'true';
|
|
59
|
+
var useEnglish = el.dataset.en === 'true';
|
|
60
|
+
|
|
61
|
+
if (useNepali) {
|
|
62
|
+
// Devanagari digits and Nepali month/day names
|
|
63
|
+
options.unicodeDate = true;
|
|
64
|
+
} else if (useEnglish || (!useNepali && !el.dataset.ne)) {
|
|
65
|
+
// English month/day names and digits
|
|
66
|
+
options.language = 'english';
|
|
67
|
+
}
|
|
68
|
+
|
|
48
69
|
// Nepali Datepicker v5 exposes `element.NepaliDatePicker(options)`
|
|
49
70
|
if (typeof el.NepaliDatePicker === 'function') {
|
|
50
|
-
el.NepaliDatePicker(
|
|
51
|
-
|
|
52
|
-
|
|
71
|
+
el.NepaliDatePicker(options);
|
|
72
|
+
el.classList.add('nepali-datepicker-initialized');
|
|
73
|
+
}
|
|
74
|
+
// Fallback for jQuery plugin (v2.0.2)
|
|
75
|
+
else if (typeof window.jQuery !== 'undefined' && typeof window.jQuery(el).nepaliDatePicker === 'function') {
|
|
76
|
+
window.jQuery(el).nepaliDatePicker(options);
|
|
53
77
|
el.classList.add('nepali-datepicker-initialized');
|
|
78
|
+
}
|
|
54
79
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
80
|
+
// Ensure theme is applied when the picker is actually shown.
|
|
81
|
+
// (The plugin often creates/inserts DOM on focus.)
|
|
82
|
+
var applySoon = function () {
|
|
83
|
+
// Run twice: once immediately, once after paint.
|
|
84
|
+
applyThemeToDatepickerContainers();
|
|
85
|
+
window.setTimeout(applyThemeToDatepickerContainers, 0);
|
|
86
|
+
};
|
|
62
87
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
88
|
+
el.addEventListener('focus', applySoon);
|
|
89
|
+
el.addEventListener('click', applySoon);
|
|
66
90
|
});
|
|
67
91
|
}
|
|
68
92
|
|
|
69
|
-
//
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
if (mql && typeof mql.addEventListener === 'function') {
|
|
81
|
-
mql.addEventListener('change', applyThemeToDatepickerContainers);
|
|
82
|
-
} else if (mql && typeof mql.addListener === 'function') {
|
|
83
|
-
mql.addListener(applyThemeToDatepickerContainers);
|
|
84
|
-
}
|
|
85
|
-
} catch (e) {
|
|
86
|
-
// ignore
|
|
93
|
+
// Also listen for DOM changes (admin popups/other dynamic content)
|
|
94
|
+
function initObserver() {
|
|
95
|
+
if (typeof MutationObserver !== 'undefined' && document.body) {
|
|
96
|
+
var observer = new MutationObserver(function () {
|
|
97
|
+
initNepaliDatePickers();
|
|
98
|
+
applyThemeToDatepickerContainers();
|
|
99
|
+
});
|
|
100
|
+
observer.observe(document.body, {
|
|
101
|
+
childList: true,
|
|
102
|
+
subtree: true
|
|
103
|
+
});
|
|
87
104
|
}
|
|
88
105
|
}
|
|
89
106
|
|
|
90
|
-
//
|
|
91
|
-
if (
|
|
92
|
-
|
|
93
|
-
initNepaliDatePickers();
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Also listen for DOM changes (admin popups/other dynamic content)
|
|
98
|
-
if (typeof MutationObserver !== 'undefined') {
|
|
99
|
-
var observer = new MutationObserver(function () {
|
|
107
|
+
// Initialize on page load
|
|
108
|
+
if (document.readyState === 'loading') {
|
|
109
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
100
110
|
initNepaliDatePickers();
|
|
101
|
-
|
|
102
|
-
});
|
|
103
|
-
observer.observe(document.body, {
|
|
104
|
-
childList: true,
|
|
105
|
-
subtree: true
|
|
111
|
+
initObserver();
|
|
106
112
|
});
|
|
113
|
+
} else {
|
|
114
|
+
initNepaliDatePickers();
|
|
115
|
+
initObserver();
|
|
107
116
|
}
|
|
108
117
|
})();
|
django_nepkit/utils.py
CHANGED
|
@@ -3,75 +3,299 @@ from __future__ import annotations
|
|
|
3
3
|
from typing import Any, Optional
|
|
4
4
|
|
|
5
5
|
from nepali.datetime import nepalidate, nepalidatetime
|
|
6
|
+
from nepali.locations import districts, municipalities, provinces
|
|
6
7
|
|
|
8
|
+
from django_nepkit.conf import nepkit_settings
|
|
7
9
|
|
|
8
|
-
BS_DATE_FORMAT =
|
|
9
|
-
BS_DATETIME_FORMAT =
|
|
10
|
+
BS_DATE_FORMAT = nepkit_settings.BS_DATE_FORMAT
|
|
11
|
+
BS_DATETIME_FORMAT = nepkit_settings.BS_DATETIME_FORMAT
|
|
10
12
|
|
|
11
13
|
|
|
12
|
-
def
|
|
13
|
-
"""
|
|
14
|
-
Best-effort conversion to `nepalidate`.
|
|
15
|
-
|
|
16
|
-
- Returns `None` for empty values.
|
|
17
|
-
- Returns `nepalidate` for valid inputs.
|
|
18
|
-
- Returns `None` if the value cannot be parsed.
|
|
19
|
-
|
|
20
|
-
Callers decide whether to raise, fallback, etc.
|
|
21
|
-
"""
|
|
14
|
+
def _try_parse_nepali(value: Any, cls: Any, fallback_fmt: str) -> Any:
|
|
15
|
+
"""Helper to turn a string into a Nepali date object."""
|
|
22
16
|
if value in (None, ""):
|
|
23
17
|
return None
|
|
24
|
-
if isinstance(value,
|
|
18
|
+
if isinstance(value, cls):
|
|
25
19
|
return value
|
|
26
20
|
if isinstance(value, str):
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
21
|
+
formats = nepkit_settings.DATE_INPUT_FORMATS
|
|
22
|
+
if fallback_fmt not in formats:
|
|
23
|
+
formats = list(formats) + [fallback_fmt]
|
|
24
|
+
|
|
25
|
+
for fmt in formats:
|
|
26
|
+
try:
|
|
27
|
+
return cls.strptime(value.strip(), fmt)
|
|
28
|
+
except Exception:
|
|
29
|
+
continue
|
|
31
30
|
return None
|
|
32
31
|
|
|
33
32
|
|
|
33
|
+
def try_parse_nepali_date(value: Any) -> Optional[nepalidate]:
|
|
34
|
+
"""Convert any value to a Nepali Date."""
|
|
35
|
+
return _try_parse_nepali(value, nepalidate, BS_DATE_FORMAT)
|
|
36
|
+
|
|
37
|
+
|
|
34
38
|
def try_parse_nepali_datetime(value: Any) -> Optional[nepalidatetime]:
|
|
39
|
+
"""Convert any value to a Nepali Date and Time."""
|
|
40
|
+
return _try_parse_nepali(value, nepalidatetime, BS_DATETIME_FORMAT)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _get_location_children(parent_list, parent_name, child_attr, ne=False):
|
|
44
|
+
"""Find children (like districts) of a parent (like a province)."""
|
|
45
|
+
selected_parent = None
|
|
46
|
+
for p in parent_list:
|
|
47
|
+
p_name = p.name
|
|
48
|
+
p_name_ne = getattr(p, "name_nepali", None)
|
|
49
|
+
|
|
50
|
+
if p_name == parent_name or p_name_ne == parent_name:
|
|
51
|
+
selected_parent = p
|
|
52
|
+
break
|
|
53
|
+
|
|
54
|
+
if not selected_parent:
|
|
55
|
+
return []
|
|
56
|
+
|
|
57
|
+
children = getattr(selected_parent, child_attr, [])
|
|
58
|
+
|
|
59
|
+
if ne:
|
|
60
|
+
return [
|
|
61
|
+
{
|
|
62
|
+
"id": getattr(child, "name_nepali", child.name),
|
|
63
|
+
"text": getattr(child, "name_nepali", child.name),
|
|
64
|
+
}
|
|
65
|
+
for child in children
|
|
66
|
+
]
|
|
67
|
+
else:
|
|
68
|
+
return [{"id": child.name, "text": child.name} for child in children]
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def get_districts_by_province(province_name, ne=False, en=True):
|
|
72
|
+
"""Get all districts for a province."""
|
|
73
|
+
# Logic note: if ne=True is passed, we shouldn't care about en=True (handled by caller typically)
|
|
74
|
+
return _get_location_children(provinces, province_name, "districts", ne=ne)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def get_municipalities_by_district(district_name, ne=False, en=True):
|
|
78
|
+
"""Get all municipalities for a district."""
|
|
79
|
+
return _get_location_children(districts, district_name, "municipalities", ne=ne)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def format_nepali_currency(
|
|
83
|
+
number: Any, currency_symbol: str = "Rs.", ne: bool = False
|
|
84
|
+
) -> str:
|
|
35
85
|
"""
|
|
36
|
-
|
|
86
|
+
Formats a number with Nepali-style commas and optional currency symbol.
|
|
87
|
+
Eg. 1234567 -> Rs. 12,34,567
|
|
88
|
+
"""
|
|
89
|
+
from nepali.number import add_comma, english_to_nepali
|
|
90
|
+
|
|
91
|
+
if number is None:
|
|
92
|
+
return ""
|
|
93
|
+
|
|
94
|
+
try:
|
|
95
|
+
# Convert to string and split by decimal point
|
|
96
|
+
num_str = f"{float(number):.2f}"
|
|
97
|
+
if "." in num_str:
|
|
98
|
+
integer_part, decimal_part = num_str.split(".")
|
|
99
|
+
else:
|
|
100
|
+
integer_part, decimal_part = num_str, ""
|
|
101
|
+
|
|
102
|
+
# Format integer part with commas
|
|
103
|
+
formatted_integer = add_comma(int(integer_part))
|
|
104
|
+
|
|
105
|
+
# Join back
|
|
106
|
+
res = formatted_integer
|
|
107
|
+
if decimal_part:
|
|
108
|
+
res = f"{res}.{decimal_part}"
|
|
109
|
+
|
|
110
|
+
if ne:
|
|
111
|
+
res = english_to_nepali(res)
|
|
112
|
+
|
|
113
|
+
if currency_symbol:
|
|
114
|
+
return f"{currency_symbol} {res}"
|
|
115
|
+
return res
|
|
116
|
+
except Exception:
|
|
117
|
+
return str(number)
|
|
37
118
|
|
|
38
|
-
- Returns `None` for empty values.
|
|
39
|
-
- Returns `nepalidatetime` for valid inputs.
|
|
40
|
-
- Returns `None` if the value cannot be parsed.
|
|
41
119
|
|
|
42
|
-
|
|
120
|
+
def number_to_nepali_words(number: Any) -> str:
|
|
43
121
|
"""
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
122
|
+
Converts a number to Nepali words.
|
|
123
|
+
Eg. 123 -> एक सय तेईस
|
|
124
|
+
"""
|
|
125
|
+
from django_nepkit.constants import NEPALI_ONES, NEPALI_UNITS
|
|
126
|
+
|
|
127
|
+
if number is None:
|
|
128
|
+
return ""
|
|
129
|
+
|
|
130
|
+
try:
|
|
131
|
+
num = int(float(number))
|
|
132
|
+
except (ValueError, TypeError):
|
|
133
|
+
return str(number)
|
|
134
|
+
|
|
135
|
+
if num == 0:
|
|
136
|
+
return "शून्य"
|
|
54
137
|
|
|
138
|
+
def _convert(n):
|
|
139
|
+
if n == 0:
|
|
140
|
+
return ""
|
|
141
|
+
if n < 100:
|
|
142
|
+
return NEPALI_ONES[n]
|
|
55
143
|
|
|
56
|
-
|
|
144
|
+
for i in range(len(NEPALI_UNITS) - 1, 0, -1):
|
|
145
|
+
div, unit_name = NEPALI_UNITS[i]
|
|
146
|
+
if n >= div:
|
|
147
|
+
prefix_val = n // div
|
|
148
|
+
remainder = n % div
|
|
149
|
+
|
|
150
|
+
# For 'सय' (100), we use NEPALI_ONES[prefix_val]
|
|
151
|
+
# For others, we might need recursive calls if prefix_val >= 100
|
|
152
|
+
prefix_words = _convert(prefix_val)
|
|
153
|
+
res = f"{prefix_words} {unit_name}"
|
|
154
|
+
if remainder > 0:
|
|
155
|
+
res += f" {_convert(remainder)}"
|
|
156
|
+
return res.strip()
|
|
157
|
+
return ""
|
|
158
|
+
|
|
159
|
+
return _convert(num)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def english_to_nepali_unicode(text: Any) -> str:
|
|
57
163
|
"""
|
|
58
|
-
|
|
164
|
+
Converts English text/numbers to Nepali Unicode.
|
|
165
|
+
Currently focuses on numbers.
|
|
59
166
|
"""
|
|
60
|
-
from nepali.
|
|
167
|
+
from nepali.number import english_to_nepali
|
|
61
168
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
return []
|
|
65
|
-
return [{"id": d.name, "text": d.name} for d in selected_province.districts]
|
|
169
|
+
if text is None:
|
|
170
|
+
return ""
|
|
66
171
|
|
|
172
|
+
return english_to_nepali(text)
|
|
67
173
|
|
|
68
|
-
|
|
174
|
+
|
|
175
|
+
def _normalize_nepali_text(text):
|
|
69
176
|
"""
|
|
70
|
-
|
|
177
|
+
Normalize Nepali text for easier matching.
|
|
178
|
+
Replaces Chandrabindu with Anusvara.
|
|
71
179
|
"""
|
|
72
|
-
|
|
180
|
+
if not text:
|
|
181
|
+
return text
|
|
182
|
+
return text.replace("ँ", "ं").replace("ाँ", "ां")
|
|
73
183
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
184
|
+
|
|
185
|
+
def _matches_location_name(name_eng, name_nep, token, normalized_token):
|
|
186
|
+
"""
|
|
187
|
+
Check if a token matches a location name (English or Nepali).
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
name_eng: English name of location
|
|
191
|
+
name_nep: Nepali name of location
|
|
192
|
+
token: Original token to match
|
|
193
|
+
normalized_token: Normalized version of token
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
True if token matches the location name
|
|
197
|
+
"""
|
|
198
|
+
token_lower = token.lower()
|
|
199
|
+
name_nep_norm = _normalize_nepali_text(name_nep)
|
|
200
|
+
|
|
201
|
+
# Exact matches
|
|
202
|
+
if (
|
|
203
|
+
token == name_nep
|
|
204
|
+
or normalized_token == name_nep_norm
|
|
205
|
+
or token_lower == name_eng.lower()
|
|
206
|
+
):
|
|
207
|
+
return True
|
|
208
|
+
|
|
209
|
+
# Partial matches for English (e.g., "Pokhara" in "Pokhara Metropolitan City")
|
|
210
|
+
# Only if token is at least 4 characters to avoid too many false positives
|
|
211
|
+
if len(token) >= 4:
|
|
212
|
+
if token_lower in name_eng.lower():
|
|
213
|
+
return True
|
|
214
|
+
|
|
215
|
+
# Partial matches for Nepali
|
|
216
|
+
if len(normalized_token) >= 2:
|
|
217
|
+
if normalized_token in name_nep_norm:
|
|
218
|
+
return True
|
|
219
|
+
|
|
220
|
+
return False
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def _find_location_in_tokens(location_list, tokens, normalized_tokens):
|
|
224
|
+
"""
|
|
225
|
+
Find a location from a list by matching against tokens.
|
|
226
|
+
|
|
227
|
+
Args:
|
|
228
|
+
location_list: List of location objects to search
|
|
229
|
+
tokens: List of original tokens
|
|
230
|
+
normalized_tokens: List of normalized tokens
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
Matching location object or None
|
|
234
|
+
"""
|
|
235
|
+
for i, token in enumerate(tokens):
|
|
236
|
+
nt = normalized_tokens[i]
|
|
237
|
+
for location in location_list:
|
|
238
|
+
if _matches_location_name(location.name, location.name_nepali, token, nt):
|
|
239
|
+
return location
|
|
240
|
+
return None
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def _is_nepali_text(tokens):
|
|
244
|
+
"""Check if any token contains Devanagari characters."""
|
|
245
|
+
import re
|
|
246
|
+
|
|
247
|
+
return any(re.search(r"[\u0900-\u097F]", t) for t in tokens)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def normalize_address(address_string: str) -> dict[str, Optional[str]]:
|
|
251
|
+
"""
|
|
252
|
+
Attempts to normalize a Nepali address string into Province, District, and Municipality.
|
|
253
|
+
Returns a dictionary with 'province', 'district', and 'municipality'.
|
|
254
|
+
"""
|
|
255
|
+
if not address_string:
|
|
256
|
+
return {"province": None, "district": None, "municipality": None}
|
|
257
|
+
|
|
258
|
+
result: dict[str, Optional[str]] = {
|
|
259
|
+
"province": None,
|
|
260
|
+
"district": None,
|
|
261
|
+
"municipality": None,
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
# Prepare tokens
|
|
265
|
+
content = address_string.replace(",", " ").replace("-", " ")
|
|
266
|
+
tokens = [t.strip() for t in content.split() if t.strip()]
|
|
267
|
+
normalized_tokens = [_normalize_nepali_text(t) for t in tokens]
|
|
268
|
+
|
|
269
|
+
# Find locations - most specific to least specific
|
|
270
|
+
found_municipality = _find_location_in_tokens(
|
|
271
|
+
municipalities, tokens, normalized_tokens
|
|
272
|
+
)
|
|
273
|
+
found_district = _find_location_in_tokens(districts, tokens, normalized_tokens)
|
|
274
|
+
found_province = _find_location_in_tokens(provinces, tokens, normalized_tokens)
|
|
275
|
+
|
|
276
|
+
# Fill in the gaps using hierarchy
|
|
277
|
+
if found_municipality:
|
|
278
|
+
result["municipality"] = found_municipality.name
|
|
279
|
+
if not found_district:
|
|
280
|
+
found_district = found_municipality.district
|
|
281
|
+
if not found_province:
|
|
282
|
+
found_province = found_municipality.province
|
|
283
|
+
|
|
284
|
+
if found_district:
|
|
285
|
+
result["district"] = found_district.name
|
|
286
|
+
if not found_province:
|
|
287
|
+
found_province = found_district.province
|
|
288
|
+
|
|
289
|
+
if found_province:
|
|
290
|
+
result["province"] = found_province.name
|
|
291
|
+
|
|
292
|
+
# Use Nepali names if input contains Nepali text
|
|
293
|
+
if _is_nepali_text(tokens):
|
|
294
|
+
if found_municipality:
|
|
295
|
+
result["municipality"] = found_municipality.name_nepali
|
|
296
|
+
if found_district:
|
|
297
|
+
result["district"] = found_district.name_nepali
|
|
298
|
+
if found_province:
|
|
299
|
+
result["province"] = found_province.name_nepali
|
|
300
|
+
|
|
301
|
+
return result
|
django_nepkit/validators.py
CHANGED
|
@@ -3,7 +3,7 @@ from django.utils.translation import gettext_lazy as _
|
|
|
3
3
|
from nepali import phone_number
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
#
|
|
6
|
+
# Check if a phone number is valid in Nepal
|
|
7
7
|
def validate_nepali_phone_number(value):
|
|
8
8
|
if not phone_number.is_valid(value):
|
|
9
9
|
raise ValidationError(
|
django_nepkit/views.py
CHANGED
|
@@ -1,22 +1,139 @@
|
|
|
1
|
-
from django.http import JsonResponse
|
|
1
|
+
from django.http import JsonResponse, HttpResponse
|
|
2
2
|
|
|
3
3
|
from django_nepkit.utils import (
|
|
4
4
|
get_districts_by_province,
|
|
5
5
|
get_municipalities_by_district,
|
|
6
6
|
)
|
|
7
7
|
|
|
8
|
+
from django_nepkit.conf import nepkit_settings
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
|
|
11
|
+
def _render_options(data, placeholder):
|
|
12
|
+
"""Internal helper to render list of options as HTML."""
|
|
13
|
+
options = [f'<option value="">{placeholder}</option>']
|
|
14
|
+
for item in data:
|
|
15
|
+
options.append(f'<option value="{item["id"]}">{item["text"]}</option>')
|
|
16
|
+
return HttpResponse("\n".join(options), content_type="text/html")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _get_primary_param(request, param_name, exclude_params=None):
|
|
20
|
+
"""
|
|
21
|
+
Extract a parameter from request.GET with fallback logic.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
request: Django request object
|
|
25
|
+
param_name: Primary parameter name to look for
|
|
26
|
+
exclude_params: List of parameter names to exclude from fallback
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
Parameter value or None
|
|
30
|
+
"""
|
|
31
|
+
from django_nepkit.constants import INTERNAL_PARAMS
|
|
32
|
+
|
|
33
|
+
if exclude_params is None:
|
|
34
|
+
exclude_params = INTERNAL_PARAMS
|
|
35
|
+
|
|
36
|
+
# Try primary parameter first
|
|
37
|
+
value = request.GET.get(param_name)
|
|
38
|
+
|
|
39
|
+
# Fallback: take the first non-internal parameter
|
|
40
|
+
if not value:
|
|
41
|
+
for key, val in request.GET.items():
|
|
42
|
+
if key not in exclude_params and val:
|
|
43
|
+
value = val
|
|
44
|
+
break
|
|
45
|
+
|
|
46
|
+
return value
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _parse_language_params(request):
|
|
50
|
+
"""
|
|
51
|
+
Parse language parameters from request.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
request: Django request object
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
Tuple of (ne, en) boolean values
|
|
58
|
+
"""
|
|
59
|
+
default_lang = nepkit_settings.DEFAULT_LANGUAGE
|
|
60
|
+
ne_param = request.GET.get("ne")
|
|
61
|
+
ne = ne_param.lower() == "true" if ne_param else default_lang == "ne"
|
|
62
|
+
|
|
63
|
+
en_param = request.GET.get("en")
|
|
64
|
+
en = en_param.lower() == "true" if en_param else not ne
|
|
65
|
+
|
|
66
|
+
return ne, en
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _should_return_html(request):
|
|
70
|
+
"""
|
|
71
|
+
Check if response should be HTML format.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
request: Django request object
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
Boolean indicating if HTML format is requested
|
|
78
|
+
"""
|
|
79
|
+
return (
|
|
80
|
+
request.GET.get("html", "false").lower() == "true"
|
|
81
|
+
or request.headers.get("HX-Request") == "true"
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _location_list_view(request, param_name, data_func, placeholders):
|
|
86
|
+
"""
|
|
87
|
+
Generic view handler for location hierarchy endpoints.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
request: Django request object
|
|
91
|
+
param_name: Name of the primary parameter to extract
|
|
92
|
+
data_func: Function to call to get location data
|
|
93
|
+
placeholders: Tuple of (nepali_placeholder, english_placeholder)
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
JsonResponse or HttpResponse with location data
|
|
97
|
+
"""
|
|
98
|
+
param_value = _get_primary_param(request, param_name)
|
|
99
|
+
|
|
100
|
+
if not param_value:
|
|
12
101
|
return JsonResponse([], safe=False)
|
|
13
|
-
|
|
102
|
+
|
|
103
|
+
ne, en = _parse_language_params(request)
|
|
104
|
+
as_html = _should_return_html(request)
|
|
105
|
+
|
|
106
|
+
data = data_func(param_value, ne=ne, en=en)
|
|
107
|
+
|
|
108
|
+
if as_html:
|
|
109
|
+
placeholder = placeholders[0] if ne else placeholders[1]
|
|
110
|
+
return _render_options(data, placeholder)
|
|
111
|
+
|
|
14
112
|
return JsonResponse(data, safe=False)
|
|
15
113
|
|
|
16
114
|
|
|
115
|
+
def district_list_view(request):
|
|
116
|
+
"""Return list of districts for a given province."""
|
|
117
|
+
from django_nepkit.constants import PLACEHOLDERS
|
|
118
|
+
|
|
119
|
+
return _location_list_view(
|
|
120
|
+
request,
|
|
121
|
+
param_name="province",
|
|
122
|
+
data_func=get_districts_by_province,
|
|
123
|
+
placeholders=(PLACEHOLDERS["district"]["ne"], PLACEHOLDERS["district"]["en"]),
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
|
|
17
127
|
def municipality_list_view(request):
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
128
|
+
"""Return list of municipalities for a given district."""
|
|
129
|
+
from django_nepkit.constants import PLACEHOLDERS
|
|
130
|
+
|
|
131
|
+
return _location_list_view(
|
|
132
|
+
request,
|
|
133
|
+
param_name="district",
|
|
134
|
+
data_func=get_municipalities_by_district,
|
|
135
|
+
placeholders=(
|
|
136
|
+
PLACEHOLDERS["municipality"]["ne"],
|
|
137
|
+
PLACEHOLDERS["municipality"]["en"],
|
|
138
|
+
),
|
|
139
|
+
)
|