django-nepkit 0.2.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.
@@ -0,0 +1,129 @@
1
+ """
2
+ Constants for django-nepkit package.
3
+ Contains hardcoded values, word mappings, and configuration data.
4
+ """
5
+
6
+ # Nepali number words mapping (0-99)
7
+ NEPALI_ONES = [
8
+ "",
9
+ "एक",
10
+ "दुई",
11
+ "तीन",
12
+ "चार",
13
+ "पाँच",
14
+ "छ",
15
+ "सात",
16
+ "आठ",
17
+ "नौ",
18
+ "दश",
19
+ "एघार",
20
+ "बाह्र",
21
+ "तेह्र",
22
+ "चौध",
23
+ "पन्ध्र",
24
+ "सोह्र",
25
+ "सत्र",
26
+ "अठार",
27
+ "उन्नाइस",
28
+ "बीस",
29
+ "एकाइस",
30
+ "बाइस",
31
+ "तेईस",
32
+ "चौबीस",
33
+ "पच्चीस",
34
+ "छब्बीस",
35
+ "सत्ताइस",
36
+ "अठाइस",
37
+ "उनन्तीस",
38
+ "तीस",
39
+ "एकतीस",
40
+ "बत्तीस",
41
+ "तेत्तीस",
42
+ "चौंतीस",
43
+ "पैंतीस",
44
+ "छत्तीस",
45
+ "सैंतीस",
46
+ "अठतीस",
47
+ "उनन्चालीस",
48
+ "चालीस",
49
+ "एकचालीस",
50
+ "बयालीस",
51
+ "त्रिचालीस",
52
+ "चवालीस",
53
+ "पैंतालीस",
54
+ "छयालीस",
55
+ "सत्तालीस",
56
+ "अठचालीस",
57
+ "उनन्पचास",
58
+ "पचास",
59
+ "एकाउन्न",
60
+ "बाउन्न",
61
+ "त्रिपन्न",
62
+ "चउन्न",
63
+ "पचपन्न",
64
+ "छपन्न",
65
+ "सन्ताउन्न",
66
+ "अन्ठाउन्न",
67
+ "उनन्साठी",
68
+ "साठी",
69
+ "एकसाठी",
70
+ "बासट्ठी",
71
+ "त्रिसट्ठी",
72
+ "चौसट्ठी",
73
+ "पैंसट्ठी",
74
+ "छयसट्ठी",
75
+ "सतसट्ठी",
76
+ "अठसट्ठी",
77
+ "उनन्सत्तरी",
78
+ "सत्तरी",
79
+ "एकहत्तर",
80
+ "बाहत्तर",
81
+ "त्रिहत्तर",
82
+ "चौरहत्तर",
83
+ "पचहत्तर",
84
+ "छयहत्तर",
85
+ "सतहत्तर",
86
+ "अठहत्तर",
87
+ "उनन्असी",
88
+ "असी",
89
+ "एकासी",
90
+ "बयासी",
91
+ "त्रियासी",
92
+ "चौरासी",
93
+ "पचासी",
94
+ "छयासी",
95
+ "सतासी",
96
+ "अठासी",
97
+ "उनन्नब्बे",
98
+ "नब्बे",
99
+ "एकानब्बे",
100
+ "बयानब्बे",
101
+ "त्रियानब्बे",
102
+ "चौरानब्बे",
103
+ "पञ्चानब्बे",
104
+ "छ्यानब्बे",
105
+ "सन्तानब्बे",
106
+ "अन्ठानब्बे",
107
+ "उनन्सय",
108
+ ]
109
+
110
+ # Nepali number units and their corresponding names
111
+ NEPALI_UNITS = [
112
+ ("", ""),
113
+ (100, "सय"),
114
+ (1000, "हजार"),
115
+ (100000, "लाख"),
116
+ (10000000, "करोड"),
117
+ (1000000000, "अरब"),
118
+ (100000000000, "खरब"),
119
+ ]
120
+
121
+ # Placeholder text for location selects
122
+ PLACEHOLDERS = {
123
+ "province": {"ne": "प्रदेश छान्नुहोस्", "en": "Select Province"},
124
+ "district": {"ne": "जिल्ला छान्नुहोस्", "en": "Select District"},
125
+ "municipality": {"ne": "नगरपालिका छान्नुहोस्", "en": "Select Municipality"},
126
+ }
127
+
128
+ # Internal parameters to exclude from fallback logic
129
+ INTERNAL_PARAMS = ["ne", "en", "html"]
@@ -0,0 +1,52 @@
1
+ """
2
+ Language parameter utilities for django-nepkit.
3
+ Helper functions to resolve Nepali/English language parameters consistently.
4
+ """
5
+
6
+ from django_nepkit.conf import nepkit_settings
7
+
8
+
9
+ def resolve_language_params(ne=None, en=None, **kwargs):
10
+ """
11
+ Resolve language parameters with fallback to default settings.
12
+ """
13
+ default_lang = nepkit_settings.DEFAULT_LANGUAGE
14
+
15
+ # Extract from kwargs if provided
16
+ if ne is None and "ne" in kwargs:
17
+ ne = kwargs.get("ne")
18
+ if en is None and "en" in kwargs:
19
+ en = kwargs.get("en")
20
+
21
+ # Set defaults
22
+ if ne is None:
23
+ ne = default_lang == "ne"
24
+
25
+ # Handle en parameter logic
26
+ explicit_en = en is not None
27
+ if en is None:
28
+ en = not ne
29
+
30
+ # If ne is True and en wasn't explicitly set, en should be False
31
+ if ne and not explicit_en:
32
+ en = False
33
+
34
+ return ne, en
35
+
36
+
37
+ def pop_language_params(kwargs):
38
+ """
39
+ Pop ne/en parameters from kwargs dict and return resolved values.
40
+ """
41
+ default_lang = nepkit_settings.DEFAULT_LANGUAGE
42
+ ne = kwargs.pop("ne", default_lang == "ne")
43
+
44
+ explicit_en = "en" in kwargs
45
+ en_value = kwargs.pop("en", not ne)
46
+
47
+ if ne and not explicit_en:
48
+ en = False
49
+ else:
50
+ en = en_value
51
+
52
+ return ne, en
django_nepkit/models.py CHANGED
@@ -7,7 +7,6 @@ from django.utils.translation import gettext_lazy as _
7
7
  from nepali.datetime import nepalidate, nepalidatetime
8
8
  from nepali.locations import districts, municipalities, provinces
9
9
 
10
- from django_nepkit.forms import NepaliDateFormField
11
10
  from django_nepkit.utils import (
12
11
  BS_DATE_FORMAT,
13
12
  BS_DATETIME_FORMAT,
@@ -172,6 +171,9 @@ class NepaliDateField(BaseNepaliBSField):
172
171
  parse_func = staticmethod(try_parse_nepali_date)
173
172
 
174
173
  def formfield(self, **kwargs):
174
+ # Import here to avoid circular dependency
175
+ from django_nepkit.forms import NepaliDateFormField
176
+
175
177
  kwargs.setdefault("form_class", NepaliDateFormField)
176
178
  return super().formfield(**kwargs)
177
179
 
@@ -206,13 +208,7 @@ class BaseLocationField(NepaliFieldMixin, models.CharField):
206
208
  return [(self._get_name(item, ne), self._get_name(item, ne)) for item in source]
207
209
 
208
210
  def _get_name(self, item, ne):
209
- name = getattr(item, "name_nepali", item.name) if ne else item.name
210
- # Handle name variations and Koshi Province mapping
211
- if name == "Province 1":
212
- return "Koshi Province"
213
- if name == "प्रदेश नं. १":
214
- return "कोशी प्रदेश"
215
- return name
211
+ return getattr(item, "name_nepali", item.name) if ne else item.name
216
212
 
217
213
  def formfield(self, **kwargs):
218
214
  widget_cls = getattr(self, "widget_class", None)
@@ -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) {
@@ -43,7 +57,45 @@
43
57
  return null;
44
58
  }
45
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
+
46
97
  function init() {
98
+ // Initialize province selects
47
99
  document.querySelectorAll('.nepkit-province-select').forEach(provinceSelect => {
48
100
  const isNepali = provinceSelect.dataset.ne === 'true';
49
101
  const container = provinceSelect.closest('form') || document;
@@ -52,14 +104,15 @@
52
104
 
53
105
  if (!provinceSelect.value) {
54
106
  if (districtSelect && !districtSelect.value) {
55
- updateOptions(districtSelect, [], isNepali ? 'जिल्ला छान्नुहोस्' : 'Select District');
107
+ updateOptions(districtSelect, [], getPlaceholder('district', isNepali));
56
108
  }
57
109
  if (municipalitySelect && !municipalitySelect.value) {
58
- updateOptions(municipalitySelect, [], isNepali ? 'नगरपालिका छान्नुहोस्' : 'Select Municipality');
110
+ updateOptions(municipalitySelect, [], getPlaceholder('municipality', isNepali));
59
111
  }
60
112
  }
61
113
  });
62
114
 
115
+ // Initialize district selects
63
116
  document.querySelectorAll('.nepkit-district-select').forEach(districtSelect => {
64
117
  const isNepali = districtSelect.dataset.ne === 'true';
65
118
  const container = districtSelect.closest('form') || document;
@@ -67,13 +120,14 @@
67
120
 
68
121
  if (!districtSelect.value) {
69
122
  if (municipalitySelect && !municipalitySelect.value) {
70
- updateOptions(municipalitySelect, [], isNepali ? 'नगरपालिका छान्नुहोस्' : 'Select Municipality');
123
+ updateOptions(municipalitySelect, [], getPlaceholder('municipality', isNepali));
71
124
  }
72
125
  }
73
126
  });
74
127
  }
75
128
 
76
129
  document.addEventListener('change', function(e) {
130
+ // Handle province select change
77
131
  if (e.target.matches('.nepkit-province-select')) {
78
132
  const province = e.target.value;
79
133
  const isNepali = e.target.dataset.ne === 'true';
@@ -83,63 +137,22 @@
83
137
 
84
138
  // Always clear municipality if province changes
85
139
  if (municipalitySelect) {
86
- updateOptions(municipalitySelect, [], isNepali ? 'नगरपालिका छान्नुहोस्' : 'Select Municipality');
140
+ updateOptions(municipalitySelect, [], getPlaceholder('municipality', isNepali));
87
141
  }
88
142
 
89
- if (districtSelect) {
90
- if (!province) {
91
- updateOptions(districtSelect, [], isNepali ? 'जिल्ला छान्नुहोस्' : 'Select District');
92
- return;
93
- }
94
-
95
- const localData = getLocalData('districts', province, isNepali);
96
-
97
- if (localData) {
98
- updateOptions(districtSelect, localData, isNepali ? 'जिल्ला छान्नुहोस्' : 'Select District');
99
- } else if (districtSelect.dataset.url) {
100
- // Fallback to AJAX
101
- let url = districtSelect.dataset.url + '?province=' + encodeURIComponent(province);
102
- if (isNepali) url += '&ne=true';
103
- if (districtSelect.dataset.en === 'true') url += '&en=true';
104
-
105
- fetch(url)
106
- .then(response => response.json())
107
- .then(data => {
108
- updateOptions(districtSelect, data, isNepali ? 'जिल्ला छान्नुहोस्' : 'Select District');
109
- });
110
- }
111
- }
143
+ // Update district options
144
+ updateDependentSelect(districtSelect, 'districts', province, 'district', isNepali, 'province');
112
145
  }
113
146
 
147
+ // Handle district select change
114
148
  if (e.target.matches('.nepkit-district-select')) {
115
149
  const district = e.target.value;
116
150
  const isNepali = e.target.dataset.ne === 'true';
117
151
  const container = e.target.closest('form') || document;
118
152
  const municipalitySelect = getMatchingSelect(container, '.nepkit-municipality-select', isNepali);
119
153
 
120
- if (municipalitySelect) {
121
- if (!district) {
122
- updateOptions(municipalitySelect, [], isNepali ? 'नगरपालिका छान्नुहोस्' : 'Select Municipality');
123
- return;
124
- }
125
-
126
- const localData = getLocalData('municipalities', district, isNepali);
127
-
128
- if (localData) {
129
- updateOptions(municipalitySelect, localData, isNepali ? 'नगरपालिका छान्नुहोस्' : 'Select Municipality');
130
- } else if (municipalitySelect.dataset.url) {
131
- // Fallback to AJAX
132
- let url = municipalitySelect.dataset.url + '?district=' + encodeURIComponent(district);
133
- if (isNepali) url += '&ne=true';
134
- if (municipalitySelect.dataset.en === 'true') url += '&en=true';
135
-
136
- fetch(url)
137
- .then(response => response.json())
138
- .then(data => {
139
- updateOptions(municipalitySelect, data, isNepali ? 'नगरपालिका छान्नुहोस्' : 'Select Municipality');
140
- });
141
- }
142
- }
154
+ // Update municipality options
155
+ updateDependentSelect(municipalitySelect, 'municipalities', district, 'municipality', isNepali, 'district');
143
156
  }
144
157
  });
145
158
 
django_nepkit/utils.py CHANGED
@@ -47,16 +47,6 @@ def _get_location_children(parent_list, parent_name, child_attr, ne=False):
47
47
  p_name = p.name
48
48
  p_name_ne = getattr(p, "name_nepali", None)
49
49
 
50
- # Handle province name variations
51
- if parent_name == "Koshi Province":
52
- if p_name == "Province 1":
53
- selected_parent = p
54
- break
55
- elif parent_name == "कोशी प्रदेश":
56
- if p_name_ne == "प्रदेश नं. १":
57
- selected_parent = p
58
- break
59
-
60
50
  if p_name == parent_name or p_name_ne == parent_name:
61
51
  selected_parent = p
62
52
  break
@@ -132,14 +122,11 @@ def number_to_nepali_words(number: Any) -> str:
132
122
  Converts a number to Nepali words.
133
123
  Eg. 123 -> एक सय तेईस
134
124
  """
125
+ from django_nepkit.constants import NEPALI_ONES, NEPALI_UNITS
126
+
135
127
  if number is None:
136
128
  return ""
137
129
 
138
- # Basic implementation for now, can be expanded
139
- # Mapping for numbers to words (simplified)
140
- # This is a complex task for a full implementation,
141
- # but I'll provide a robust enough version for common usage.
142
-
143
130
  try:
144
131
  num = int(float(number))
145
132
  except (ValueError, TypeError):
@@ -148,132 +135,19 @@ def number_to_nepali_words(number: Any) -> str:
148
135
  if num == 0:
149
136
  return "शून्य"
150
137
 
151
- ones = [
152
- "",
153
- "एक",
154
- "दुई",
155
- "तीन",
156
- "चार",
157
- "पाँच",
158
- "छ",
159
- "सात",
160
- "आठ",
161
- "नौ",
162
- "दश",
163
- "एघार",
164
- "बाह्र",
165
- "तेह्र",
166
- "चौध",
167
- "पन्ध्र",
168
- "सोह्र",
169
- "सत्र",
170
- "अठार",
171
- "उन्नाइस",
172
- "बीस",
173
- "एकाइस",
174
- "बाइस",
175
- "तेईस",
176
- "चौबीस",
177
- "पच्चीस",
178
- "छब्बीस",
179
- "सत्ताइस",
180
- "अठाइस",
181
- "उनन्तीस",
182
- "तीस",
183
- "एकतीस",
184
- "बत्तीस",
185
- "तेत्तीस",
186
- "चौंतीस",
187
- "पैंतीस",
188
- "छत्तीस",
189
- "सैंतीस",
190
- "अठतीस",
191
- "उनन्चालीस",
192
- "चालीस",
193
- "एकचालीस",
194
- "बयालीस",
195
- "त्रिचालीस",
196
- "चवालीस",
197
- "पैंतालीस",
198
- "छयालीस",
199
- "सत्तालीस",
200
- "अठचालीस",
201
- "उनन्पचास",
202
- "पचास",
203
- "एकाउन्न",
204
- "बाउन्न",
205
- "त्रिपन्न",
206
- "चउन्न",
207
- "पचपन्न",
208
- "छपन्न",
209
- "सन्ताउन्न",
210
- "अन्ठाउन्न",
211
- "उनन्साठी",
212
- "साठी",
213
- "एकसाठी",
214
- "बासट्ठी",
215
- "त्रिसट्ठी",
216
- "चौसट्ठी",
217
- "पैंसट्ठी",
218
- "छयसट्ठी",
219
- "सतसट्ठी",
220
- "अठसट्ठी",
221
- "उनन्सत्तरी",
222
- "सत्तरी",
223
- "एकहत्तर",
224
- "बाहत्तर",
225
- "त्रिहत्तर",
226
- "चौरहत्तर",
227
- "पचहत्तर",
228
- "छयहत्तर",
229
- "सतहत्तर",
230
- "अठहत्तर",
231
- "उनन्असी",
232
- "असी",
233
- "एकासी",
234
- "बयासी",
235
- "त्रियासी",
236
- "चौरासी",
237
- "पचासी",
238
- "छयासी",
239
- "सतासी",
240
- "अठासी",
241
- "उनन्नब्बे",
242
- "नब्बे",
243
- "एकानब्बे",
244
- "बयानब्बे",
245
- "त्रियानब्बे",
246
- "चौरानब्बे",
247
- "पञ्चानब्बे",
248
- "छ्यानब्बे",
249
- "सन्तानब्बे",
250
- "अन्ठानब्बे",
251
- "उनन्सय",
252
- ]
253
-
254
- units = [
255
- ("", ""),
256
- (100, "सय"),
257
- (1000, "हजार"),
258
- (100000, "लाख"),
259
- (10000000, "करोड"),
260
- (1000000000, "अरब"),
261
- (100000000000, "खरब"),
262
- ]
263
-
264
138
  def _convert(n):
265
139
  if n == 0:
266
140
  return ""
267
141
  if n < 100:
268
- return ones[n]
142
+ return NEPALI_ONES[n]
269
143
 
270
- for i in range(len(units) - 1, 0, -1):
271
- div, unit_name = units[i]
144
+ for i in range(len(NEPALI_UNITS) - 1, 0, -1):
145
+ div, unit_name = NEPALI_UNITS[i]
272
146
  if n >= div:
273
147
  prefix_val = n // div
274
148
  remainder = n % div
275
149
 
276
- # For 'सय' (100), we use ones[prefix_val]
150
+ # For 'सय' (100), we use NEPALI_ONES[prefix_val]
277
151
  # For others, we might need recursive calls if prefix_val >= 100
278
152
  prefix_words = _convert(prefix_val)
279
153
  res = f"{prefix_words} {unit_name}"
@@ -298,96 +172,106 @@ def english_to_nepali_unicode(text: Any) -> str:
298
172
  return english_to_nepali(text)
299
173
 
300
174
 
301
- def normalize_address(address_string: str) -> dict[str, Optional[str]]:
175
+ def _normalize_nepali_text(text):
302
176
  """
303
- Attempts to normalize a Nepali address string into Province, District, and Municipality.
304
- Returns a dictionary with 'province', 'district', and 'municipality'.
177
+ Normalize Nepali text for easier matching.
178
+ Replaces Chandrabindu with Anusvara.
305
179
  """
306
- if not address_string:
307
- return {"province": None, "district": None, "municipality": None}
180
+ if not text:
181
+ return text
182
+ return text.replace("ँ", "ं").replace("ाँ", "ां")
308
183
 
309
- result = {"province": None, "district": None, "municipality": None}
310
184
 
311
- import re
312
-
313
- content = address_string.replace(",", " ").replace("-", " ")
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).
314
188
 
315
- def normalize_nepali(text):
316
- if not text:
317
- return text
318
- # Replace Chandrabindu with Anusvara for easier matching
319
- return text.replace("ँ", "ं").replace("ाँ", "ां")
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
320
194
 
321
- tokens = [t.strip() for t in content.split() if t.strip()]
322
- normalized_tokens = [normalize_nepali(t) for t in tokens]
323
-
324
- # We try to match from most specific to least specific
325
- found_municipality = None
326
- found_district = None
327
- found_province = None
328
-
329
- # Helper for name matching
330
- def matches(name_eng, name_nep, token, normalized_token):
331
- token_lower = token.lower()
332
- name_nep_norm = normalize_nepali(name_nep)
333
-
334
- # Exact matches
335
- if (
336
- token == name_nep
337
- or normalized_token == name_nep_norm
338
- or token_lower == name_eng.lower()
339
- ):
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():
340
213
  return True
341
214
 
342
- # Handle "Province 1" -> "Koshi" mapping
343
- if (name_eng == "Province 1" and "koshi" in token_lower) or (
344
- name_nep_norm == normalize_nepali("प्रदेश नं. १")
345
- and "कोशी" in normalized_token
346
- ):
215
+ # Partial matches for Nepali
216
+ if len(normalized_token) >= 2:
217
+ if normalized_token in name_nep_norm:
347
218
  return True
348
219
 
349
- # Partial matches for English (e.g., "Pokhara" in "Pokhara Metropolitan City")
350
- # Only if token is at least 4 characters to avoid too many false positives
351
- if len(token) >= 4:
352
- if token_lower in name_eng.lower():
353
- return True
220
+ return False
354
221
 
355
- # Partial matches for Nepali
356
- if len(normalized_token) >= 2:
357
- if normalized_token in name_nep_norm:
358
- return True
359
222
 
360
- return False
223
+ def _find_location_in_tokens(location_list, tokens, normalized_tokens):
224
+ """
225
+ Find a location from a list by matching against tokens.
361
226
 
362
- # Check for municipality first
363
- for i, token in enumerate(tokens):
364
- nt = normalized_tokens[i]
365
- for m in municipalities:
366
- if matches(m.name, m.name_nepali, token, nt):
367
- found_municipality = m
368
- break
369
- if found_municipality:
370
- break
227
+ Args:
228
+ location_list: List of location objects to search
229
+ tokens: List of original tokens
230
+ normalized_tokens: List of normalized tokens
371
231
 
372
- # Check for district
232
+ Returns:
233
+ Matching location object or None
234
+ """
373
235
  for i, token in enumerate(tokens):
374
236
  nt = normalized_tokens[i]
375
- for d in districts:
376
- if matches(d.name, d.name_nepali, token, nt):
377
- found_district = d
378
- break
379
- if found_district:
380
- break
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)
381
248
 
382
- # Check for province
383
- for i, token in enumerate(tokens):
384
- nt = normalized_tokens[i]
385
- for p in provinces:
386
- if matches(p.name, p.name_nepali, token, nt):
387
- found_province = p
388
- break
389
- if found_province:
390
- break
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)
391
275
 
392
276
  # Fill in the gaps using hierarchy
393
277
  if found_municipality:
@@ -403,25 +287,15 @@ def normalize_address(address_string: str) -> dict[str, Optional[str]]:
403
287
  found_province = found_district.province
404
288
 
405
289
  if found_province:
406
- # Handle "Province 1" -> "Koshi Province" consistency
407
- name = found_province.name
408
- if name == "Province 1":
409
- name = "Koshi Province"
410
- result["province"] = name
411
-
412
- # Check for Nepali context
413
- is_nepali = any(
414
- re.search(r"[\u0900-\u097F]", t) for t in tokens
415
- ) # Basic check for Devanagari characters
416
- if is_nepali:
290
+ result["province"] = found_province.name
291
+
292
+ # Use Nepali names if input contains Nepali text
293
+ if _is_nepali_text(tokens):
417
294
  if found_municipality:
418
295
  result["municipality"] = found_municipality.name_nepali
419
296
  if found_district:
420
297
  result["district"] = found_district.name_nepali
421
298
  if found_province:
422
- name = found_province.name_nepali
423
- if name == "प्रदेश नं. १":
424
- name = "कोशी प्रदेश"
425
- result["province"] = name
299
+ result["province"] = found_province.name_nepali
426
300
 
427
301
  return result
django_nepkit/views.py CHANGED
@@ -5,7 +5,6 @@ from django_nepkit.utils import (
5
5
  get_municipalities_by_district,
6
6
  )
7
7
 
8
-
9
8
  from django_nepkit.conf import nepkit_settings
10
9
 
11
10
 
@@ -14,22 +13,49 @@ def _render_options(data, placeholder):
14
13
  options = [f'<option value="">{placeholder}</option>']
15
14
  for item in data:
16
15
  options.append(f'<option value="{item["id"]}">{item["text"]}</option>')
17
- return HttpResponse("\n".join(options))
16
+ return HttpResponse("\n".join(options), content_type="text/html")
18
17
 
19
18
 
20
- def district_list_view(request):
21
- province = request.GET.get("province")
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
22
35
 
23
- # Fallback: take the first non-internal parameter as province
24
- if not province:
25
- for key, value in request.GET.items():
26
- if key not in ["ne", "en", "html"] and value:
27
- province = value
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
28
44
  break
29
45
 
30
- if not province:
31
- return JsonResponse([], safe=False)
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
32
55
 
56
+ Returns:
57
+ Tuple of (ne, en) boolean values
58
+ """
33
59
  default_lang = nepkit_settings.DEFAULT_LANGUAGE
34
60
  ne_param = request.GET.get("ne")
35
61
  ne = ne_param.lower() == "true" if ne_param else default_lang == "ne"
@@ -37,49 +63,77 @@ def district_list_view(request):
37
63
  en_param = request.GET.get("en")
38
64
  en = en_param.lower() == "true" if en_param else not ne
39
65
 
40
- as_html = (
41
- request.GET.get("html", "false").lower() == "true"
42
- or request.headers.get("HX-Request") == "true"
43
- )
66
+ return ne, en
44
67
 
45
- data = get_districts_by_province(province, ne=ne, en=en)
46
68
 
47
- if as_html:
48
- placeholder = "जिल्ला छान्नुहोस्" if ne else "Select District"
49
- return _render_options(data, placeholder)
69
+ def _should_return_html(request):
70
+ """
71
+ Check if response should be HTML format.
50
72
 
51
- return JsonResponse(data, safe=False)
73
+ Args:
74
+ request: Django request object
52
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
+ )
53
83
 
54
- def municipality_list_view(request):
55
- district = request.GET.get("district")
56
84
 
57
- # Fallback: take the first non-internal parameter as district
58
- if not district:
59
- for key, value in request.GET.items():
60
- if key not in ["ne", "en", "html"] and value:
61
- district = value
62
- break
85
+ def _location_list_view(request, param_name, data_func, placeholders):
86
+ """
87
+ Generic view handler for location hierarchy endpoints.
63
88
 
64
- if not district:
65
- return JsonResponse([], safe=False)
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)
66
94
 
67
- default_lang = nepkit_settings.DEFAULT_LANGUAGE
68
- ne_param = request.GET.get("ne")
69
- ne = ne_param.lower() == "true" if ne_param else default_lang == "ne"
95
+ Returns:
96
+ JsonResponse or HttpResponse with location data
97
+ """
98
+ param_value = _get_primary_param(request, param_name)
70
99
 
71
- en_param = request.GET.get("en")
72
- en = en_param.lower() == "true" if en_param else not ne
100
+ if not param_value:
101
+ return JsonResponse([], safe=False)
73
102
 
74
- as_html = (
75
- request.GET.get("html", "false").lower() == "true"
76
- or request.headers.get("HX-Request") == "true"
77
- )
103
+ ne, en = _parse_language_params(request)
104
+ as_html = _should_return_html(request)
78
105
 
79
- data = get_municipalities_by_district(district, ne=ne, en=en)
106
+ data = data_func(param_value, ne=ne, en=en)
80
107
 
81
108
  if as_html:
82
- placeholder = "नगरपालिका छान्नुहोस्" if ne else "Select Municipality"
109
+ placeholder = placeholders[0] if ne else placeholders[1]
83
110
  return _render_options(data, placeholder)
84
111
 
85
112
  return JsonResponse(data, safe=False)
113
+
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
+
127
+ def municipality_list_view(request):
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
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-nepkit
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: Django Nepali date, time, phone, and address fields with helpers.
5
5
  Home-page: https://github.com/S4NKALP/django-nepkit
6
6
  Author: Sankalp Tharu
@@ -28,7 +28,7 @@ License-File: LICENSE
28
28
  Requires-Dist: django>=4.2
29
29
  Requires-Dist: django-filter>=25.2
30
30
  Requires-Dist: djangorestframework>=3.16.1
31
- Requires-Dist: nepali>=1.1.3
31
+ Requires-Dist: nepali>=1.2.0
32
32
  Provides-Extra: drf
33
33
  Requires-Dist: djangorestframework>=3.14; extra == "drf"
34
34
  Requires-Dist: django-filter>=23.1; extra == "drf"
@@ -1,21 +1,23 @@
1
1
  django_nepkit/__init__.py,sha256=20cQUKdAjNtQYkAJU0g4OSJa90Ru3M9reNfV0ZgG3vc,1164
2
2
  django_nepkit/admin.py,sha256=xqQHybYb9T4_NYvCqDV3VYt0tbwWQDc8dwTOrkBmj0Q,12714
3
3
  django_nepkit/conf.py,sha256=urr8gwBj2YZgL2Po_C7339YafoQ_PMOWb1MEh-vPdfs,981
4
+ django_nepkit/constants.py,sha256=cVq-ZFkDCBwETU2IDEtAMz2H-oLzSw8TtncRm954Jlg,3507
4
5
  django_nepkit/filters.py,sha256=dw5Yej9cLI-cZxlVHSaNUHq63hnnzhkjqmgLPTvbpd8,3939
5
6
  django_nepkit/forms.py,sha256=yrRE1gzkxGyqrAl0ETcOWnNsTyhPxlS_nlq4ex5a3UY,1675
6
- django_nepkit/models.py,sha256=WnwR-lO5Ec3y0iiFfdE1IB0pY-XWMSeCWPOfpzv6_vU,8601
7
+ django_nepkit/lang_utils.py,sha256=OY0EdWCHL3pVz4rLijCPv1qs8DMVK8ypL4sv8Dw4w3w,1264
8
+ django_nepkit/models.py,sha256=_u1Y0_q9liReodkDHl-kt8D9b_HxP_GwJo2koLRNBWg,8406
7
9
  django_nepkit/serializers.py,sha256=P8dTLOLEaGpc2OuetGXh9LZnbwF1l4VBWQU7y5hwWPA,6487
8
10
  django_nepkit/urls.py,sha256=mPF_xEaOqNrInggBhUbwwMq-FTR7rJ0CfT0fQPx41t8,297
9
- django_nepkit/utils.py,sha256=eTcNvr1A12vraUxtG2KcyfoFEFjzhHAu4gQW123RAtQ,13092
11
+ django_nepkit/utils.py,sha256=md3VpScslatEqtaIHo1_kbwvmlVPO45G_Qoikd_oFsE,9106
10
12
  django_nepkit/validators.py,sha256=HGd4PwXY7vLh8j2r2pnAHM-rfwVWCa4UDfuW79JYxJ8,407
11
- django_nepkit/views.py,sha256=GFdGmYDSp7YwsQ9WMRkauefsKr301s-s6x9oNtsMNnM,2666
13
+ django_nepkit/views.py,sha256=u0Hv343omi51pPF0jOLbwBn9cFkDK-Svt9aXtDrHCxM,3864
12
14
  django_nepkit/widgets.py,sha256=36Q5kkEn4FuFw--CiAOBrp2mzYSaknCmoLpTFFEDgYU,4588
13
15
  django_nepkit/static/django_nepkit/css/admin-nepali-datepicker.css,sha256=g9ago3O0UfoNwRD02AmWSdxiHmXxkQ7mpi94Nn1rO3E,834
14
- django_nepkit/static/django_nepkit/js/address-chaining.js,sha256=xJXWiK377flZ-sQrwBrJNRw7tnzgAzanUFKESUj0Cgc,6816
16
+ django_nepkit/static/django_nepkit/js/address-chaining.js,sha256=-_eSgL38nc2XUvKv-Ns7cOQyTxra_roRb0z1R0sBKN0,6503
15
17
  django_nepkit/static/django_nepkit/js/admin-jquery-bridge.js,sha256=6iqIuSqgYssKtt61WSjye3GBpYHh3AKKYUtGL6Uh3S8,319
16
18
  django_nepkit/static/django_nepkit/js/nepal-data.js,sha256=llFe3CAloAoUU505dnNpKgS9Qhqp0-hmZdLbkBDZkz4,168476
17
19
  django_nepkit/static/django_nepkit/js/nepali-datepicker-init.js,sha256=b1X15_AAyw5DzHHArGjpF9BFA4RM5IVw9CgzAmPAf40,4356
18
- django_nepkit-0.2.0.dist-info/licenses/LICENSE,sha256=76985cvIL0AXuhZd5L2C9tbdxKMZX7IOejjvZI8kXMo,1070
20
+ django_nepkit-0.2.1.dist-info/licenses/LICENSE,sha256=76985cvIL0AXuhZd5L2C9tbdxKMZX7IOejjvZI8kXMo,1070
19
21
  example/manage.py,sha256=wZWUpuXgYEHBxZR-sysETawIaMhr83HQglQTeZdadpQ,752
20
22
  example/demo/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
23
  example/demo/admin.py,sha256=pNlA7_GlDpNQRIb7VoTEO1fa0JD-EwLA6VK72aQR504,1635
@@ -29,7 +31,7 @@ example/example_project/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG
29
31
  example/example_project/settings.py,sha256=nq5lGLEY1cl99XhfS60BpIVfhVFzUhHb_qfNPm5TqSY,2334
30
32
  example/example_project/urls.py,sha256=AK8SdvMN2K0QvdzhMr5BUMzl0SrEUhVmMP-3uLEqnU4,217
31
33
  example/example_project/wsgi.py,sha256=OK4x37NxQNVNWV4ynLqWP546tR4eALIFn7pyHc8XDNw,176
32
- django_nepkit-0.2.0.dist-info/METADATA,sha256=KDvtVL49V9y1zuTx-C3YRyDn6mJE1DnzRjOLtSdrgK0,11008
33
- django_nepkit-0.2.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
34
- django_nepkit-0.2.0.dist-info/top_level.txt,sha256=Rs68WqPeyCvsWQStPNLl6vu10wCIfrFrc7xhkRcFzss,22
35
- django_nepkit-0.2.0.dist-info/RECORD,,
34
+ django_nepkit-0.2.1.dist-info/METADATA,sha256=4FIFav_TWnqaPd-AY5Zsp3lZ0uI1b0ZFZIishqIrsqM,11008
35
+ django_nepkit-0.2.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
36
+ django_nepkit-0.2.1.dist-info/top_level.txt,sha256=Rs68WqPeyCvsWQStPNLl6vu10wCIfrFrc7xhkRcFzss,22
37
+ django_nepkit-0.2.1.dist-info/RECORD,,