Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

datefield and none/empty values #5365

Closed
2 tasks
sehmaschine opened this issue Aug 25, 2017 · 2 comments
Closed
2 tasks

datefield and none/empty values #5365

sehmaschine opened this issue Aug 25, 2017 · 2 comments

Comments

@sehmaschine
Copy link

Checklist

  • I have verified that that issue exists against the master branch of Django REST framework.
  • [x ] I have searched for similar issues in both open and closed tickets and cannot find a duplicate.
  • [x ] This is not a usage question. (Those should be directed to the discussion group instead.)
  • [x ] This cannot be dealt with as a third party library. (We prefer new functionality to be in the form of third party libraries where possible.)
  • [x ] I have reduced the issue to the simplest possible case.
  • I have included a failing test as a pull request. (If you are unable to do so we can still accept the issue.)
class MyModel(models.Model):
    date = models.DateField(null=True, blank=True)
    title = models.CharField(max_length=100)


class MySerializer(serializers.ModelSerializer):
    class Meta:
        model = MyModel


class TestSerializer(TestCase):

    def setUp(self):
        self.factory = RequestFactory()
        self.client = Client()
        self.view = TestView.as_view()

    def test_post_create(self):
        self.client.login(username="demo", password="demo")
        data = {
            "title": "bar"
        }
        res = self.client.post(
            '/api/v1/posts/',
            json.dumps(data),
            format='json',
            content_type='application/json',
            HTTP_ACCEPT='application/json')
        self.assertEqual(res.status_code, 201)
        self.assertEqual(res.data["date"], None)

    def test_date_none(self):
        self.client.login(username="demo", password="demo")
        data = {
            "title": "bar",
            "date": "2017-01-01"
        }
        res = self.client.post(
            '/api/v1/posts/',
            json.dumps(data),
            format='json',
            content_type='application/json',
            HTTP_ACCEPT='application/json')
        self.assertEqual(res.status_code, 201)
        self.assertEqual(res.data["date"], '2017-01-01')

        data = {
            "title": "bar",
            "date": None
        }
        res = self.client.patch(
            '/api/v1/posts/%s/' % (res.data["id"]),
            json.dumps(data),
            format='json',
            content_type='application/json',
            HTTP_ACCEPT='application/json')
        self.assertEqual(res.status_code, 200)
        self.assertEqual(res.data["date"], None)

    def test_date_empty(self):
        self.client.login(username="demo", password="demo")
        data = {
            "title": "bar",
            "date": "2017-01-01"
        }
        res = self.client.post(
            '/api/v1/posts/',
            json.dumps(data),
            format='json',
            content_type='application/json',
            HTTP_ACCEPT='application/json')
        self.assertEqual(res.status_code, 201)
        self.assertEqual(res.data["date"], '2017-01-01')

        data = {
            "title": "bar",
            "date": ""
        }
        res = self.client.patch(
            '/api/v1/posts/%s/' % (res.data["id"]),
            json.dumps(data),
            format='json',
            content_type='application/json',
            HTTP_ACCEPT='application/json')
        # this is unexpected behaviour
        self.assertEqual(res.status_code, 400)
        self.assertEqual(res.data["date"], [u'Date has wrong format. Use one of these formats instead: YYYY-MM-DD.'])

I'm actually not sure if this is a bug, but I expected being able to send an empty string if a DateField is not required. If an empty string is not allowed, at least the error message should (IMHO) refer to something like "blank is not allowed" instead of mentioning the wrong format.
I've done quite some research on this topic and also found some issues and explanations (but not an answer). Please feel free to close this if I missed something (and/or this is a duplicate).

See also #2687

@sehmaschine
Copy link
Author

sehmaschine commented Aug 26, 2017

btw, I've been able to solve the issue with this workaround ...

def transfer_empty_to_none(field_names, data):
    for field_name in field_names:
        if field_name in data and data[field_name] == "":
            data[field_name] = None
    return data


class TransferEmptyNoneMixin(object):
    def create(self, request):
        if self.empty_to_none_fields:
            transfer_empty_to_none(self.empty_to_none_fields, request.data)
        return super(TransferEmptyNoneMixin, self).create(request)

    def update(self, request, *args, **kwargs):
        if self.empty_to_none_fields:
            transfer_empty_to_none(self.empty_to_none_fields, request.data)
        return super(TransferEmptyNoneMixin, self).update(request, *args, **kwargs)

    def partial_update(self, request, *args, **kwargs):
        if self.empty_to_none_fields:
            transfer_empty_to_none(self.empty_to_none_fields, request.data)
        return super(TransferEmptyNoneMixin, self).partial_update(request, *args, **kwargs)

A view/viewset then looks like this ...

class PostViewSet(TransferEmptyNoneMixin, viewsets.ModelViewSet):
    empty_to_none_fields = ("date",)

The mixin can be used for all fields which need to normalize empty strings to None (e.g. date, decimal).

@carltongibson
Copy link
Collaborator

carltongibson commented Aug 28, 2017

Hi @sehmaschine,

Thanks for the report. I’m going to go with not a bug for now. 🙂

If you sent None you’d get the not required behaviour you’re looking for but, DateFields don’t have the same empty instead of null behaviour/conventions that CharField types do and, an empty string is not a valid date.

Your workaround is fine. It might be better at the field level in to_internal_value or run_validation (You can see that CharField takes steps to handle the empty string.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants