django-npdatetime 0.1.0__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_npdatetime-0.1.0.dist-info/METADATA +397 -0
- django_npdatetime-0.1.0.dist-info/RECORD +23 -0
- django_npdatetime-0.1.0.dist-info/WHEEL +5 -0
- django_npdatetime-0.1.0.dist-info/licenses/LICENSE +21 -0
- django_npdatetime-0.1.0.dist-info/top_level.txt +1 -0
- npdatetime_django/__init__.py +22 -0
- npdatetime_django/apps.py +13 -0
- npdatetime_django/forms.py +239 -0
- npdatetime_django/models.py +198 -0
- npdatetime_django/static/npdatetime_django/css/date_picker.css +746 -0
- npdatetime_django/static/npdatetime_django/js/date_picker.min.js +1381 -0
- npdatetime_django/static/npdatetime_django/js/pkg/README.md +345 -0
- npdatetime_django/static/npdatetime_django/js/pkg/npdatetime_wasm.d.ts +205 -0
- npdatetime_django/static/npdatetime_django/js/pkg/npdatetime_wasm.js +813 -0
- npdatetime_django/static/npdatetime_django/js/pkg/npdatetime_wasm_bg.wasm +0 -0
- npdatetime_django/static/npdatetime_django/js/pkg/npdatetime_wasm_bg.wasm.d.ts +36 -0
- npdatetime_django/static/npdatetime_django/js/pkg/package.json +15 -0
- npdatetime_django/templates/npdatetime_django/widgets/date_picker.html +28 -0
- npdatetime_django/templates/npdatetime_django/widgets/date_range.html +25 -0
- npdatetime_django/templatetags/__init__.py +259 -0
- npdatetime_django/templatetags/nepali_date.py +1 -0
- npdatetime_django/utils.py +131 -0
- npdatetime_django/widgets.py +168 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
"""Custom form fields for Nepali dates"""
|
|
2
|
+
from django import forms
|
|
3
|
+
from django.core.exceptions import ValidationError
|
|
4
|
+
import re
|
|
5
|
+
|
|
6
|
+
try:
|
|
7
|
+
from npdatetime import NepaliDate
|
|
8
|
+
NPDATETIME_AVAILABLE = True
|
|
9
|
+
except ImportError:
|
|
10
|
+
NPDATETIME_AVAILABLE = False
|
|
11
|
+
|
|
12
|
+
from .widgets import NepaliDatePickerWidget, NepaliDateRangeWidget
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class NepaliDateField(forms.CharField):
|
|
16
|
+
"""
|
|
17
|
+
A form field for Nepali (Bikram Sambat) dates.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
mode (str): 'BS' or 'AD'. Default: 'BS'
|
|
21
|
+
language (str): 'en' or 'np'. Default: 'en'
|
|
22
|
+
widget: Custom widget. Default: NepaliDatePickerWidget
|
|
23
|
+
**kwargs: Additional arguments passed to CharField
|
|
24
|
+
|
|
25
|
+
Example:
|
|
26
|
+
class PersonForm(forms.Form):
|
|
27
|
+
birth_date = NepaliDateField(
|
|
28
|
+
mode='BS',
|
|
29
|
+
language='np',
|
|
30
|
+
label='जन्म मिति'
|
|
31
|
+
)
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(self, *args, mode='BS', language='en', **kwargs):
|
|
35
|
+
self.mode = mode
|
|
36
|
+
self.language = language
|
|
37
|
+
|
|
38
|
+
# Set widget if not provided
|
|
39
|
+
if 'widget' not in kwargs:
|
|
40
|
+
kwargs['widget'] = NepaliDatePickerWidget(
|
|
41
|
+
mode=mode,
|
|
42
|
+
language=language
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
# Set max length for YYYY-MM-DD format
|
|
46
|
+
kwargs.setdefault('max_length', 10)
|
|
47
|
+
|
|
48
|
+
super().__init__(*args, **kwargs)
|
|
49
|
+
|
|
50
|
+
def clean(self, value):
|
|
51
|
+
"""Validate and clean the date value."""
|
|
52
|
+
value = super().clean(value)
|
|
53
|
+
|
|
54
|
+
if not value:
|
|
55
|
+
return value
|
|
56
|
+
|
|
57
|
+
# Validate format
|
|
58
|
+
if not re.match(r'^\d{4}-\d{2}-\d{2}$', value):
|
|
59
|
+
raise ValidationError(
|
|
60
|
+
'Enter a valid date in YYYY-MM-DD format.',
|
|
61
|
+
code='invalid_format'
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# Validate actual date if npdatetime is available
|
|
65
|
+
if NPDATETIME_AVAILABLE:
|
|
66
|
+
try:
|
|
67
|
+
year, month, day = map(int, value.split('-'))
|
|
68
|
+
if self.mode == 'BS':
|
|
69
|
+
NepaliDate(year, month, day)
|
|
70
|
+
else:
|
|
71
|
+
# Validate Gregorian date
|
|
72
|
+
NepaliDate.from_gregorian(year, month, day)
|
|
73
|
+
except Exception as e:
|
|
74
|
+
raise ValidationError(
|
|
75
|
+
f'Invalid {self.mode} date: {e}',
|
|
76
|
+
code='invalid_date'
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
return value
|
|
80
|
+
|
|
81
|
+
def to_nepali_date(self, value):
|
|
82
|
+
"""
|
|
83
|
+
Convert the cleaned value to a NepaliDate instance.
|
|
84
|
+
Returns None if npdatetime is not available or value is empty.
|
|
85
|
+
"""
|
|
86
|
+
if not value or not NPDATETIME_AVAILABLE:
|
|
87
|
+
return None
|
|
88
|
+
|
|
89
|
+
year, month, day = map(int, value.split('-'))
|
|
90
|
+
if self.mode == 'BS':
|
|
91
|
+
return NepaliDate(year, month, day)
|
|
92
|
+
else:
|
|
93
|
+
return NepaliDate.from_gregorian(year, month, day)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class NepaliDateTimeField(forms.CharField):
|
|
97
|
+
"""
|
|
98
|
+
A form field for Nepali dates with time.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
mode (str): 'BS' or 'AD'. Default: 'BS'
|
|
102
|
+
language (str): 'en' or 'np'. Default: 'en'
|
|
103
|
+
**kwargs: Additional arguments passed to CharField
|
|
104
|
+
|
|
105
|
+
Example:
|
|
106
|
+
class EventForm(forms.Form):
|
|
107
|
+
event_datetime = NepaliDateTimeField(
|
|
108
|
+
mode='BS',
|
|
109
|
+
language='en'
|
|
110
|
+
)
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
def __init__(self, *args, mode='BS', language='en', **kwargs):
|
|
114
|
+
self.mode = mode
|
|
115
|
+
self.language = language
|
|
116
|
+
|
|
117
|
+
# Set widget if not provided
|
|
118
|
+
if 'widget' not in kwargs:
|
|
119
|
+
kwargs['widget'] = NepaliDatePickerWidget(
|
|
120
|
+
mode=mode,
|
|
121
|
+
language=language,
|
|
122
|
+
include_time=True
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
# Set max length for YYYY-MM-DD HH:MM:SS format
|
|
126
|
+
kwargs.setdefault('max_length', 19)
|
|
127
|
+
|
|
128
|
+
super().__init__(*args, **kwargs)
|
|
129
|
+
|
|
130
|
+
def clean(self, value):
|
|
131
|
+
"""Validate and clean the datetime value."""
|
|
132
|
+
value = super().clean(value)
|
|
133
|
+
|
|
134
|
+
if not value:
|
|
135
|
+
return value
|
|
136
|
+
|
|
137
|
+
# Validate format
|
|
138
|
+
if not re.match(r'^\d{4}-\d{2}-\d{2} \d{2}:\d{2}(:\d{2})?$', value):
|
|
139
|
+
raise ValidationError(
|
|
140
|
+
'Enter a valid datetime in YYYY-MM-DD HH:MM:SS format.',
|
|
141
|
+
code='invalid_format'
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
# Add seconds if not present
|
|
145
|
+
if value.count(':') == 1:
|
|
146
|
+
value += ':00'
|
|
147
|
+
|
|
148
|
+
# Validate date part if npdatetime is available
|
|
149
|
+
if NPDATETIME_AVAILABLE:
|
|
150
|
+
try:
|
|
151
|
+
date_part = value.split(' ')[0]
|
|
152
|
+
year, month, day = map(int, date_part.split('-'))
|
|
153
|
+
if self.mode == 'BS':
|
|
154
|
+
NepaliDate(year, month, day)
|
|
155
|
+
else:
|
|
156
|
+
NepaliDate.from_gregorian(year, month, day)
|
|
157
|
+
|
|
158
|
+
# Validate time part
|
|
159
|
+
time_part = value.split(' ')[1]
|
|
160
|
+
hour, minute, second = map(int, time_part.split(':'))
|
|
161
|
+
if not (0 <= hour < 24 and 0 <= minute < 60 and 0 <= second < 60):
|
|
162
|
+
raise ValueError("Invalid time values")
|
|
163
|
+
|
|
164
|
+
except Exception as e:
|
|
165
|
+
raise ValidationError(
|
|
166
|
+
f'Invalid {self.mode} datetime: {e}',
|
|
167
|
+
code='invalid_datetime'
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
return value
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class NepaliDateRangeField(forms.CharField):
|
|
174
|
+
"""
|
|
175
|
+
A form field for selecting a Nepali date range.
|
|
176
|
+
|
|
177
|
+
Returns dates in format: "YYYY-MM-DD to YYYY-MM-DD"
|
|
178
|
+
|
|
179
|
+
Example:
|
|
180
|
+
class ReportForm(forms.Form):
|
|
181
|
+
report_period = NepaliDateRangeField(
|
|
182
|
+
mode='BS',
|
|
183
|
+
language='np'
|
|
184
|
+
)
|
|
185
|
+
"""
|
|
186
|
+
|
|
187
|
+
def __init__(self, *args, mode='BS', language='en', **kwargs):
|
|
188
|
+
self.mode = mode
|
|
189
|
+
self.language = language
|
|
190
|
+
|
|
191
|
+
if 'widget' not in kwargs:
|
|
192
|
+
kwargs['widget'] = NepaliDateRangeWidget(
|
|
193
|
+
mode=mode,
|
|
194
|
+
language=language
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
super().__init__(*args, **kwargs)
|
|
198
|
+
|
|
199
|
+
def clean(self, value):
|
|
200
|
+
"""Validate the date range."""
|
|
201
|
+
value = super().clean(value)
|
|
202
|
+
|
|
203
|
+
if not value:
|
|
204
|
+
return value
|
|
205
|
+
|
|
206
|
+
if ' to ' not in value:
|
|
207
|
+
raise ValidationError(
|
|
208
|
+
'Enter a valid date range in format: YYYY-MM-DD to YYYY-MM-DD',
|
|
209
|
+
code='invalid_format'
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
start_date, end_date = value.split(' to ')
|
|
213
|
+
|
|
214
|
+
# Validate both dates
|
|
215
|
+
for date_str in [start_date, end_date]:
|
|
216
|
+
if not re.match(r'^\d{4}-\d{2}-\d{2}$', date_str):
|
|
217
|
+
raise ValidationError(
|
|
218
|
+
'Invalid date format in range. Use YYYY-MM-DD.',
|
|
219
|
+
code='invalid_format'
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
# Validate that start <= end
|
|
223
|
+
if start_date > end_date:
|
|
224
|
+
raise ValidationError(
|
|
225
|
+
'Start date must be before or equal to end date.',
|
|
226
|
+
code='invalid_range'
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
return value
|
|
230
|
+
|
|
231
|
+
def get_date_range(self, value):
|
|
232
|
+
"""
|
|
233
|
+
Return tuple of (start_date, end_date) as strings.
|
|
234
|
+
Returns (None, None) if value is empty.
|
|
235
|
+
"""
|
|
236
|
+
if not value or ' to ' not in value:
|
|
237
|
+
return (None, None)
|
|
238
|
+
|
|
239
|
+
return tuple(value.split(' to '))
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"""Custom model fields for Nepali dates"""
|
|
2
|
+
from django.db import models
|
|
3
|
+
from django.core import validators
|
|
4
|
+
from django.core.exceptions import ValidationError
|
|
5
|
+
import re
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
from npdatetime import NepaliDate
|
|
9
|
+
NPDATETIME_AVAILABLE = True
|
|
10
|
+
except ImportError:
|
|
11
|
+
NPDATETIME_AVAILABLE = False
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class NepaliDateField(models.CharField):
|
|
15
|
+
"""
|
|
16
|
+
A model field for storing Nepali (Bikram Sambat) dates.
|
|
17
|
+
|
|
18
|
+
Stores dates in YYYY-MM-DD format internally.
|
|
19
|
+
Can be used to create, validate, and convert Nepali dates.
|
|
20
|
+
|
|
21
|
+
Example:
|
|
22
|
+
class Person(models.Model):
|
|
23
|
+
name = models.CharField(max_length=100)
|
|
24
|
+
birth_date_bs = NepaliDateField()
|
|
25
|
+
|
|
26
|
+
person = Person(name="Ram", birth_date_bs="2081-01-15")
|
|
27
|
+
person.save()
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
description = "Nepali Date (Bikram Sambat) field"
|
|
31
|
+
|
|
32
|
+
def __init__(self, *args, **kwargs):
|
|
33
|
+
# Force max_length to 10 for YYYY-MM-DD format
|
|
34
|
+
kwargs['max_length'] = 10
|
|
35
|
+
super().__init__(*args, **kwargs)
|
|
36
|
+
|
|
37
|
+
# Add validator for date format
|
|
38
|
+
self.validators.append(validators.RegexValidator(
|
|
39
|
+
regex=r'^\d{4}-\d{2}-\d{2}$',
|
|
40
|
+
message='Enter a valid Nepali date in YYYY-MM-DD format.',
|
|
41
|
+
code='invalid_nepali_date_format'
|
|
42
|
+
))
|
|
43
|
+
|
|
44
|
+
def deconstruct(self):
|
|
45
|
+
"""
|
|
46
|
+
Return enough information to recreate the field as a 4-tuple.
|
|
47
|
+
"""
|
|
48
|
+
name, path, args, kwargs = super().deconstruct()
|
|
49
|
+
# Remove max_length as we set it automatically
|
|
50
|
+
kwargs.pop('max_length', None)
|
|
51
|
+
return name, path, args, kwargs
|
|
52
|
+
|
|
53
|
+
def to_python(self, value):
|
|
54
|
+
"""
|
|
55
|
+
Convert the input value to a NepaliDate instance or string.
|
|
56
|
+
"""
|
|
57
|
+
if value is None or value == '':
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
if isinstance(value, str):
|
|
61
|
+
# Validate format
|
|
62
|
+
if not re.match(r'^\d{4}-\d{2}-\d{2}$', value):
|
|
63
|
+
raise ValidationError(
|
|
64
|
+
'%(value)s is not a valid Nepali date format. Use YYYY-MM-DD.',
|
|
65
|
+
code='invalid',
|
|
66
|
+
params={'value': value},
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# Validate the actual date if npdatetime is available
|
|
70
|
+
if NPDATETIME_AVAILABLE:
|
|
71
|
+
try:
|
|
72
|
+
year, month, day = map(int, value.split('-'))
|
|
73
|
+
NepaliDate(year, month, day) # Validate
|
|
74
|
+
except Exception as e:
|
|
75
|
+
raise ValidationError(
|
|
76
|
+
'%(value)s is not a valid Nepali date: %(error)s',
|
|
77
|
+
code='invalid',
|
|
78
|
+
params={'value': value, 'error': str(e)},
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
return value
|
|
82
|
+
|
|
83
|
+
if NPDATETIME_AVAILABLE and isinstance(value, NepaliDate):
|
|
84
|
+
return f"{value.year}-{value.month:02d}-{value.day:02d}"
|
|
85
|
+
|
|
86
|
+
return str(value)
|
|
87
|
+
|
|
88
|
+
def from_db_value(self, value, expression, connection):
|
|
89
|
+
"""
|
|
90
|
+
Convert database value to Python value.
|
|
91
|
+
"""
|
|
92
|
+
return self.to_python(value)
|
|
93
|
+
|
|
94
|
+
def get_prep_value(self, value):
|
|
95
|
+
"""
|
|
96
|
+
Convert Python value to database value.
|
|
97
|
+
"""
|
|
98
|
+
value = super().get_prep_value(value)
|
|
99
|
+
return self.to_python(value)
|
|
100
|
+
|
|
101
|
+
def formfield(self, **kwargs):
|
|
102
|
+
"""
|
|
103
|
+
Return a form field instance for this model field.
|
|
104
|
+
"""
|
|
105
|
+
from .forms import NepaliDateField as NepaliDateFormField
|
|
106
|
+
from .widgets import NepaliDatePickerWidget
|
|
107
|
+
|
|
108
|
+
defaults = {
|
|
109
|
+
'form_class': NepaliDateFormField,
|
|
110
|
+
'widget': NepaliDatePickerWidget,
|
|
111
|
+
}
|
|
112
|
+
defaults.update(kwargs)
|
|
113
|
+
return super().formfield(**defaults)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class NepaliDateTimeField(models.CharField):
|
|
117
|
+
"""
|
|
118
|
+
A model field for storing Nepali dates with time.
|
|
119
|
+
|
|
120
|
+
Stores datetime in YYYY-MM-DD HH:MM:SS format internally.
|
|
121
|
+
|
|
122
|
+
Example:
|
|
123
|
+
class Event(models.Model):
|
|
124
|
+
name = models.CharField(max_length=100)
|
|
125
|
+
event_datetime_bs = NepaliDateTimeField()
|
|
126
|
+
|
|
127
|
+
event = Event(name="Concert", event_datetime_bs="2081-01-15 14:30:00")
|
|
128
|
+
event.save()
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
description = "Nepali DateTime (Bikram Sambat) field"
|
|
132
|
+
|
|
133
|
+
def __init__(self, *args, **kwargs):
|
|
134
|
+
# Force max_length to 19 for YYYY-MM-DD HH:MM:SS format
|
|
135
|
+
kwargs['max_length'] = 19
|
|
136
|
+
super().__init__(*args, **kwargs)
|
|
137
|
+
|
|
138
|
+
# Add validator for datetime format
|
|
139
|
+
self.validators.append(validators.RegexValidator(
|
|
140
|
+
regex=r'^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$',
|
|
141
|
+
message='Enter a valid Nepali datetime in YYYY-MM-DD HH:MM:SS format.',
|
|
142
|
+
code='invalid_nepali_datetime_format'
|
|
143
|
+
))
|
|
144
|
+
|
|
145
|
+
def deconstruct(self):
|
|
146
|
+
name, path, args, kwargs = super().deconstruct()
|
|
147
|
+
kwargs.pop('max_length', None)
|
|
148
|
+
return name, path, args, kwargs
|
|
149
|
+
|
|
150
|
+
def to_python(self, value):
|
|
151
|
+
"""Convert input value to valid Nepali datetime string."""
|
|
152
|
+
if value is None or value == '':
|
|
153
|
+
return None
|
|
154
|
+
|
|
155
|
+
if isinstance(value, str):
|
|
156
|
+
# Validate format
|
|
157
|
+
if not re.match(r'^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$', value):
|
|
158
|
+
raise ValidationError(
|
|
159
|
+
'%(value)s is not a valid Nepali datetime format. Use YYYY-MM-DD HH:MM:SS.',
|
|
160
|
+
code='invalid',
|
|
161
|
+
params={'value': value},
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
# Validate the date part if npdatetime is available
|
|
165
|
+
if NPDATETIME_AVAILABLE:
|
|
166
|
+
try:
|
|
167
|
+
date_part = value.split(' ')[0]
|
|
168
|
+
year, month, day = map(int, date_part.split('-'))
|
|
169
|
+
NepaliDate(year, month, day) # Validate
|
|
170
|
+
except Exception as e:
|
|
171
|
+
raise ValidationError(
|
|
172
|
+
'%(value)s contains an invalid Nepali date: %(error)s',
|
|
173
|
+
code='invalid',
|
|
174
|
+
params={'value': value, 'error': str(e)},
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
return value
|
|
178
|
+
|
|
179
|
+
return str(value)
|
|
180
|
+
|
|
181
|
+
def from_db_value(self, value, expression, connection):
|
|
182
|
+
return self.to_python(value)
|
|
183
|
+
|
|
184
|
+
def get_prep_value(self, value):
|
|
185
|
+
value = super().get_prep_value(value)
|
|
186
|
+
return self.to_python(value)
|
|
187
|
+
|
|
188
|
+
def formfield(self, **kwargs):
|
|
189
|
+
"""Return a form field instance for this model field."""
|
|
190
|
+
from .forms import NepaliDateTimeField as NepaliDateTimeFormField
|
|
191
|
+
from .widgets import NepaliDatePickerWidget
|
|
192
|
+
|
|
193
|
+
defaults = {
|
|
194
|
+
'form_class': NepaliDateTimeFormField,
|
|
195
|
+
'widget': NepaliDatePickerWidget(include_time=True),
|
|
196
|
+
}
|
|
197
|
+
defaults.update(kwargs)
|
|
198
|
+
return super().formfield(**defaults)
|