Django expects wrong format when validating modelform datefield - django

I have a page where the user inputs some dates in the dd/mm/yyyy format.
In my settings file I have this:
DATE_INPUT_FORMATS = ('%d/%m/%Y','%Y/%m/%d', '%Y-%m-%d',)
USE_TZ = True
USE_L10N = True
TIME_ZONE = 'Europe/Rome
I use the django bootstrap4 datetime picker plus
The field renders as such: (i apologize about the screenshot, i couldnt copy the html without it being escaped and it looked extra-messy)
The problem
The problem is that when i input dates in dd/mm/yyyy format, it uses the american format (mm/dd/yyyy) to validate them. So if i enter 27/12/2021 it will try to save [day 12, month 27, year 2021] and fail the validation. Preventing the formset from being saved.
What i don't understand is where in hell does django get the idea that it should use the american format to validate dates when I have set three separate DATE_INPUT_FORMATS and none of them are in the american format?
Here an image showing that on the page the date is collected in the right format
And the response : [{'start_date': ['Enter a valid date.'], 'end_date': ['Enter a valid date.']}]
Forms
this is where i am now:
class EducationForm(forms.ModelForm):
# def clean_start_date(self):
# print(self.cleaned_data['start_date'])
# return datetime.strptime((self.cleaned_data['start_date']), '%Y-%m-%d')
# def clean_end_date(self):
# return datetime.strptime((self.cleaned_data['end_date']), '%Y-%m-%d')
class Meta:
model = Education
fields = ['start_date', 'end_date', 'institution', 'title']
# localized_fields = ('start_date', 'end_date',)
widgets = {
'start_date': DatePickerInput(attrs={
'data-provide' : 'datepicker',
'class' : 'form-control my-date-picker',
# 'data-date-format' : "DD/MM/YYYY"
},
options={
"format": "DD/MM/YYYY",
}),
'end_date': DatePickerInput(attrs={
'placeholder': 'Ongoing',
'data-provide' : 'datepicker',
'class' : 'form-control my-date-picker',
# 'data-date-format' : "DD/MM/YYYY"
},
options={
"format": "DD/MM/YYYY",
}),
Template
In the template there's nothing particular going on. I just render the datetime fields as {{ field }}.

In the end the solution was adding USE_L10N = False to my settings file.
It appears that unless specified, django will disregard the contents of DATE_INPUT_FORMATS

Related

Testing Django Wagtail - assert that a child of the given Page type can be created under the parent, using the supplied POST data

I've defined a custom page model (a blog post) as a child of a parent model (a blog index page) and I want to test that the child can be created under its parent.
The BlogPage and BlogIndexPage models are copied from the wagtail "basic blog" example in the documentation, and works as expected.
I'm trying to follow the documentation but I get the following validation error:
AssertionError: Validation errors found when creating a cms.blogpage:
E date:
E This field is required.
E intro:
E This field is required.
E slug:
E This field is required.
E title:
E This field is required.
I suspect that I'm defining my fixture incorrectly, but I am not what the correct form is. Any help is greatly appreciated! Can someone explain why it isn't working?
fixture (apps.cms.tests.fixtures.blogPage.json):
[
{
"model":"wagtailcore.page",
"pk": 1,
"fields":{
"date":"2022-02-28",
"intro":"intro to the post...",
"slug":"slug/",
"title":"the title",
"body":"body of the post...",
"categories":[
1
],
"content_type": ["cms", "blogpage"],
"depth": 2
}
},
{
"model": "cms.blogpage",
"pk": 1,
"fields": {}
}
]
the test class (apps.cms.tests.test_pages.py):
class MyPageTests(WagtailPageTests):
def setUp(self):
self.login()
page = BlogIndexPage(title="Home page", slug="home", path="foo", depth=1)
page.save()
def test_create_blog_post(self):
cwd = Path.cwd()
root_page = BlogIndexPage.objects.first()
with open(f"{cwd}/lettergun/apps/cms/tests/fixtures/BlogPage.json") as json_file:
fixture = json.load(json_file)
# Assert that a ContentPage can be made here, with this POST data
self.assertCanCreate(root_page, BlogPage, nested_form_data(fixture))
the models (apps.cms.models.py):
class BlogIndexPage(Page):
template = "blog.html"
intro = models.TextField(blank=True)
def get_context(self, request):
# Update context to include only published posts, ordered by reverse-chron
context = super().get_context(request)
blogpages = self.get_children().live().order_by("-first_published_at")
context["blogpages"] = blogpages
return context
content_panels = Page.content_panels + [FieldPanel("intro", classname="full")]
class BlogPageTag(TaggedItemBase):
content_object = ParentalKey("BlogPage", related_name="tagged_items", on_delete=models.CASCADE)
class BlogPage(Page):
template = "blog-post.html"
date = models.DateField("Post date")
intro = models.CharField(max_length=250)
body = RichTextField(blank=True)
tags = ClusterTaggableManager(through=BlogPageTag, blank=True)
categories = ParentalManyToManyField("cms.BlogCategory", blank=True)
def main_image(self):
gallery_item = self.gallery_images.first()
if gallery_item:
return gallery_item.image
else:
return None
search_fields = Page.search_fields + [
index.SearchField("intro"),
index.SearchField("body"),
]
content_panels = Page.content_panels + [
MultiFieldPanel(
[
FieldPanel("date"),
FieldPanel("tags"),
FieldPanel("categories", widget=forms.CheckboxSelectMultiple),
],
heading="Blog information",
),
FieldPanel("intro"),
FieldPanel("body"),
InlinePanel("gallery_images", label="Gallery images"),
]
The last argument to self.assertCanCreate is a dictionary of HTTP POST data to be submitted to the 'create page' admin view. This is an entirely different thing to a fixture (which is a representation of the page data as stored in the database), and the data structures are not compatible.
At its simplest, the POST dictionary can just consist of the required fields date, intro, slug and title:
self.assertCanCreate(root_page, BlogPage, {
'date': '2022-02-28',
'intro': "intro to the post...",
'slug': 'my-blog-page',
'title': 'My blog page',
})
The nested_form_data helper is only needed if your test is creating a page with data that's more complex than a flat list of fields - for example, if you wanted your page data to contain some gallery images, you'd need to use it along with the inline_formset helper. As you have an InlinePanel on your page, you need to account for that even if you're not passing it any data - see https://stackoverflow.com/a/71356332/1853523 for details.

Django - How to filter and return data by groups

I have a model which I want to return group by an attribute of the object itself. Let's suppose the model is the next one:
class User():
username = models.CharField(max_length=50, unique=True)
group = models.CharField(max_length=20)
Later in the view, I would be getting by group all the users:
group1 = User.objects.filter(group='1')
group2 = User.objects.filter(group='2')
group3 = User.objects.filter(group='3')
But that would return for each group the next structure:
[{"username":"user1", "group":"1"}]
[{"username":"user2", "group":"2"}]
[{"username":"user3", "group":"3"}]
How can I obtain the next structure (where the group is the root) directly from the filter or how can I combine the groups to achieve that:
[
"1": [{"username":"user1","group":"1"}],
"2": [{"username":"user2","group":"2"}],
"3": [{"username":"user3","group":"3"}]
]
I assume you don't have a Group model, just a field in the User model. I am not a Django pro, so I don't know of any Django tricks which could help you to get to your goal - though I am certain Django ORM has some functions which will perform a GROUP BY query for you. But why don't you write a completely pythonesque solution, simply transforming the collection you already have into one you want?
update following the comment
try something like this script:
from functools import reduce
users = [
{'username': 'foo', 'group':'1'},
{'username': 'bar', 'group':'2'},
{'username': 'baz', 'group':'1'},
{'username': 'asd', 'group':'2'},
{'username': 'zxc', 'group':'3'},
{'username': 'rty', 'group':'1'},
{'username': 'fgh', 'group':'2'},
{'username': 'vbn', 'group':'3'},
]
def reducer(acc,el):
group = el['group']
if not acc.get(group):
acc[group]=[el.get('username')]
else:
acc[group].append(el['username'])
return acc
print(reduce(reducer, users, {}))
It will print the result, which looks close to what you want:
{'1': ['foo', 'baz', 'rty'],
'3': ['zxc', 'vbn', 'vbn'],
'2': ['bar', 'asd', 'fgh']}

Django extract field form html

I have a html page that returns data in a form. The forms is a multiple choice checkbox field with "name=states1". When I run print(vars(self)) in my forms.py I see that the data is there ['MD', 'WV', 'WY'] (see below) however, when I try to extract it with print(self.data['states1']) I just get 'WY' and print(len(self.data['states1'])) returns 2.
{'instance': <AA: AAobject>, '_validate_unique': False, 'is_bound': True, 'data': <QueryDict: {'csrfmiddlewaretoken': ['AAAAA']: ['1'], 'states1': ['MD', 'WV', 'WY'], 'states': [''], 'price': ['2.00'], 'user': ['1', '1']}>, ...
However, when I run
import json
print(json.dumps(self.data, indent=2))
I get the following output
{
"csrfmiddlewaretoken": "AAAAA",
"roles": "1",
"states1": "WY",
"states": "",
"price": "2.00",
"user": "1"
}
The data is just gone. Why is my data being restricted to the last field, when I can clearly see it when looking at vars?
forms.py
class UserProfileChangeForm(forms.ModelForm):
states = forms.CharField(widget=USStateSelect(), initial='TX', required=False)
class Meta:
model = SkilledLaborer
fields = ['user','roles','travel_flag','price','states']
def clean_states(self):
print('states')
states = self.cleaned_data['states']
print(type(vars(self)))
print(vars(self).keys())
print(vars(self))
print(self.data.keys())
print(self.data['states1'])
print(len(self.data['states1']))
import json
print(json.dumps(self.data, indent=2))
try:
self.data['states1']
except:
pass
return states

Form Validation Reversed

Django Version 1.10.5
I can't find this answer in the documentation or the code source. Using the example form below I am expecting my form validation on submit to run in the order of which I have defined.
So validators.RegexValidator, validators.MinLengthValidator, etc... would run in that order. However when submitting the form it would appear that the validators are running in a reversed order.
Where validate_status, validate_employee_id, etc... would run.
Is this expected?
class FormLogin(forms.Form):
# Employee ID
employee_id = forms.CharField(
label=_('employeeId'),
required=True,
widget=forms.TextInput(
attrs={
'id': 'employee_id',
'placeholder': _('employeeId'),
'class': 'form-control'
}
),
validators=[
validators.RegexValidator('^[1-9][0-9]*$', _('validatorStartsWithZero'))
validators.MinLengthValidator(1),
validators.MaxLengthValidator(20),
validate_employee_id,
validate_status
]
)
Currently I have 1 user with the ID of 1.
When I submit the form using 01, the validate_status validator is taking over and returning that a user doesn't even exist. I would have expected the validators.RegexValidator to be returned first because it has a 0 in front.
If I reversed the entire order of validators, then the validation appears to work in the order I wanted. But now code readability isn't clear that's what is actually happening.
Edit 1 Cleaned up example with more information
I wrote this little test code to reproduce this.
from django import forms
def validate_1(value):
print('RUNNING VALIDATOR 1')
raise ValidationError(
'validation error 1',
params={'value': value},
)
def validate_2(value):
print('RUNNING VALIDATOR 2')
raise ValidationError(
'validation error 2',
params={'value': value},
)
class FormLogin(forms.Form):
# Employee ID
employee_id = forms.CharField(validators=[
validate_1,
validate_2
])
Running it:
>>> f = FormLogin({'employee_id': '01'})
>>> f.is_valid()
RUNNING VALIDATOR 1
RUNNING VALIDATOR 2
False
>>> f.errors
{'employee_id': ['validation error 1', 'validation error 2']}
>>>
As you can see, the validators where executed in descending order.
I assume that one of your self written validators does not properly throw a ValidationError so that the list of errors get's messed up or that you do not properly render the errors in your template.
Normally all validators will run and every validation error will be appended to the list of errors. But they run in descending order.

Testing related page in Wagtail

I've got a ContentPage model in wagtail and a RelatedPost model that links other ContentPage models to ContentPage a bit like this:
class ContentPage(Page):
summary = RichTextField(blank=True)
body = RichTextField(blank=True)
published = models.DateTimeField(default=timezone.now())
content_panels = Page.content_panels + [
FieldPanel('summary'),
FieldPanel('body', classname="full"),
InlinePanel('related_page', label="Related Content"),
]
settings_panels = Page.settings_panels + [
FieldPanel('published'),
]
class RelatedPost(Orderable):
post = ParentalKey(
'ContentPage',
related_name='related_page'
)
page = models.ForeignKey(
'ContentPage',
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="+"
)
panels = [
FieldPanel('page')
]
When I run this test:
class ContentPageTests(WagtailPageTests):
def test_can_create_article_page(self):
self.assertCanCreateAt(ContentIndexPage, ContentPage)
# content_index is just a parent page
content_index = self.create_content_index_page()
self.assertCanCreate(content_index, ContentPage, {
'title': 'Test Article',
'published': datetime.datetime.now()
})
I get an error saying:
django.core.exceptions.ValidationError: ['ManagementForm data is missing or has been tampered with']
The admin works fine. I can save related pages etc and when I comment out the InlinePanel line it works fine.
The data passed to assertCanCreate needs to match the format of a form submission being posted to the 'edit page' form in the Wagtail admin. For a child model in an InlinePanel, Wagtail handles this with a Django formset - see https://docs.djangoproject.com/en/1.10/topics/forms/formsets/#formset-validation - and so you need to supply all the fields that the Django formset logic would expect, including the management form. The simplest case that passes validation is a management form that simply reports that there are no child forms:
self.assertCanCreate(content_index, ContentPage, {
'title': 'Test Article',
'published': datetime.datetime.now(),
'related_page-TOTAL_FORMS': 0,
'related_page-INITIAL_FORMS': 0,
'related_page-MAX_NUM_FORMS': 999,
})