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.
Files changed (39) hide show
  1. django_nepkit/__init__.py +20 -0
  2. django_nepkit/admin.py +243 -93
  3. django_nepkit/conf.py +34 -0
  4. django_nepkit/constants.py +129 -0
  5. django_nepkit/filters.py +113 -0
  6. django_nepkit/forms.py +9 -9
  7. django_nepkit/lang_utils.py +52 -0
  8. django_nepkit/models.py +138 -161
  9. django_nepkit/serializers.py +127 -28
  10. django_nepkit/static/django_nepkit/js/address-chaining.js +129 -29
  11. django_nepkit/static/django_nepkit/js/nepal-data.js +1 -0
  12. django_nepkit/static/django_nepkit/js/nepali-datepicker-init.js +55 -46
  13. django_nepkit/utils.py +270 -46
  14. django_nepkit/validators.py +1 -1
  15. django_nepkit/views.py +127 -10
  16. django_nepkit/widgets.py +100 -31
  17. django_nepkit-0.2.1.dist-info/METADATA +308 -0
  18. django_nepkit-0.2.1.dist-info/RECORD +37 -0
  19. example/demo/admin.py +45 -21
  20. example/demo/models.py +41 -4
  21. example/demo/serializers.py +39 -0
  22. example/demo/urls.py +13 -2
  23. example/demo/views.py +125 -3
  24. example/example_project/settings.py +10 -0
  25. example/example_project/urls.py +1 -1
  26. example/manage.py +2 -2
  27. django_nepkit/templatetags/__init__.py +0 -0
  28. django_nepkit/templatetags/nepali.py +0 -74
  29. django_nepkit-0.1.0.dist-info/METADATA +0 -377
  30. django_nepkit-0.1.0.dist-info/RECORD +0 -39
  31. example/demo/migrations/0001_initial.py +0 -2113
  32. example/demo/migrations/0002_alter_person_phone_number.py +0 -18
  33. example/demo/migrations/0003_person_created_at_person_updated_at.py +0 -27
  34. example/demo/migrations/0004_alter_person_created_at_alter_person_updated_at.py +0 -23
  35. example/demo/migrations/0005_alter_person_created_at_alter_person_updated_at.py +0 -27
  36. example/demo/migrations/__init__.py +0 -0
  37. {django_nepkit-0.1.0.dist-info → django_nepkit-0.2.1.dist-info}/WHEEL +0 -0
  38. {django_nepkit-0.1.0.dist-info → django_nepkit-0.2.1.dist-info}/licenses/LICENSE +0 -0
  39. {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
- dateFormat: 'YYYY-MM-DD'
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
- // Ensure theme is applied when the picker is actually shown.
56
- // (The plugin often creates/inserts DOM on focus.)
57
- var applySoon = function () {
58
- // Run twice: once immediately, once after paint.
59
- applyThemeToDatepickerContainers();
60
- window.setTimeout(applyThemeToDatepickerContainers, 0);
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
- el.addEventListener('focus', applySoon);
64
- el.addEventListener('click', applySoon);
65
- }
88
+ el.addEventListener('focus', applySoon);
89
+ el.addEventListener('click', applySoon);
66
90
  });
67
91
  }
68
92
 
69
- // Initialize on page load
70
- if (document.readyState === 'loading') {
71
- document.addEventListener('DOMContentLoaded', initNepaliDatePickers);
72
- } else {
73
- initNepaliDatePickers();
74
- }
75
-
76
- // Keep in sync if system theme changes (fallback path)
77
- if (window.matchMedia) {
78
- try {
79
- var mql = window.matchMedia('(prefers-color-scheme: dark)');
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
- // Re-initialize when Django admin adds inlines dynamically
91
- if (typeof django !== 'undefined' && django.jQuery) {
92
- django.jQuery(document).on('formset:added', function () {
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
- applyThemeToDatepickerContainers();
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 = "%Y-%m-%d"
9
- BS_DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S"
10
+ BS_DATE_FORMAT = nepkit_settings.BS_DATE_FORMAT
11
+ BS_DATETIME_FORMAT = nepkit_settings.BS_DATETIME_FORMAT
10
12
 
11
13
 
12
- def try_parse_nepali_date(value: Any) -> Optional[nepalidate]:
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, nepalidate):
18
+ if isinstance(value, cls):
25
19
  return value
26
20
  if isinstance(value, str):
27
- try:
28
- return nepalidate.strptime(value.strip(), BS_DATE_FORMAT)
29
- except Exception:
30
- return None
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
- Best-effort conversion to `nepalidatetime`.
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
- Callers decide whether to raise, fallback, etc.
120
+ def number_to_nepali_words(number: Any) -> str:
43
121
  """
44
- if value in (None, ""):
45
- return None
46
- if isinstance(value, nepalidatetime):
47
- return value
48
- if isinstance(value, str):
49
- try:
50
- return nepalidatetime.strptime(value.strip(), BS_DATETIME_FORMAT)
51
- except Exception:
52
- return None
53
- return None
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
- def get_districts_by_province(province_name):
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
- Returns a list of districts in the given province.
164
+ Converts English text/numbers to Nepali Unicode.
165
+ Currently focuses on numbers.
59
166
  """
60
- from nepali.locations import provinces
167
+ from nepali.number import english_to_nepali
61
168
 
62
- selected_province = next((p for p in provinces if p.name == province_name), None)
63
- if not selected_province:
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
- def get_municipalities_by_district(district_name):
174
+
175
+ def _normalize_nepali_text(text):
69
176
  """
70
- Returns a list of municipalities in the given district.
177
+ Normalize Nepali text for easier matching.
178
+ Replaces Chandrabindu with Anusvara.
71
179
  """
72
- from nepali.locations import districts
180
+ if not text:
181
+ return text
182
+ return text.replace("ँ", "ं").replace("ाँ", "ां")
73
183
 
74
- selected_district = next((d for d in districts if d.name == district_name), None)
75
- if not selected_district:
76
- return []
77
- return [{"id": m.name, "text": m.name} for m in selected_district.municipalities]
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
@@ -3,7 +3,7 @@ from django.utils.translation import gettext_lazy as _
3
3
  from nepali import phone_number
4
4
 
5
5
 
6
- # validates the given value is a valid nepali phone number
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
- def district_list_view(request):
10
- province = request.GET.get("province")
11
- if not province:
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
- data = get_districts_by_province(province)
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
- district = request.GET.get("district")
19
- if not district:
20
- return JsonResponse([], safe=False)
21
- data = get_municipalities_by_district(district)
22
- return JsonResponse(data, safe=False)
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
+ )