diff --git a/setup.py b/setup.py index 3a0e697..e247179 100644 --- a/setup.py +++ b/setup.py @@ -13,7 +13,7 @@ setup( name='django-rest-form-fields', - version='1.2.8', + version='1.3.0', packages=['django_rest_form_fields'], package_dir={'': 'src'}, url='https://github.com/M1hacka/django-rest-form-fields', diff --git a/src/django_rest_form_fields/fields.py b/src/django_rest_form_fields/fields.py index b5306f2..6f43a9a 100644 --- a/src/django_rest_form_fields/fields.py +++ b/src/django_rest_form_fields/fields.py @@ -4,11 +4,10 @@ import datetime import json -import os -import re - import jsonschema +import os import pytz +import re import six from django import forms from django.core.exceptions import ValidationError @@ -26,6 +25,7 @@ class BaseField(forms.Field): All library fields are inherited from this base Adds source """ + def __init__(self, *args, **kwargs): self.source = kwargs.pop('source', None) super(BaseField, self).__init__(*args, **kwargs) @@ -94,11 +94,12 @@ class RegexField(RestCharField): """ Wraps CharField to validate via regular expression """ + def __init__(self, *args, **kwargs): self.regex = kwargs.pop('regex', None) self.flags = kwargs.pop('flags', 0) - assert self.regex is None or isinstance(self.regex, (six.string_types, get_pattern_type())),\ + assert self.regex is None or isinstance(self.regex, (six.string_types, get_pattern_type())), \ 'regex must be string if given' assert isinstance(self.flags, int), 'flags must be integer' @@ -223,6 +224,7 @@ class DateTimeField(RestCharField): """ Parses given string as datetime by given mask with datetime.datetime.strptime() method """ + base_type = datetime.datetime def __init__(self, *args, **kwargs): """ @@ -240,7 +242,7 @@ def __init__(self, *args, **kwargs): def clean(self, value): # type: (Any) -> datetime.datetime value = super(DateTimeField, self).clean(value) - if value is not None and not isinstance(value, datetime.datetime): + if value is not None and not isinstance(value, self.base_type): try: dt = datetime.datetime.strptime(value, self.mask) except (ValueError, TypeError): @@ -251,6 +253,28 @@ def clean(self, value): # type: (Any) -> datetime.datetime return value +class DateField(DateTimeField): + """ + Parses month with datetime.datetime.strptime() method. Default format is %Y-%m. + Returns datetime.date with first day of month. + """ + base_type = datetime.date + + def __init__(self, *args, **kwargs): + """ + Initializes field + :param args: Positional arguments + :param mask: Mask to parse month string with datetime.datetime.strptime() method + :param kwargs: Named arguments + """ + mask = kwargs.pop("mask", "%Y-%m-%d") + super(DateField, self).__init__(*args, mask=mask, **kwargs) + + def clean(self, value): # type (Any) -> datetime.date + value = super(DateField, self).clean(value) + return value.date() if isinstance(value, datetime.datetime) else value + + class MonthField(DateTimeField): """ Parses month with datetime.datetime.strptime() method. Default format is %Y-%m. diff --git a/tests/test_fields.py b/tests/test_fields.py index 15a558a..a22cdec 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -6,21 +6,20 @@ import json import random import re +import six import uuid - +from django.core.exceptions import ValidationError from django.core.validators import BaseValidator from django.utils import timezone +from django.utils.timezone import utc from io import BytesIO from unittest import TestCase -import six -from django.core.exceptions import ValidationError -from django.utils.timezone import utc - from django_rest_form_fields.compatibility import to_timestamp from django_rest_form_fields.fields import RestBooleanField, LowerCaseEmailField, TimestampField, DateUnitField, \ ColorField, IdArrayField, IdSetField, TruncatedCharField, JsonField, ArrayField, UrlField, RestCharField, \ - RestChoiceField, RestIntegerField, RegexField, UUIDField, DateTimeField, MonthField, FileField, RestFloatField + RestChoiceField, RestIntegerField, RegexField, UUIDField, DateTimeField, MonthField, FileField, RestFloatField, \ + DateField class TestErrorValidator(BaseValidator): @@ -463,6 +462,35 @@ def test_empty_value_validators(self): f.clean('') +class DateFieldTest(TestCase): + def test_today(self): + today = datetime.date.today() + f = DateField() + res = f.clean(today.isoformat()) + self.assertEqual(today, res) + + def test_initial(self): + today = datetime.date.today() + f = DateField(initial=today, required=False) + res = f.clean(None) + self.assertEqual(today, res) + + def test_required(self): + f = DateField(required=False) + self.assertEqual(None, f.clean(None)) + + f = DateField() + with self.assertRaises(ValidationError): + f.clean(None) + + def test_empty_value_validators(self): + # By default django ignores skips run_validators methods, if value is in empty_values + # It's not correct for REST, as empty value is not equal to None value now + f = DateField(validators=[TestErrorValidator(0)]) + with self.assertRaises(ValidationError): + f.clean('') + + class MonthFieldTest(TestCase): def test_now(self): now = timezone.now().replace(microsecond=0) @@ -879,10 +907,10 @@ def test_file_size(self): test_file = self._get_test_file('pdf') - f = FileField(max_size=2*1024*1024) + f = FileField(max_size=2 * 1024 * 1024) self.assertEqual(test_file, f.clean(test_file)) - test_file.size = 1*1024*1024 + test_file.size = 1 * 1024 * 1024 self.assertEqual(test_file, f.clean(test_file)) test_file.size = 2 * 1024 * 1024 - 1 @@ -906,4 +934,4 @@ def test_empty_value_validators(self): # It's not correct for REST, as empty value is not equal to None value now f = FileField(validators=[TestErrorValidator(0)]) with self.assertRaises(ValidationError): - f.clean('') \ No newline at end of file + f.clean('')