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.
@@ -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)