I want to add an "expiry_date" field to a DjangoModelFactory, to match its related model.

Here is my implementation :

models.py

def set_default_expiry_date():
    return timezone.now() + datetime.timedelta(days=7)

[...]

    expiry_date = models.DateTimeField(
            verbose_name=_('Expiry date'),
            default=set_default_expiry_date,
            validators=[validate_expiry_date]
    )

factories.py

class OfferFactory(factory.django.DjangoModelFactory):
[...]
    expiry_date = factory.LazyFunction(set_default_expiry_date)

test_views.py

def test_POST_error_messages(self):
    offer = factory.build(dict, FACTORY_CLASS=OfferFactory)
    offer['price'] = 9999
    offer['item_count'] = -123

    self.client.force_login(self.company_user)
    response = self.client.post(self.url, offer)

    self.assertEqual(2, len(response.context['form'].errors))
    self.assertTrue(
        'price' and 'item_count' in response.context['form'].errors
    )

This test should only return two error messages, both from failed validation constraints on the 'price' and 'item_count' fields. Yet I get a translated form error message saying that I should provide a valid date and time. This error message does not originate from the custom validator I have added for this field.

here is the form's definition, for the sake of completeness :

forms.py

class OfferForm(forms.ModelForm):
[...]
    class Meta:
        model = Offer
        fields = (
                [...]
                 'expiry_date'
        widgets = {
        [...]
                'expiry_date': forms.DateTimeInput(
                    attrs={'class': 'form-control', }
                )
        }

I have USE_TZ and USE_L10N enabled.

It looks like the datetime object should use a localized format but fails to do so.

When I run the server, the datetime field uses the localized format.

So this isn't a configuration issue at the form level.

Any insight is appreciated and thank you for your time.

EDIT:

output of print(offer)

{'title': 'Back grow artist.', 'description': '...', 'price': 9999, 'item_count': -123, 'discount': 18, 'created_by': <CustomUser: TestCompany5>, 'expiry_date': datetime.datetime(2019, 4, 15, 13, 9, 52, 202191, tzinfo=<UTC>)}

2 Answers

0
Alexandre Beauvois On Best Solutions

I have finally decided to use a different approach and simply pass an integer representing an offset in days.
Thank you to @Ramy Mohamed for his insight.
I was trying to POST the datetime object as-is. Since the widget is rendered as a text input, the server would receive a string and not a datetime object after a POST request.
I didn't need to configure the format as shown in his answer though, since he was talking about how the datetime would be displayed, not fed back to the server.

This is what I had done that worked:
test_views.py

locale_format = formats.get_format('DATETIME_INPUT_FORMATS', lang=translation.get_language())[0]
offer['expiry_date'] = offer['expiry_date'].strftime(locale_format)
0
Ramy Mohamed On

In django, widgets are responsible for rendering the view. so you might expect that rendering a non-formatted DateTimeInput cause this unexpected behavior.

Try doing this:

class OfferForm(forms.ModelForm):
[...]
    class Meta:
        model = Offer
        fields = (
                [...]
                 'expiry_date'
        widgets = {
        [...]
                'expiry_date': forms.DateTimeInput(
                    attrs={'type': 'datetime-local', 
                           'class': 'form-control', },
                    format='your-desired-format'
                )
        }

Also add your desired format to your model field supported formats

input_formats = ['list-of-desired-formats']

For more details here: docs