chore(root): remove DRF and django-filter dependencies

drop DRF and django-filter to make django_nepkit framework‑agnostic, simplify dependencies, and align with v0.2.2 breaking change.
This commit is contained in:
sankalp committed 2026-06-06 21:06:03 +05:45
1 parent 38c426c5be
commit 0268bcfe98
40 files changed
+3286 -1406

No files matched your search

-39
View File
@@ -1,39 +0,0 @@
from rest_framework import serializers
from django_nepkit.serializers import (
NepaliDateSerializerField,
NepaliCurrencySerializerField,
NepaliLocalizedSerializerMixin,
)
from .models import Person, Citizen, AuditedPerson, Transaction
class PersonSerializer(NepaliLocalizedSerializerMixin, serializers.ModelSerializer):
birth_date = NepaliDateSerializerField()
class Meta:
model = Person
fields = "__all__"
class TransactionSerializer(
NepaliLocalizedSerializerMixin, serializers.ModelSerializer
):
amount_formatted = NepaliCurrencySerializerField(source="amount")
class Meta:
model = Transaction
fields = ["id", "title", "amount", "amount_formatted", "date"]
class CitizenSerializer(serializers.ModelSerializer):
class Meta:
model = Citizen
fields = "__all__"
class AuditedPersonSerializer(serializers.ModelSerializer):
birth_date = NepaliDateSerializerField()
class Meta:
model = AuditedPerson
fields = "__all__"
+8 -11
View File
@@ -1,20 +1,17 @@
from . import views
from rest_framework.routers import DefaultRouter
from django.urls import path
app_name = "demo"
from . import views
router = DefaultRouter()
router.register("api/persons", views.PersonViewSet, basename="person-api")
router.register("api/citizens", views.CitizenViewSet, basename="citizen-api")
router.register("api/audited", views.AuditedPersonViewSet, basename="audited-api")
router.register(
"api/transactions", views.TransactionViewSet, basename="transaction-api"
)
app_name = "demo"
urlpatterns = [
path("", views.person_list, name="person-list"),
path("add/", views.person_create, name="person-create"),
path("transactions/add/", views.transaction_create, name="transaction-create"),
path("normalize/", views.address_normalize_demo, name="address-normalize"),
] + router.urls
# JSON API (plain Django — no DRF, no django-filter)
path("api/persons.json", views.person_api, name="person-api"),
path("api/citizens.json", views.citizen_api, name="citizen-api"),
path("api/audited.json", views.audited_api, name="audited-api"),
path("api/transactions.json", views.transaction_api, name="transaction-api"),
]
+396 -90
View File
@@ -1,21 +1,39 @@
"""
Plain Django views for the demo.
The form-based views (``person_list`` / ``person_create`` / ``transaction_create``
/ ``address_normalize_demo``) use Django's built-in machinery. The JSON
endpoints under ``/api/`` use the framework-agnostic helpers in
``django_nepkit.api`` so this demo doesn't need DRF or django-filter.
"""
from __future__ import annotations
from decimal import Decimal, InvalidOperation
from typing import Any
from django import forms
from django.shortcuts import render, redirect
from rest_framework import viewsets, filters as drf_filters
from django_filters import rest_framework as django_filters
from django_nepkit.filters import NepaliDateYearFilter, NepaliDateMonthFilter
from .models import Person, Citizen, AuditedPerson, Transaction
from .serializers import (
PersonSerializer,
CitizenSerializer,
AuditedPersonSerializer,
TransactionSerializer,
)
from django_nepkit.filters import (
NepaliCurrencyRangeFilter,
NepaliDateRangeFilter,
)
from django.core.paginator import Paginator
from django.db.models import Q
from django.http import HttpRequest, JsonResponse
from django.shortcuts import redirect, render
from django.utils.translation import gettext as _
from django.views.decorators.http import require_GET
from django_nepkit import api as nepkit_api
from django_nepkit.conf import nepkit_settings
from django_nepkit.utils import normalize_address
from .models import AuditedPerson, Citizen, Person, Transaction
_TRUE_VALUES = {"true", "1", "yes", "on"}
# --------------------------------------------------------------------------- #
# Form-based views
# --------------------------------------------------------------------------- #
class PersonForm(forms.ModelForm):
class Meta:
@@ -34,7 +52,13 @@ class PersonForm(forms.ModelForm):
]
def person_list(request):
class TransactionForm(forms.ModelForm):
class Meta:
model = Transaction
fields = ["title", "amount"]
def person_list(request: HttpRequest):
persons = Person.objects.all()
transactions = Transaction.objects.all()
return render(
@@ -44,7 +68,7 @@ def person_list(request):
)
def person_create(request):
def person_create(request: HttpRequest):
if request.method == "POST":
form = PersonForm(request.POST)
if form.is_valid():
@@ -55,13 +79,7 @@ def person_create(request):
return render(request, "demo/person_form.html", {"form": form})
class TransactionForm(forms.ModelForm):
class Meta:
model = Transaction
fields = ["title", "amount"]
def transaction_create(request):
def transaction_create(request: HttpRequest):
if request.method == "POST":
form = TransactionForm(request.POST)
if form.is_valid():
@@ -74,71 +92,7 @@ def transaction_create(request):
)
# --- API Support (DRF) ---
class PersonViewSet(viewsets.ModelViewSet):
queryset = Person.objects.all()
serializer_class = PersonSerializer
# Sort and Search work automatically with BS date strings
filter_backends = [
django_filters.DjangoFilterBackend,
drf_filters.SearchFilter,
drf_filters.OrderingFilter,
]
ordering_fields = ["birth_date", "created_at"]
search_fields = ["name", "birth_date", "phone_number"]
# Simple exact date filter
filterset_fields = {
"birth_date": ["exact"],
}
class PersonFilter(django_filters.FilterSet):
year = NepaliDateYearFilter(field_name="birth_date")
month = NepaliDateMonthFilter(field_name="birth_date")
class Meta:
model = Person
fields = ["province", "district"]
filterset_class = PersonFilter
class CitizenViewSet(viewsets.ModelViewSet):
queryset = Citizen.objects.all()
serializer_class = CitizenSerializer
filter_backends = [drf_filters.OrderingFilter]
ordering_fields = ["province", "district"]
class AuditedPersonViewSet(viewsets.ModelViewSet):
queryset = AuditedPerson.objects.all()
serializer_class = AuditedPersonSerializer
filter_backends = [drf_filters.SearchFilter, drf_filters.OrderingFilter]
search_fields = ["name", "birth_date"]
ordering_fields = ["created_at"]
class TransactionViewSet(viewsets.ModelViewSet):
queryset = Transaction.objects.all()
serializer_class = TransactionSerializer
filter_backends = [django_filters.DjangoFilterBackend]
class TransactionFilter(django_filters.FilterSet):
amount_range = NepaliCurrencyRangeFilter(field_name="amount")
date_range = NepaliDateRangeFilter(field_name="date")
class Meta:
model = Transaction
fields = ["title", "amount", "date"]
filterset_class = TransactionFilter
def address_normalize_demo(request):
def address_normalize_demo(request: HttpRequest):
address = request.GET.get("address", "")
result = None
if address:
@@ -150,5 +104,357 @@ def address_normalize_demo(request):
)
return render(
request, "demo/address_normalize.html", {"address": address, "result": result}
request,
"demo/address_normalize.html",
{"address": address, "result": result},
)
# --------------------------------------------------------------------------- #
# Plain-Django JSON API (no DRF, no django-filter)
# --------------------------------------------------------------------------- #
def _bad_request(message: str, **extra) -> JsonResponse:
payload = {"error": message}
payload.update(extra)
return JsonResponse(payload, status=400)
def _ne_flag(request: HttpRequest) -> bool:
"""Resolve the ``?ne=true`` flag for JSON endpoints.
Falls back to the ``Accept-Language`` header before defaulting to the
project setting so that consumers that send ``ne`` only on some
requests still get a sensible answer.
"""
raw = request.GET.get("ne")
if raw is not None:
return raw.lower() in _TRUE_VALUES
accept = (request.headers.get("Accept-Language") or "").lower()
if "ne" in accept:
return True
return nepkit_settings.DEFAULT_LANGUAGE == "ne"
def _strict_flag(request: HttpRequest) -> bool:
"""Resolve the ``?strict=1`` flag for JSON endpoints.
When True, the demo's ``serialize_*`` calls raise on unparseable
values instead of falling back to ``str(value)`` — useful for
smoke-testing the strict paths the API exposes.
"""
raw = request.GET.get("strict")
if raw is None:
return False
return raw.lower() in _TRUE_VALUES
def _paginate(request: HttpRequest, queryset, per_page: int = 25):
try:
page_num = max(1, int(request.GET.get("page", 1)))
except (TypeError, ValueError):
page_num = 1
paginator = Paginator(queryset, per_page)
page = paginator.get_page(page_num)
return page.object_list, {
"page": page.number,
"pages": paginator.num_pages,
"per_page": per_page,
"total": paginator.count,
}
def _search(queryset, search_fields: list[str], term: str | None):
if not term:
return queryset
q = Q()
for field in search_fields:
q |= Q(**{f"{field}__icontains": term})
return queryset.filter(q)
def _apply_ordering(queryset, ordering_fields: list[str], order_param: str | None):
"""Apply ``?order=`` whitelist.
Returns ``(queryset, error)``: the queryset is unchanged on error, and
``error`` is an :class:`HttpResponseBadRequest` if the value is
malformed.
"""
if not order_param:
return queryset, None
field = order_param.lstrip("-")
if field not in ordering_fields:
return (
queryset,
_bad_request(
_("Invalid order field: %(field)s"),
field=field,
allowed=sorted(ordering_fields),
),
)
return queryset.order_by(order_param), None
def _year_month_filter(queryset, field_name: str, year: str | None, month: str | None):
"""Year/month substring filter, mirroring the default ``BS_DATE_FORMAT``."""
fmt = nepkit_settings.BS_DATE_FORMAT
if year:
try:
year_int = int(year)
except (TypeError, ValueError):
return queryset, _bad_request(_("Invalid year: %(value)s"), value=year)
if fmt.startswith("%Y"):
sep = fmt[2] if len(fmt) > 2 and not fmt[2].startswith("%") else "-"
queryset = queryset.filter(
**{f"{field_name}__startswith": f"{year_int}{sep}"}
)
if month:
try:
month_int = int(month)
except (TypeError, ValueError):
return queryset, _bad_request(_("Invalid month: %(value)s"), value=month)
if not 1 <= month_int <= 12:
return queryset, _bad_request(
_("Month out of range: %(value)s"), value=month
)
month_str = f"{month_int:02d}"
if fmt == "%Y-%m-%d":
queryset = queryset.filter(**{f"{field_name}__contains": f"-{month_str}-"})
else:
sep = fmt[2] if len(fmt) > 2 and not fmt[2].startswith("%") else "-"
queryset = queryset.filter(
**{f"{field_name}__contains": f"{sep}{month_str}{sep}"}
)
return queryset, None
def _range_filter(queryset, field_name: str, raw: str | None, field_type: str = "auto"):
"""Filter by ``"min,max"`` / ``"min,"`` / ``",max"`` / ``"value"``.
``field_type`` is one of ``"auto"``, ``"decimal"``, ``"date"`` or
``"integer"`` and is used to validate user input before it reaches
the ORM (so a stray string doesn't 500 the request).
"""
if not raw:
return queryset, None
parts = [p.strip() for p in raw.split(",")]
if len(parts) > 2:
return queryset, _bad_request(
_("Range must be one or two comma-separated values: %(value)s"),
value=raw,
)
def _validate(value: str):
if not value:
return None
if field_type == "decimal":
try:
return Decimal(value)
except (InvalidOperation, ValueError):
raise ValueError(value)
if field_type == "integer":
try:
return int(value)
except (TypeError, ValueError):
raise ValueError(value)
if field_type == "date":
# Will be re-validated by the ORM; let it pass.
return value
# Auto: try decimal first, fall back to string.
try:
return Decimal(value)
except (InvalidOperation, ValueError):
return value
try:
if len(parts) == 1:
v = _validate(parts[0])
if v is None:
return queryset, None
return queryset.filter(**{field_name: v}), None
lo, hi = parts
lo_v = _validate(lo) if lo else None
hi_v = _validate(hi) if hi else None
if lo_v is not None and hi_v is not None:
return queryset.filter(**{f"{field_name}__range": (lo_v, hi_v)}), None
if lo_v is not None:
return queryset.filter(**{f"{field_name}__gte": lo_v}), None
if hi_v is not None:
return queryset.filter(**{f"{field_name}__lte": hi_v}), None
return queryset, None
except ValueError as exc:
return queryset, _bad_request(
_("Invalid range value: %(value)s") % {"value": str(exc)}
)
# ---- Person -----------------------------------------------------------------
@require_GET
def person_api(request: HttpRequest):
qs = Person.objects.all()
qs = _search(qs, ["name", "birth_date", "phone_number"], request.GET.get("q"))
qs, err = _apply_ordering(
qs, ["name", "birth_date", "created_at"], request.GET.get("order")
)
if err is not None:
return err
birth_date = request.GET.get("birth_date")
if birth_date:
qs = qs.filter(birth_date=birth_date)
qs, err = _year_month_filter(
qs, "birth_date", request.GET.get("year"), request.GET.get("month")
)
if err is not None:
return err
if request.GET.get("province"):
qs = qs.filter(province=request.GET["province"])
if request.GET.get("district"):
qs = qs.filter(district=request.GET["district"])
items, meta = _paginate(request, qs)
ne = _ne_flag(request)
strict = _strict_flag(request)
payload = [_person_to_payload(p, ne=ne, strict=strict) for p in items]
return JsonResponse({"results": payload, "meta": meta})
def _person_to_payload(
person: Person, *, ne: bool, strict: bool = False
) -> dict[str, Any]:
return nepkit_api.build_localized_payload(
{
"id": person.id,
"name": person.name,
"birth_date": nepkit_api.serialize_nepali_date(
person.birth_date, ne=ne, strict=strict
),
"phone_number": person.phone_number,
"province": person.province,
"district": person.district,
"municipality": person.municipality,
"created_at": nepkit_api.serialize_nepali_datetime(
person.created_at, ne=ne, strict=strict
),
},
ne=ne,
)
# ---- Citizen ----------------------------------------------------------------
@require_GET
def citizen_api(request: HttpRequest):
qs = Citizen.objects.all()
qs, err = _apply_ordering(
qs, ["name", "province", "district"], request.GET.get("order")
)
if err is not None:
return err
items, meta = _paginate(request, qs)
ne = _ne_flag(request)
return JsonResponse(
{
"results": [
nepkit_api.build_localized_payload(
{
"id": c.id,
"name": c.name,
"province": c.province,
"district": c.district,
"municipality": c.municipality,
},
ne=ne,
)
for c in items
],
"meta": meta,
}
)
# ---- Audited person ---------------------------------------------------------
@require_GET
def audited_api(request: HttpRequest):
qs = AuditedPerson.objects.all()
qs = _search(qs, ["name", "birth_date"], request.GET.get("q"))
qs, err = _apply_ordering(qs, ["name", "created_at"], request.GET.get("order"))
if err is not None:
return err
items, meta = _paginate(request, qs)
ne = _ne_flag(request)
strict = _strict_flag(request)
return JsonResponse(
{
"results": [
nepkit_api.build_localized_payload(
{
"id": a.id,
"name": a.name,
"birth_date": nepkit_api.serialize_nepali_date(
a.birth_date, ne=ne, strict=strict
),
"created_at": nepkit_api.serialize_nepali_datetime(
a.created_at, ne=ne, strict=strict
),
},
ne=ne,
)
for a in items
],
"meta": meta,
}
)
# ---- Transaction ------------------------------------------------------------
@require_GET
def transaction_api(request: HttpRequest):
qs = Transaction.objects.all()
qs = _search(qs, ["title", "amount", "date"], request.GET.get("q"))
qs, err = _range_filter(
qs, "amount", request.GET.get("amount_range"), field_type="decimal"
)
if err is not None:
return err
qs, err = _range_filter(
qs, "date", request.GET.get("date_range"), field_type="date"
)
if err is not None:
return err
if request.GET.get("title"):
qs = qs.filter(title=request.GET["title"])
items, meta = _paginate(request, qs)
ne = _ne_flag(request)
strict = _strict_flag(request)
return JsonResponse(
{
"results": [
nepkit_api.build_localized_payload(
{
"id": t.id,
"title": t.title,
"amount": str(t.amount),
"amount_formatted": nepkit_api.serialize_nepali_currency(
t.amount, ne=ne
),
"date": nepkit_api.serialize_nepali_date(
t.date, ne=ne, strict=strict
),
},
ne=ne,
)
for t in items
],
"meta": meta,
}
)
-2
View File
@@ -14,8 +14,6 @@ INSTALLED_APPS = [
"django.contrib.messages",
"django.contrib.staticfiles",
"django_nepkit",
"rest_framework",
"django_filters",
"demo",
]
+1 -1
View File
@@ -3,6 +3,6 @@ from django.urls import include, path
urlpatterns = [
path("admin/", admin.site.urls),
# path("nepkit/", include("django_nepkit.urls")),
path("nepkit/", include("django_nepkit.urls")),
path("", include("demo.urls")),
]