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.
- django_nepkit/__init__.py +20 -0
- django_nepkit/admin.py +243 -93
- django_nepkit/conf.py +34 -0
- django_nepkit/constants.py +129 -0
- django_nepkit/filters.py +113 -0
- django_nepkit/forms.py +9 -9
- django_nepkit/lang_utils.py +52 -0
- django_nepkit/models.py +138 -161
- django_nepkit/serializers.py +127 -28
- django_nepkit/static/django_nepkit/js/address-chaining.js +129 -29
- django_nepkit/static/django_nepkit/js/nepal-data.js +1 -0
- django_nepkit/static/django_nepkit/js/nepali-datepicker-init.js +55 -46
- django_nepkit/utils.py +270 -46
- django_nepkit/validators.py +1 -1
- django_nepkit/views.py +127 -10
- django_nepkit/widgets.py +100 -31
- django_nepkit-0.2.1.dist-info/METADATA +308 -0
- django_nepkit-0.2.1.dist-info/RECORD +37 -0
- example/demo/admin.py +45 -21
- example/demo/models.py +41 -4
- example/demo/serializers.py +39 -0
- example/demo/urls.py +13 -2
- example/demo/views.py +125 -3
- example/example_project/settings.py +10 -0
- example/example_project/urls.py +1 -1
- example/manage.py +2 -2
- django_nepkit/templatetags/__init__.py +0 -0
- django_nepkit/templatetags/nepali.py +0 -74
- django_nepkit-0.1.0.dist-info/METADATA +0 -377
- django_nepkit-0.1.0.dist-info/RECORD +0 -39
- example/demo/migrations/0001_initial.py +0 -2113
- example/demo/migrations/0002_alter_person_phone_number.py +0 -18
- example/demo/migrations/0003_person_created_at_person_updated_at.py +0 -27
- example/demo/migrations/0004_alter_person_created_at_alter_person_updated_at.py +0 -23
- example/demo/migrations/0005_alter_person_created_at_alter_person_updated_at.py +0 -27
- example/demo/migrations/__init__.py +0 -0
- {django_nepkit-0.1.0.dist-info → django_nepkit-0.2.1.dist-info}/WHEEL +0 -0
- {django_nepkit-0.1.0.dist-info → django_nepkit-0.2.1.dist-info}/licenses/LICENSE +0 -0
- {django_nepkit-0.1.0.dist-info → django_nepkit-0.2.1.dist-info}/top_level.txt +0 -0
django_nepkit/widgets.py
CHANGED
|
@@ -1,72 +1,141 @@
|
|
|
1
1
|
from django import forms
|
|
2
|
-
from django.urls import
|
|
2
|
+
from django.urls import reverse, NoReverseMatch
|
|
3
|
+
from nepali.datetime import nepalidate
|
|
4
|
+
from django_nepkit.utils import BS_DATE_FORMAT
|
|
5
|
+
|
|
6
|
+
from django_nepkit.conf import nepkit_settings
|
|
3
7
|
|
|
4
8
|
|
|
5
9
|
def _append_css_class(attrs, class_name: str):
|
|
6
|
-
"""
|
|
7
|
-
Django-idiomatic helper to append a CSS class without clobbering existing ones.
|
|
8
|
-
"""
|
|
10
|
+
"""Helper to add a CSS class safely."""
|
|
9
11
|
existing = (attrs.get("class") or "").strip()
|
|
10
12
|
attrs["class"] = (f"{existing} {class_name}").strip() if existing else class_name
|
|
11
13
|
return attrs
|
|
12
14
|
|
|
13
15
|
|
|
14
|
-
class
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
class NepaliWidgetMixin:
|
|
17
|
+
def __init__(self, *args, **kwargs):
|
|
18
|
+
default_lang = nepkit_settings.DEFAULT_LANGUAGE
|
|
19
|
+
self.ne = kwargs.pop("ne", default_lang == "ne")
|
|
17
20
|
|
|
21
|
+
self.en = kwargs.pop("en", not self.ne)
|
|
22
|
+
self.htmx = kwargs.pop("htmx", False)
|
|
23
|
+
|
|
24
|
+
attrs = kwargs.get("attrs", {}) or {}
|
|
25
|
+
|
|
26
|
+
# Language settings
|
|
27
|
+
if self.ne:
|
|
28
|
+
attrs["data-ne"] = "true"
|
|
29
|
+
if self.en:
|
|
30
|
+
attrs["data-en"] = "true"
|
|
31
|
+
|
|
32
|
+
# Pass the date format to JavaScript
|
|
33
|
+
attrs["data-format"] = BS_DATE_FORMAT
|
|
34
|
+
|
|
35
|
+
self._configure_attrs(attrs)
|
|
18
36
|
|
|
19
|
-
class ProvinceSelectWidget(ChainedSelectWidget):
|
|
20
|
-
def __init__(self, *args, **kwargs):
|
|
21
|
-
attrs = kwargs.get("attrs", {})
|
|
22
|
-
_append_css_class(attrs, "nepkit-province-select")
|
|
23
37
|
kwargs["attrs"] = attrs
|
|
24
38
|
super().__init__(*args, **kwargs)
|
|
25
39
|
|
|
40
|
+
def _configure_attrs(self, attrs):
|
|
41
|
+
"""Override in subclasses or use class attributes."""
|
|
42
|
+
css_class = getattr(self, "css_class", None)
|
|
43
|
+
if css_class:
|
|
44
|
+
_append_css_class(attrs, css_class)
|
|
45
|
+
|
|
46
|
+
def get_context(self, name, value, attrs):
|
|
47
|
+
context = super().get_context(name, value, attrs)
|
|
48
|
+
widget_attrs = context.get("widget", {}).get("attrs", {})
|
|
49
|
+
|
|
50
|
+
# Handle URLs for data fetching
|
|
51
|
+
url_name = getattr(self, "_url_name", None)
|
|
52
|
+
if url_name:
|
|
53
|
+
try:
|
|
54
|
+
widget_attrs["data-url"] = reverse(url_name)
|
|
55
|
+
except NoReverseMatch:
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
# Handle HTMX dynamic updates
|
|
59
|
+
if self.htmx:
|
|
60
|
+
hx_url_name = getattr(self, "_hx_url_name", None)
|
|
61
|
+
if hx_url_name:
|
|
62
|
+
try:
|
|
63
|
+
widget_attrs["hx-get"] = reverse(hx_url_name)
|
|
64
|
+
widget_attrs["hx-target"] = getattr(self, "_hx_target", "")
|
|
65
|
+
widget_attrs["hx-trigger"] = "change"
|
|
66
|
+
except NoReverseMatch:
|
|
67
|
+
pass
|
|
68
|
+
|
|
69
|
+
return context
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class ChainedSelectWidget(forms.Select):
|
|
73
|
+
class Media:
|
|
74
|
+
js = (
|
|
75
|
+
"django_nepkit/js/nepal-data.js",
|
|
76
|
+
"django_nepkit/js/address-chaining.js",
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class ProvinceSelectWidget(NepaliWidgetMixin, ChainedSelectWidget):
|
|
81
|
+
css_class = "nepkit-province-select"
|
|
26
82
|
|
|
27
|
-
class DistrictSelectWidget(ChainedSelectWidget):
|
|
28
83
|
def __init__(self, *args, **kwargs):
|
|
29
|
-
attrs = kwargs.get("attrs", {})
|
|
30
|
-
_append_css_class(attrs, "nepkit-district-select")
|
|
31
|
-
attrs["data-url"] = reverse_lazy("django_nepkit:district-list")
|
|
32
|
-
kwargs["attrs"] = attrs
|
|
33
84
|
super().__init__(*args, **kwargs)
|
|
85
|
+
if self.htmx:
|
|
86
|
+
self._hx_url_name = "django_nepkit:district-list"
|
|
87
|
+
self._hx_target = ".nepkit-district-select"
|
|
88
|
+
|
|
34
89
|
|
|
90
|
+
class DistrictSelectWidget(NepaliWidgetMixin, ChainedSelectWidget):
|
|
91
|
+
css_class = "nepkit-district-select"
|
|
92
|
+
_url_name = "django_nepkit:district-list"
|
|
35
93
|
|
|
36
|
-
class MunicipalitySelectWidget(ChainedSelectWidget):
|
|
37
94
|
def __init__(self, *args, **kwargs):
|
|
38
|
-
attrs = kwargs.get("attrs", {})
|
|
39
|
-
_append_css_class(attrs, "nepkit-municipality-select")
|
|
40
|
-
attrs["data-url"] = reverse_lazy("django_nepkit:municipality-list")
|
|
41
|
-
kwargs["attrs"] = attrs
|
|
42
95
|
super().__init__(*args, **kwargs)
|
|
96
|
+
if self.htmx:
|
|
97
|
+
self._hx_url_name = "django_nepkit:municipality-list"
|
|
98
|
+
self._hx_target = ".nepkit-municipality-select"
|
|
99
|
+
|
|
43
100
|
|
|
101
|
+
class MunicipalitySelectWidget(NepaliWidgetMixin, ChainedSelectWidget):
|
|
102
|
+
css_class = "nepkit-municipality-select"
|
|
103
|
+
_url_name = "django_nepkit:municipality-list"
|
|
44
104
|
|
|
45
|
-
|
|
105
|
+
|
|
106
|
+
class NepaliDatePickerWidget(NepaliWidgetMixin, forms.TextInput):
|
|
46
107
|
input_type = "text"
|
|
47
108
|
|
|
48
109
|
class Media:
|
|
49
110
|
css = {
|
|
50
111
|
"all": (
|
|
51
|
-
"https://
|
|
112
|
+
"https://nepalidatepicker.sajanmaharjan.com.np/v5/nepali.datepicker/css/nepali.datepicker.v5.0.6.min.css",
|
|
52
113
|
)
|
|
53
114
|
}
|
|
54
115
|
js = (
|
|
55
116
|
"https://code.jquery.com/jquery-3.5.1.slim.min.js",
|
|
56
|
-
"https://
|
|
117
|
+
"https://nepalidatepicker.sajanmaharjan.com.np/v5/nepali.datepicker/js/nepali.datepicker.v5.0.6.min.js",
|
|
57
118
|
"django_nepkit/js/nepali-datepicker-init.js",
|
|
58
119
|
)
|
|
59
120
|
|
|
60
|
-
def
|
|
61
|
-
attrs = kwargs.get("attrs", {})
|
|
62
|
-
# Ensure we don't have vDateField class which triggers Django admin calendar
|
|
121
|
+
def _configure_attrs(self, attrs):
|
|
63
122
|
classes = attrs.get("class", "")
|
|
64
123
|
if "vDateField" in classes:
|
|
65
124
|
classes = classes.replace("vDateField", "")
|
|
125
|
+
attrs["class"] = classes
|
|
66
126
|
|
|
67
|
-
attrs["class"] = (classes or "").strip()
|
|
68
127
|
_append_css_class(attrs, "nepkit-datepicker")
|
|
69
128
|
attrs["autocomplete"] = "off"
|
|
70
|
-
attrs["placeholder"] =
|
|
71
|
-
|
|
72
|
-
|
|
129
|
+
attrs["placeholder"] = (
|
|
130
|
+
BS_DATE_FORMAT.replace("%Y", "YYYY").replace("%m", "MM").replace("%d", "DD")
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
def format_value(self, value):
|
|
134
|
+
if value is None:
|
|
135
|
+
return None
|
|
136
|
+
|
|
137
|
+
if self.ne and isinstance(value, nepalidate):
|
|
138
|
+
if hasattr(value, "strftime_ne"):
|
|
139
|
+
return value.strftime_ne(BS_DATE_FORMAT)
|
|
140
|
+
|
|
141
|
+
return super().format_value(value)
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: django-nepkit
|
|
3
|
+
Version: 0.2.1
|
|
4
|
+
Summary: Django Nepali date, time, phone, and address fields with helpers.
|
|
5
|
+
Home-page: https://github.com/S4NKALP/django-nepkit
|
|
6
|
+
Author: Sankalp Tharu
|
|
7
|
+
Author-email: Sankalp Tharu <sankalptharu50028@gmail.com>
|
|
8
|
+
License: MIT
|
|
9
|
+
Project-URL: Homepage, https://github.com/S4NKALP/django-nepkit
|
|
10
|
+
Keywords: django,django-fields,django-forms,nepal,nepali,nepali-date,bikram-sambat,nepali-calendar,timezone,asia-kathmandu,phone-number,address,province,district,municipality
|
|
11
|
+
Classifier: Framework :: Django
|
|
12
|
+
Classifier: Framework :: Django :: 4.2
|
|
13
|
+
Classifier: Framework :: Django :: 5.0
|
|
14
|
+
Classifier: Framework :: Django :: 5.1
|
|
15
|
+
Classifier: Framework :: Django :: 6.0
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
20
|
+
Classifier: Intended Audience :: Developers
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Classifier: Topic :: Utilities
|
|
23
|
+
Classifier: Operating System :: OS Independent
|
|
24
|
+
Classifier: Natural Language :: English
|
|
25
|
+
Requires-Python: >=3.11
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Requires-Dist: django>=4.2
|
|
29
|
+
Requires-Dist: django-filter>=25.2
|
|
30
|
+
Requires-Dist: djangorestframework>=3.16.1
|
|
31
|
+
Requires-Dist: nepali>=1.2.0
|
|
32
|
+
Provides-Extra: drf
|
|
33
|
+
Requires-Dist: djangorestframework>=3.14; extra == "drf"
|
|
34
|
+
Requires-Dist: django-filter>=23.1; extra == "drf"
|
|
35
|
+
Dynamic: author
|
|
36
|
+
Dynamic: home-page
|
|
37
|
+
Dynamic: license-file
|
|
38
|
+
Dynamic: requires-python
|
|
39
|
+
|
|
40
|
+
# 🇳🇵 django-nepkit
|
|
41
|
+
|
|
42
|
+
<div align="center">
|
|
43
|
+
|
|
44
|
+
[](https://badge.fury.io/py/django-nepkit)
|
|
45
|
+
[](https://pypi.org/project/django-nepkit/)
|
|
46
|
+
[](https://www.djangoproject.com/)
|
|
47
|
+
[](https://opensource.org/licenses/MIT)
|
|
48
|
+
|
|
49
|
+
**A toolkit for handling BS dates, regional locations, and validation in the local context.**
|
|
50
|
+
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
Building software for local requirements comes with unique challenges, from handling BS dates to managing the regional administrative hierarchy. `django-nepkit` provides solutions for these requirements directly within the Django ecosystem.
|
|
54
|
+
|
|
55
|
+

|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## 🎯 Features
|
|
60
|
+
|
|
61
|
+
- **📅 BS Date Support**: Model fields for `nepalidate` and `nepalidatetime` objects.
|
|
62
|
+
- **🗺️ Regional Locations**: Pre-defined Provinces, Districts, and Municipalities.
|
|
63
|
+
- **📱 Phone Validation**: Patterns for local mobile and landline numbers.
|
|
64
|
+
- **💰 Currency Formatting**: `NepaliCurrencyField` with automatic Lakhs/Crores comma placement.
|
|
65
|
+
- **🔤 Numbers to Words**: Convert digits into Nepali text representation.
|
|
66
|
+
- **🔌 Admin Integration**: Automatic setup for datepickers and localized list displays.
|
|
67
|
+
- **🚀 API Support**: DRF Serializers and Filtering backends for BS searching and ordering.
|
|
68
|
+
- **⚡ Location Chaining**: Address linking via client side JS or server driven HTMX.
|
|
69
|
+
- **🔍 Address Normalization**: Utility to extract structured locations from raw strings.
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## 🛠 Setup
|
|
74
|
+
|
|
75
|
+
Installation:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
pip install django-nepkit
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### 1. Basic Configuration
|
|
82
|
+
|
|
83
|
+
Add it to your `INSTALLED_APPS`:
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
INSTALLED_APPS = [
|
|
87
|
+
# ...
|
|
88
|
+
"django_nepkit",
|
|
89
|
+
]
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### 2. Global Control
|
|
93
|
+
|
|
94
|
+
Configure behavior in your `settings.py`:
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
NEPKIT = {
|
|
98
|
+
"DEFAULT_LANGUAGE": "en", # "en" or "ne"
|
|
99
|
+
"ADMIN_DATEPICKER": True, # Toggle the datepicker
|
|
100
|
+
"TIME_FORMAT": 12, # 12 or 24 hour display
|
|
101
|
+
"DATE_INPUT_FORMATS": ["%Y-%m-%d", "%d/%m/%Y", "%d-%m-%Y"], # Input formats
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## 🚀 Core Usage
|
|
108
|
+
|
|
109
|
+
### 1. Model Implementation
|
|
110
|
+
|
|
111
|
+
Fields store `YYYY-MM-DD` strings for database stability while providing BS objects in Python.
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
from django_nepkit import NepaliDateField, NepaliPhoneNumberField
|
|
115
|
+
|
|
116
|
+
class Profile(models.Model):
|
|
117
|
+
name = models.CharField(max_length=100)
|
|
118
|
+
birth_date = NepaliDateField() # BS Date support
|
|
119
|
+
phone = NepaliPhoneNumberField() # Local pattern validation
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### 2. Admin Integration
|
|
123
|
+
|
|
124
|
+
Use `NepaliModelAdmin` for automatic formatting and datepicker support.
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
from django_nepkit import NepaliModelAdmin, NepaliDateFilter
|
|
128
|
+
|
|
129
|
+
@admin.register(Profile)
|
|
130
|
+
class ProfileAdmin(NepaliModelAdmin):
|
|
131
|
+
list_display = ("name", "birth_date", "phone")
|
|
132
|
+
list_filter = (("birth_date", NepaliDateFilter),)
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## 🗺️ Address Management
|
|
138
|
+
|
|
139
|
+
Manage the **Province → District → Municipality** hierarchy.
|
|
140
|
+
|
|
141
|
+
### Client-Side Chaining (Standard)
|
|
142
|
+
|
|
143
|
+
Cascading selects in the Django Admin without extra configuration.
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
from django_nepkit import ProvinceField, DistrictField, MunicipalityField
|
|
147
|
+
|
|
148
|
+
class Address(models.Model):
|
|
149
|
+
province = ProvinceField()
|
|
150
|
+
district = DistrictField()
|
|
151
|
+
municipality = MunicipalityField()
|
|
152
|
+
|
|
153
|
+
### Address Normalization
|
|
154
|
+
|
|
155
|
+
Standardize raw strings into structured location data (Province, District, Municipality).
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
from django_nepkit.utils import normalize_address
|
|
159
|
+
|
|
160
|
+
# Supports English or Nepali input
|
|
161
|
+
result = normalize_address("House 123, Bharatpur, Chitwan")
|
|
162
|
+
# Returns: {'province': 'Bagmati Province', 'district': 'Chitawan', 'municipality': 'Bharatpur Metropolitan City'}
|
|
163
|
+
|
|
164
|
+
result_ne = normalize_address("विराटनगर, कोशी")
|
|
165
|
+
# Returns: {'province': 'कोशी प्रदेश', 'district': 'मोरङ', 'municipality': 'विराटनगर महानगरपालिका'}
|
|
166
|
+
```
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Server Side Chaining (HTMX)
|
|
170
|
+
|
|
171
|
+
Enable `htmx=True` for a server driven experience.
|
|
172
|
+
|
|
173
|
+
> [!IMPORTANT]
|
|
174
|
+
> **HTMX Setup:**
|
|
175
|
+
> Include the required URLs in your main `urls.py`:
|
|
176
|
+
>
|
|
177
|
+
> ```python
|
|
178
|
+
> path("nepkit/", include("django_nepkit.urls")),
|
|
179
|
+
> ```
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## 🔌 API & DRF Support
|
|
184
|
+
|
|
185
|
+
Search and ordering work natively. BS year/month filtering is supported.
|
|
186
|
+
|
|
187
|
+
```python
|
|
188
|
+
from django_nepkit.filters import NepaliDateYearFilter
|
|
189
|
+
|
|
190
|
+
class ProfileFilter(filters.FilterSet):
|
|
191
|
+
# Filter by BS Year (e.g., /api/profiles/?year=2081)
|
|
192
|
+
year = NepaliDateYearFilter(field_name="birth_date")
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## 💰 Formatting & Helpers
|
|
198
|
+
|
|
199
|
+
### 1. Currency & Numbers
|
|
200
|
+
Use `NepaliCurrencyField` for automatic formatting in the admin and templates.
|
|
201
|
+
|
|
202
|
+
```python
|
|
203
|
+
from django_nepkit import NepaliCurrencyField
|
|
204
|
+
|
|
205
|
+
class Transaction(models.Model):
|
|
206
|
+
amount = NepaliCurrencyField() # Defaults to 19 digits, 2 decimals
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### 2. Template Filters
|
|
210
|
+
Load the tags to use localized formatting in your templates.
|
|
211
|
+
|
|
212
|
+
```html
|
|
213
|
+
{% load nepali %}
|
|
214
|
+
|
|
215
|
+
<!-- Comma formatting: Rs. 1,12,000.00 -->
|
|
216
|
+
<p>{{ 112000 | nepali_currency }}</p>
|
|
217
|
+
|
|
218
|
+
<!-- Numbers to Words: एक सय तेईस -->
|
|
219
|
+
<p>{{ 123 | nepali_words }}</p>
|
|
220
|
+
|
|
221
|
+
<!-- English to Nepali Digits: १२३ -->
|
|
222
|
+
<p>{{ "123" | nepali_unicode }}</p>
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## 🕒 Technical Design
|
|
228
|
+
|
|
229
|
+
This library is engineered for performance, data integrity, and local compliance.
|
|
230
|
+
|
|
231
|
+
### 1. The "Source of Truth" Strategy
|
|
232
|
+
We avoid on-the-fly `AD to BS` conversion during database queries because it is computationally expensive and prone to logical drift (due to lunar calendar offsets).
|
|
233
|
+
- **Storage**: All BS dates are stored as `VARCHAR(10)` in `YYYY-MM-DD` format.
|
|
234
|
+
- **Sorting**: Because `YYYY` is at the start, string based database sorting (ascending/descending) accurately matches chronological order.
|
|
235
|
+
- **Indexability**: Standard B-Tree indexes work perfectly on these fields without requiring custom database functions.
|
|
236
|
+
- **Timezone Safety**: Dates are stored without time components, making them immune to server side timezone shifts during saving.
|
|
237
|
+
|
|
238
|
+
### 2. Python Object Mapping
|
|
239
|
+
While data is stored as strings, it is automatically hydrated into rich Python objects.
|
|
240
|
+
- **`nepali-datetime` Integration**: Values are cast to `nepalidate` or `nepalidatetime` objects when retrieved from the database.
|
|
241
|
+
- **Validation**: Fields use specialized validators (e.g., `validate_nepali_phone_number`) that leverage official regional patterns.
|
|
242
|
+
|
|
243
|
+
### 3. Frontend Architecture
|
|
244
|
+
- **Automatic Initialization**: The library includes a lightweight JS observer that automatically initializes the datepicker for any field with the `.nepkit-datepicker` class.
|
|
245
|
+
- **Theme Support**: The datepicker dynamically adapts its skin based on the Django Admin's dark/light mode state.
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## ❓ FAQ
|
|
250
|
+
|
|
251
|
+
**Q: How do I handle Null or Optional dates?**
|
|
252
|
+
|
|
253
|
+
Just like standard Django fields, pass `null=True, blank=True` to any `django-nepkit` field. The library handles empty strings and `None` values gracefully.
|
|
254
|
+
|
|
255
|
+
**Q: Can I change the database storage format?**
|
|
256
|
+
|
|
257
|
+
No. The `YYYY-MM-DD` format is hardcoded to ensure database level sorting and indexing work consistently. However, you can change the **display** format via global settings or template filters.
|
|
258
|
+
|
|
259
|
+
**Q: Can I use Devanagari output?**
|
|
260
|
+
|
|
261
|
+
Yes. Pass `ne=True` to fields, forms, or serializers.
|
|
262
|
+
|
|
263
|
+
**Q: Can I display the datepicker in English?**
|
|
264
|
+
|
|
265
|
+
Yes. By default, if you pass `en=True` (or if `DEFAULT_LANGUAGE` is set to `"en"`), the datepicker will display month and day names in English instead of Devanagari script.
|
|
266
|
+
|
|
267
|
+
```python
|
|
268
|
+
birth_date = NepaliDateField(en=True) # English month/day names in picker
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
**Q: Is the location data up to date?**
|
|
272
|
+
|
|
273
|
+
Yes. Province, District, and Municipality data is sourced from the `nepali` Python package, which is regularly updated to reflect administrative changes in Nepal.
|
|
274
|
+
|
|
275
|
+
**Q: Does it work with standard Django Forms?**
|
|
276
|
+
|
|
277
|
+
Yes. `NepaliDateField` uses a specialized `NepaliDateFormField` that automatically handles input parsing and error reporting.
|
|
278
|
+
|
|
279
|
+
**Q: How do I migrate existing English (AD) dates to BS?**
|
|
280
|
+
|
|
281
|
+
We recommend staying on standard `DateField` for AD data. If you must convert to BS, use our [Migration Script](docs/migration_guide.py) to perform a bulk data transformation safely.
|
|
282
|
+
|
|
283
|
+
**Q: Why use VARCHAR instead of a native DateField?**
|
|
284
|
+
|
|
285
|
+
Native `DateField` in most SQL engines is locked to the Gregorian calendar. Using `VARCHAR` allows us to treat the BS date as the primary data point, avoiding the "off-by-one" conversion errors common when syncing two disparate calendars.
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
## 🤝 Community
|
|
290
|
+
|
|
291
|
+
We welcome contributions and feedback from the community.
|
|
292
|
+
|
|
293
|
+
1. **Clone**: `git clone https://github.com/S4NKALP/django-nepkit`
|
|
294
|
+
2. **Setup**: `uv sync`
|
|
295
|
+
3. **Test**: `uv run pytest`
|
|
296
|
+
|
|
297
|
+
---
|
|
298
|
+
|
|
299
|
+
## 🙏 Credits
|
|
300
|
+
|
|
301
|
+
This library is built on top of excellent open source projects:
|
|
302
|
+
|
|
303
|
+
- **[nepali](https://github.com/opensource-nepal/py-nepali)** by [@opensource-nepal](https://github.com/opensource-nepal) - Provides the core `nepalidate`, `nepalidatetime` objects and regional location data (Provinces, Districts, Municipalities).
|
|
304
|
+
- **[Nepali Datepicker](https://nepalidatepicker.sajanmaharjan.com.np/)** by [Sajan Maharjan](https://github.com/sajanm/nepali-date-picker) - Powers the beautiful BS date picker widget in the Django Admin.
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
MIT License. Designed for the local Django ecosystem.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
django_nepkit/__init__.py,sha256=20cQUKdAjNtQYkAJU0g4OSJa90Ru3M9reNfV0ZgG3vc,1164
|
|
2
|
+
django_nepkit/admin.py,sha256=xqQHybYb9T4_NYvCqDV3VYt0tbwWQDc8dwTOrkBmj0Q,12714
|
|
3
|
+
django_nepkit/conf.py,sha256=urr8gwBj2YZgL2Po_C7339YafoQ_PMOWb1MEh-vPdfs,981
|
|
4
|
+
django_nepkit/constants.py,sha256=cVq-ZFkDCBwETU2IDEtAMz2H-oLzSw8TtncRm954Jlg,3507
|
|
5
|
+
django_nepkit/filters.py,sha256=dw5Yej9cLI-cZxlVHSaNUHq63hnnzhkjqmgLPTvbpd8,3939
|
|
6
|
+
django_nepkit/forms.py,sha256=yrRE1gzkxGyqrAl0ETcOWnNsTyhPxlS_nlq4ex5a3UY,1675
|
|
7
|
+
django_nepkit/lang_utils.py,sha256=OY0EdWCHL3pVz4rLijCPv1qs8DMVK8ypL4sv8Dw4w3w,1264
|
|
8
|
+
django_nepkit/models.py,sha256=_u1Y0_q9liReodkDHl-kt8D9b_HxP_GwJo2koLRNBWg,8406
|
|
9
|
+
django_nepkit/serializers.py,sha256=P8dTLOLEaGpc2OuetGXh9LZnbwF1l4VBWQU7y5hwWPA,6487
|
|
10
|
+
django_nepkit/urls.py,sha256=mPF_xEaOqNrInggBhUbwwMq-FTR7rJ0CfT0fQPx41t8,297
|
|
11
|
+
django_nepkit/utils.py,sha256=md3VpScslatEqtaIHo1_kbwvmlVPO45G_Qoikd_oFsE,9106
|
|
12
|
+
django_nepkit/validators.py,sha256=HGd4PwXY7vLh8j2r2pnAHM-rfwVWCa4UDfuW79JYxJ8,407
|
|
13
|
+
django_nepkit/views.py,sha256=u0Hv343omi51pPF0jOLbwBn9cFkDK-Svt9aXtDrHCxM,3864
|
|
14
|
+
django_nepkit/widgets.py,sha256=36Q5kkEn4FuFw--CiAOBrp2mzYSaknCmoLpTFFEDgYU,4588
|
|
15
|
+
django_nepkit/static/django_nepkit/css/admin-nepali-datepicker.css,sha256=g9ago3O0UfoNwRD02AmWSdxiHmXxkQ7mpi94Nn1rO3E,834
|
|
16
|
+
django_nepkit/static/django_nepkit/js/address-chaining.js,sha256=-_eSgL38nc2XUvKv-Ns7cOQyTxra_roRb0z1R0sBKN0,6503
|
|
17
|
+
django_nepkit/static/django_nepkit/js/admin-jquery-bridge.js,sha256=6iqIuSqgYssKtt61WSjye3GBpYHh3AKKYUtGL6Uh3S8,319
|
|
18
|
+
django_nepkit/static/django_nepkit/js/nepal-data.js,sha256=llFe3CAloAoUU505dnNpKgS9Qhqp0-hmZdLbkBDZkz4,168476
|
|
19
|
+
django_nepkit/static/django_nepkit/js/nepali-datepicker-init.js,sha256=b1X15_AAyw5DzHHArGjpF9BFA4RM5IVw9CgzAmPAf40,4356
|
|
20
|
+
django_nepkit-0.2.1.dist-info/licenses/LICENSE,sha256=76985cvIL0AXuhZd5L2C9tbdxKMZX7IOejjvZI8kXMo,1070
|
|
21
|
+
example/manage.py,sha256=wZWUpuXgYEHBxZR-sysETawIaMhr83HQglQTeZdadpQ,752
|
|
22
|
+
example/demo/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
23
|
+
example/demo/admin.py,sha256=pNlA7_GlDpNQRIb7VoTEO1fa0JD-EwLA6VK72aQR504,1635
|
|
24
|
+
example/demo/apps.py,sha256=sXxYSi4dEGg-HDkM7C-TfLv26KbSUj-8HTeFS86pW8w,83
|
|
25
|
+
example/demo/models.py,sha256=zZhoFfYK3zCEapOvVgxbs2bS-OKD3kuFUFvrRDaL_-Y,1854
|
|
26
|
+
example/demo/serializers.py,sha256=V07_sXGPPicCJc1RMecd0jQFvldFPaHpjJuSArj5M7U,1034
|
|
27
|
+
example/demo/tests.py,sha256=qWDvA9ZhVCQ1rPbkoFify7o_fDirXMUdYMxF12q3WIM,26
|
|
28
|
+
example/demo/urls.py,sha256=0zB7KRRTE7HHDnTbE5yibpH9eyJhBekNguj_nuY_gcE,786
|
|
29
|
+
example/demo/views.py,sha256=ow6hQwnhTD1KjEkM4R7qRcuHcnJJ4pOXsU-JRH0Ocn4,4380
|
|
30
|
+
example/example_project/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
31
|
+
example/example_project/settings.py,sha256=nq5lGLEY1cl99XhfS60BpIVfhVFzUhHb_qfNPm5TqSY,2334
|
|
32
|
+
example/example_project/urls.py,sha256=AK8SdvMN2K0QvdzhMr5BUMzl0SrEUhVmMP-3uLEqnU4,217
|
|
33
|
+
example/example_project/wsgi.py,sha256=OK4x37NxQNVNWV4ynLqWP546tR4eALIFn7pyHc8XDNw,176
|
|
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,,
|
example/demo/admin.py
CHANGED
|
@@ -1,42 +1,66 @@
|
|
|
1
1
|
from django.contrib import admin
|
|
2
2
|
|
|
3
|
-
from django_nepkit import
|
|
3
|
+
from django_nepkit import (
|
|
4
|
+
NepaliModelAdmin,
|
|
5
|
+
NepaliAdminMixin,
|
|
6
|
+
NepaliDateFilter,
|
|
7
|
+
NepaliMonthFilter,
|
|
8
|
+
)
|
|
4
9
|
|
|
5
|
-
from .models import Person
|
|
10
|
+
from .models import Person, Citizen, AuditedPerson, Transaction
|
|
6
11
|
|
|
7
12
|
|
|
13
|
+
# Example 1: Basic setup
|
|
8
14
|
@admin.register(Person)
|
|
9
15
|
class PersonAdmin(NepaliModelAdmin):
|
|
10
16
|
list_display = (
|
|
11
17
|
"name",
|
|
12
|
-
"
|
|
18
|
+
"birth_date",
|
|
19
|
+
"birth_date_ne",
|
|
13
20
|
"phone_number",
|
|
14
21
|
"province",
|
|
22
|
+
"province_ne",
|
|
15
23
|
"district",
|
|
24
|
+
"district_ne",
|
|
16
25
|
"municipality",
|
|
17
|
-
"
|
|
18
|
-
"
|
|
26
|
+
"municipality_ne",
|
|
27
|
+
"created_at",
|
|
28
|
+
"updated_at",
|
|
29
|
+
)
|
|
30
|
+
# Added Year and Month filters
|
|
31
|
+
list_filter = (
|
|
32
|
+
("birth_date", NepaliDateFilter),
|
|
33
|
+
("birth_date", NepaliMonthFilter),
|
|
34
|
+
"province",
|
|
19
35
|
)
|
|
20
|
-
list_filter = ("birth_date", "province", "district")
|
|
21
36
|
search_fields = ("name", "phone_number")
|
|
22
37
|
|
|
23
|
-
def display_birth_date(self, obj):
|
|
24
|
-
"""Display birth date with Nepali month names"""
|
|
25
|
-
return self.format_nepali_date(obj.birth_date)
|
|
26
38
|
|
|
27
|
-
|
|
28
|
-
|
|
39
|
+
# Example 2: Using a Mixin
|
|
40
|
+
class BaseAuditAdmin(admin.ModelAdmin):
|
|
41
|
+
"""A fake base class for testing."""
|
|
42
|
+
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# Use the Mixin if you already have a base Admin class
|
|
47
|
+
@admin.register(AuditedPerson)
|
|
48
|
+
class CustomPersonAdmin(NepaliAdminMixin, BaseAuditAdmin):
|
|
49
|
+
list_display = ("name", "birth_date", "created_at")
|
|
50
|
+
list_filter = (
|
|
51
|
+
("created_at", NepaliMonthFilter), # Filter by Nepali Month
|
|
52
|
+
)
|
|
29
53
|
|
|
30
|
-
def display_created_at(self, obj):
|
|
31
|
-
"""Display created date with Nepali month names"""
|
|
32
|
-
return self.format_nepali_datetime(obj.created_at)
|
|
33
54
|
|
|
34
|
-
|
|
35
|
-
|
|
55
|
+
# Example 3: HTMX support
|
|
56
|
+
@admin.register(Citizen)
|
|
57
|
+
class CitizenAdmin(NepaliModelAdmin):
|
|
58
|
+
list_display = ("name", "province", "district", "municipality")
|
|
59
|
+
# Filters still work with HTMX
|
|
60
|
+
list_filter = ("province", "district")
|
|
36
61
|
|
|
37
|
-
def display_updated_at(self, obj):
|
|
38
|
-
"""Display updated date with Nepali month names"""
|
|
39
|
-
return self.format_nepali_datetime(obj.updated_at)
|
|
40
62
|
|
|
41
|
-
|
|
42
|
-
|
|
63
|
+
@admin.register(Transaction)
|
|
64
|
+
class TransactionAdmin(NepaliModelAdmin):
|
|
65
|
+
list_display = ("title", "amount", "date")
|
|
66
|
+
list_filter = (("date", NepaliDateFilter),)
|
example/demo/models.py
CHANGED
|
@@ -7,21 +7,58 @@ from django_nepkit import (
|
|
|
7
7
|
ProvinceField,
|
|
8
8
|
DistrictField,
|
|
9
9
|
MunicipalityField,
|
|
10
|
+
NepaliCurrencyField,
|
|
10
11
|
)
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
class Person(models.Model):
|
|
14
15
|
name = models.CharField(max_length=100)
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
# Dates and times
|
|
17
|
+
birth_date = NepaliDateField() # English by default
|
|
18
|
+
birth_date_ne = NepaliDateField(ne=True, blank=True, null=True) # Using Devanagari
|
|
19
|
+
registration_time = NepaliTimeField(auto_now_add=True) # English digits by default
|
|
17
20
|
phone_number = NepaliPhoneNumberField()
|
|
18
21
|
|
|
19
|
-
#
|
|
20
|
-
province = ProvinceField()
|
|
22
|
+
# Location fields
|
|
23
|
+
province = ProvinceField() # English names by default
|
|
24
|
+
province_ne = ProvinceField(ne=True, blank=True, null=True) # In Devanagari
|
|
21
25
|
district = DistrictField()
|
|
26
|
+
district_ne = DistrictField(ne=True, blank=True, null=True)
|
|
22
27
|
municipality = MunicipalityField()
|
|
28
|
+
municipality_ne = MunicipalityField(ne=True, blank=True, null=True)
|
|
29
|
+
|
|
23
30
|
created_at = NepaliDateTimeField(auto_now_add=True)
|
|
24
31
|
updated_at = NepaliDateTimeField(auto_now=True)
|
|
25
32
|
|
|
26
33
|
def __str__(self):
|
|
27
34
|
return self.name
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class Citizen(models.Model):
|
|
38
|
+
name = models.CharField(max_length=100)
|
|
39
|
+
|
|
40
|
+
# Chaining works automatically
|
|
41
|
+
province = ProvinceField()
|
|
42
|
+
district = DistrictField()
|
|
43
|
+
municipality = MunicipalityField()
|
|
44
|
+
|
|
45
|
+
def __str__(self):
|
|
46
|
+
return self.name
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class AuditedPerson(models.Model):
|
|
50
|
+
name = models.CharField(max_length=100)
|
|
51
|
+
birth_date = NepaliDateField()
|
|
52
|
+
created_at = NepaliDateTimeField(auto_now_add=True)
|
|
53
|
+
|
|
54
|
+
def __str__(self):
|
|
55
|
+
return self.name
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class Transaction(models.Model):
|
|
59
|
+
title = models.CharField(max_length=100)
|
|
60
|
+
amount = NepaliCurrencyField()
|
|
61
|
+
date = NepaliDateField(auto_now_add=True)
|
|
62
|
+
|
|
63
|
+
def __str__(self):
|
|
64
|
+
return f"{self.title} - {self.amount}"
|