feat(model): added administrative locations (provinces, districts, municipalities) field with full localization support
This commit is contained in:
6 files changed
+217
No files matched your search
@@ -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",
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
})();
|
||||
@@ -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"),
|
||||
]
|
||||
@@ -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]
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
Reference in new issue
Block a user