feat(model): added administrative locations (provinces, districts, municipalities) field with full localization support

This commit is contained in:
sankalp committed 2026-01-28 23:51:29 +05:45
1 parent 1e3e3f58bc
commit 3cb7eccf24
6 files changed
+217

No files matched your search

+48
View File
@@ -1,7 +1,13 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
from nepali.locations import districts, municipalities, provices
from django_nepkit.validators import validate_nepali_phone_number
from django_nepkit.widgets import (
DistrictSelectWidget,
MunicipalitySelectWidget,
ProvinceSelectWidget,
)
class NepaliPhoneNumberField(models.CharField):
@@ -11,3 +17,45 @@ class NepaliPhoneNumberField(models.CharField):
kwargs.setdefault("max_length", 10)
super().__init__(*args, **kwargs)
self.validators.append(validate_nepali_phone_number)
class ProvinceField(models.CharField):
description = _("Nepal's Province")
def __init__(self, *args, **kwargs):
kwargs.setdefault("max_length", 100)
kwargs.setdefault("choices", [(p.name, p.name) for p in provices])
super().__init__(*args, **kwargs)
def formfield(self, **kwargs):
defaults = {"widget": ProvinceSelectWidget}
defaults.update(kwargs)
return super().formfield(**defaults)
class DistrictField(models.CharField):
description = _("Nepal's District")
def __init__(self, *args, **kwargs):
kwargs.setdefault("max_length", 100)
kwargs.setdefault("choices", [(d.name, d.name) for d in districts])
super().__init__(*args, **kwargs)
def formfield(self, **kwargs):
defaults = {"widget": DistrictSelectWidget}
defaults.update(kwargs)
return super().formfield(**defaults)
class MunicipalityField(models.CharField):
description = _("Nepal's Municipality")
def __init__(self, *args, **kwargs):
kwargs.setdefault("max_length", 100)
kwargs.setdefault("choices", [(m.name, m.name) for m in municipalities])
super().__init__(*args, **kwargs)
def formfield(self, **kwargs):
defaults = {"widget": MunicipalitySelectWidget}
defaults.update(kwargs)
return super().formfield(**defaults)
@@ -0,0 +1,89 @@
(function() {
"use strict";
function updateOptions(selectElement, data, placeholder) {
selectElement.innerHTML = "";
if (placeholder) {
const opt = document.createElement("option");
opt.value = "";
opt.textContent = placeholder;
selectElement.appendChild(opt);
}
data.forEach((item) => {
const opt = document.createElement("option");
opt.value = item.id;
opt.textContent = item.text;
selectElement.appendChild(opt);
});
selectElement.dispatchEvent(new Event("change"));
}
document.addEventListener("change", function(e) {
if (e.target.matches(".nepkit-province-select")) {
const province = e.target.value;
const container = e.target.closest("form") || document;
const districtSelect = container.querySelector(
".nepkit-district-select",
);
const municipalitySelect = container.querySelector(
".nepkit-municipality-select",
);
if (districtSelect) {
if (!province) {
updateOptions(districtSelect, [], "Select District");
if (municipalitySelect)
updateOptions(
municipalitySelect,
[],
"Select Municipality",
);
return;
}
const url =
districtSelect.dataset.url +
"?province=" +
encodeURIComponent(province);
fetch(url)
.then((response) => response.json())
.then((data) => {
updateOptions(districtSelect, data, "Select District");
});
}
}
if (e.target.matches(".nepkit-district-select")) {
const district = e.target.value;
const container = e.target.closest("form") || document;
const municipalitySelect = container.querySelector(
".nepkit-municipality-select",
);
if (municipalitySelect) {
if (!district) {
updateOptions(
municipalitySelect,
[],
"Select Municipality",
);
return;
}
const url =
municipalitySelect.dataset.url +
"?district=" +
encodeURIComponent(district);
fetch(url)
.then((response) => response.json())
.then((data) => {
updateOptions(
municipalitySelect,
data,
"Select Municipality",
);
});
}
}
});
})();
+10
View File
@@ -0,0 +1,10 @@
from django.urls import path
from django_nepkit.views import district_list_view, municipality_list_view
app_name = "django_nepkit"
urlpatterns = [
path("districts/", district_list_view, name="district-list"),
path("municipalities/", municipality_list_view, name="municipality-list"),
]
+15
View File
@@ -0,0 +1,15 @@
from nepali.locations import districts, provices
def get_districts_by_province(provice_name):
selected_province = next((p for p in provices if p.name == provice_name), None)
if not selected_province:
return []
return [{"id": d.name, "text": d.name} for d in selected_province.districts]
def get_municipalities_by_district(district_name):
selected_district = next((d for d in districts if d.name == district_name), None)
if not selected_district:
return []
return [{"id": m.name, "text": m.name} for m in selected_district.municipalities]
+22
View File
@@ -0,0 +1,22 @@
from django.http import JsonResponse
from django_nepkit.utils import (
get_districts_by_province,
get_municipalities_by_district,
)
def district_list_view(request):
province = request.GET.get("province")
if not province:
return JsonResponse([], safe=False)
data = get_districts_by_province(province)
return JsonResponse(data, safe=False)
def municipality_list_view(request):
district = request.GET.get("district")
if not district:
return JsonResponse([], safe=False)
data = get_municipalities_by_district(district)
return JsonResponse(data, safe=False)
+33
View File
@@ -0,0 +1,33 @@
from django import forms
from django.urls import reverse_lazy
class ChainedSelectWidget(forms.Select):
class Media:
js = ("django_nepkit/js/address-chaining.js",)
class ProvinceSelectWidget(ChainedSelectWidget):
def __init__(self, *args, **kwargs):
attrs = kwargs.get("attrs", {})
attrs["class"] = attrs.get("class", "") + " nepkit-province-select"
kwargs["attrs"] = attrs
super().__init__(*args, **kwargs)
class DistrictSelectWidget(ChainedSelectWidget):
def __init__(self, *args, **kwargs):
attrs = kwargs.get("attrs", {})
attrs["class"] = attrs.get("class", "") + " nepkit-district-select"
attrs["data-url"] = reverse_lazy("django_nepkit:district-list")
kwargs["attrs"] = attrs
super().__init__(*args, **kwargs)
class MunicipalitySelectWidget(ChainedSelectWidget):
def __init__(self, *args, **kwargs):
attrs = kwargs.get("attrs", {})
attrs["class"] = attrs.get("class", "") + " nepkit-municipality-select"
attrs["data-url"] = reverse_lazy("django_nepkit:municipality-list")
kwargs["attrs"] = attrs
super().__init__(*args, **kwargs)