Django formset fails date validation with valid date - django

I'm running into a date validation error with use of a django formset. I do not get the same validation error when I formset.is_valid(). The problem I'm experiencing is that the form is_valid check fails, only with the view and template use (not in the shell) specifically when using a date in the form of "March 20 2018" whereas it always passes with "2018-03-20".
Also I can verify the data is in the request.POST but the invalid due_date key is missing from self.cleaned_data when I look for it in the form's clean method. Perhaps that's normal given the invalid key but I would expect that to occur after the clean, not before, if at all. Feels like maybe its a django bug, I'm on django 2.0.2
Here's a summary of construction, its pretty vanilla:
# models.py
class Schedule(models.Model):
# ...
name = models.CharField(max_length=256)
status = models.CharField(max_length=16, default=choices.NOT_STARTED, choices=choices.SCHEDULE_STATUSES)
due_date = models.DateField(blank=True, null=True)
# ...
# forms.py
class ScheduleForm(forms.ModelForm):
class Meta:
model = models.Schedule
fields = ['name', 'user', 'status', 'due_date']
# views.py
def line_schedules_edit(request, line_slug):
line = get_object_or_404(models.Line, slug=line_slug)
queryset = line.schedules.all()
ScheduleFormSet = modelformset_factory(models.Schedule, form=forms.ScheduleForm)
if request.method == 'POST':
schedules_formset = ScheduleFormSet(request.POST)
if schedules_formset.is_valid():
schedules_formset.save()
return HttpResponseRedirect(reverse('products:line-schedules-edit',
kwargs={'line_slug': line_slug}))
else:
schedules_formset = ScheduleFormSet(queryset=queryset)
context = {
'line': line,
'formset': schedules_formset
}
return render(request, 'line-schedules-edit.html', context)
# template
{{ formset.management_form }}
{% csrf_token %}
{% for form in formset.forms %}
{% for hidden in form.hidden_fields %}{{ hidden }}{% endfor %}
{% for field in form.visible_fields %}
{{ field.errors }}
{{ field }}
{% endfor %}
{% endfor %}
With this structure I continually get an error of invalid date for due date when I use "March 3 2018" whereas if I provide a form input of "2018-03-18" in the browser, it works. Yet in a shell I'm able to verify that both date formats work:
In [35]: POST = {
'form-TOTAL_FORMS': '2',
'form-INITIAL_FORMS': '0',
'form-MAX_NUM_FORMS': '2',
'form-0-name': 'Test',
'form-0-status': 'Not started',
'form-0-due_date': '2018-03-20',
'form-1-name': 'Test',
'form-1-status': 'Not started',
'form-1-due_date': 'March 20, 2018'
}
In [36]: qdict = QueryDict('', mutable=True)
qdict.update(POST)
formset = ScheduleFormSet(qdict)
In [37]: formset.is_valid()
Out[37]: True
Why does the view and template fail the validation and why is the due_date key missing in the form's clean method?

Turns out all I needed to do was to provide input formats to pre-process the format before its sent off to the model. It must have been built-in model validation failing since it cannot store it in the form of "March 2 2018".
Using input_formats in the form, we can cast it to the desired format before the model processes it:
class ScheduleForm(forms.ModelForm):
class Meta:
model = models.Schedule
fields = ['name', 'user', 'status', 'due_date']
due_date = forms.DateField(widget=forms.DateInput(format='%d %B, %Y'),
input_formats=('%d %B, %Y',),
required=False)

Related

Choice Field Instance Not Displaying Correct Data But Other Fields Are

I am trying to display a ModelForm with prepopulated instance data.
It works fine except for the ChoiceField which always displays the first choice given in forms.py ('LP') rather than the choice provided by the instance.
View:
def review(request):
order = Order.objects.get(user__pk=request.user.id)
form = ProjectReviewForm(instance=order)
context = {
'form': form,
}
return render(request, 'users/projectreview.html', context)
Forms:
class ReviewForm(forms.ModelForm):
LAND = 'LP' // #INSTANCE ALWAYS SHOWS THIS RATHER THAN INSTANCE DATA
DATA = 'DC'
STATIC = 'SW'
CHOICES = (
(LAND, 'LP'),
(DATA, 'DC'),
(STATIC, 'SW')
)
product = forms.ChoiceField(choices=CHOICES, widget=forms.Select(attrs={'class': 'form-field w-input'}),)
class Meta:
model = Order
fields = '__all__'
template:
<form method="POST" class="contact-form">
{% csrf_token %}
<h2 class="form-heading-small">Please make sure everything you've submitted is correct.</h2>
{{ form }}
<button type="submit" data-wait="Please wait..." class="button w-button">Looks good!</button>
</form>
The product field on the form is overriding the field on the model. Look into using a ModelChoiceField

Django form POST resulting in 404

I am attempting to take in a form for update or delete, run the process and either return the updated url, or the updated list of objects. I've got the dynamic url building working, but when I hit submit I get a 404. I am struggling with the how to process the POST, as it doesn't even seem to be hitting that far in the code. Code below:
urls.py
path("customers/", views.customers, name="customers"),
path("customers/customer/<int:id>/", views.customer),
forms.py
class CustomerMaintForm(ModelForm):
class Meta:
model = AppCustomerCst
fields = ('id_cst', 'is_active_cst', 'name_cst', 'address_1_cst', 'address_2_cst', 'address_3_cst',
'city_cst', 'state_cst', 'zip_cst', 'country_cst', 'salesrep_cst', 'type_cst',
'is_allowed_flat_cst', 'iddef_cst', 'date_created_cst', 'date_suspended_cst',
'date_first_tran_cst', 'date_last_tran_cst', 'is_credit_hold_cst',
'old_balance_cst', 'balance_notify_cst', 'balance_statement_cst',
'balance_conversion_cst', 'balance_cst', 'receive_emails_cst',
'contact_domain_cst'
)
labels = {'id_cst': 'Customer ID', 'is_active_cst': 'Active?', 'name_cst': mark_safe('<p>Name'),
'address_1_cst': 'Address 1',
'address_2_cst': 'Address 2', 'address_3_cst': 'Address 3', 'city_cst': 'City', 'state_cst': 'State',
'zip_cst': 'Zip', 'country_cst': 'Country', 'salesrep_cst': 'Sales Rep', 'type_cst': 'Type',
'is_allowed_flat_cst': 'Allowed Flat?', 'iddef_cst': mark_safe('<p>Id'),
'date_created_cst': 'Created Date', 'date_suspended_cst': 'Suspended Date',
'date_first_tran_cst': 'First Tran Date', 'date_last_tran_cst': 'Last Tran Date',
'is_credit_hold_cst': 'Credit Hold?', 'old_balance_cst': 'Old Balance',
'balance_notify_cst': 'Balance Notify', 'balance_statement_cst': 'Balance Statement',
'balance_conversion_cst': 'Balance Conversion', 'balance_cst': 'Current Balance',
'receive_emails_cst': 'Receive Emails?', 'contact_domain_cst': mark_safe('<p>Contact Domain')}
views.py
def customer(request, id):
if request.method == "GET":
obj = AppCustomerCst.objects.get(id_cst=id)
instance = get_object_or_404(AppCustomerCst, id_cst=id)
form = CustomerMaintForm(request.POST or None, instance=instance)
ContactFormSet = modelformset_factory(AppContactCnt, can_delete=True, fields=(
'name_cnt', 'phone_cnt', 'email_cnt', 'note_cnt', 'receives_emails_cnt'))
formset = ContactFormSet(queryset=AppContactCnt.objects.filter(idcst_cnt=id), prefix='contact')
tpList = AppCustomerTpRel.objects.filter(idcst_rel=id).select_related('idcst_rel', 'idtrp_rel').values(
'idtrp_rel__id_trp', 'idtrp_rel__tpid_trp', 'idtrp_rel__name_trp', 'sender_id_rel', 'category_rel',
'cust_vendor_rel')
TPFormSet = formset_factory(TPListForm, can_delete=True)
tp_formset = TPFormSet(initial=tpList, prefix='tp')
doc_list = DocData.objects.document_list(id)
DocFormSet = formset_factory(DocumentListForm)
DocFormSet = formset_factory(DocumentListForm)
doc_formset = DocFormSet(initial=doc_list, prefix='doc')
context = {'form': form, 'formset': formset, 'tp_formset': tp_formset, 'doc_formset': doc_formset, 'id': id}
print(form.errors)
return render(request, 'customer.html', context=context)
elif '_edit' in request.POST:
print(id, request.POST)
cust_name = request.POST['name_cst']
instance = get_object_or_404(AppCustomerCst, name_cst=cust_name)
form = CustomerMaintForm(request.POST, instance=instance)
if form.is_valid():
form.save()
return HttpResponseRedirect("/customers/customer/{id}")
else:
context = {'form': form, 'contact_form': contact_form}
return redirect(request, 'customer.html', context=context)
elif '_delete' in request.POST:
cust_name = request.POST['name_cst']
instance = get_object_or_404(AppCustomerCst, name_cst=cust_name)
form = CustomerMaintForm(request.POST, instance=instance)
if form.is_valid():
AppCustomerCst.objects.filter(id_cst=id).delete()
return render(request, 'customers.html')
else:
pass
customer.html
{% extends 'base.html' %}
{% load crispy_forms_tags %}
{% url customer string %}
{% block content %}
<form method="post" action="/customer">
{% csrf_token %}
<div style="height:300px;overflow:auto;">
{{ form }}
</div>
<input type="submit" value="Edit" name="_edit">
<input type="submit" value="Delete" name="_delete">
</form>
{% endblock %}
The 404 is because the form sends a POST to /customer and your URLs are
/customers/
/customers/customer/<int:id>/
So I'd first add a name to your path so it becomes something like path("customers/customer/<int:id>/", views.customer, name='customer')
Then change your form action to use django URL reversal;
<form method="post" action="{% url 'customer' id=id %}">
By doing this it'll generate the URL for you based on the ID of the customer form you're on, assuming id is in the context which it appears to be from your view code.
That should solve the 404 and improve things a little.

Django form not rendering input fields in template

When creating a form bounded to a model in django, it does not render at all.
I have the following model:
class Recipe(models.Model):
title = models.CharField(max_length=200)
text = models.TextField()
created_date = models.DateTimeField(
default=timezone.now)
url = models.TextField(validators=[URLValidator()], blank = True)
photo = models.ImageField(upload_to='photos', blank = True)
def publish(self):
self.created_date = timezone.now()
self.save()
def __str__(self):
return self.title
The form:
class RecipeForm(forms.ModelForm):
class Meta:
model = Recipe
fields = ['title', 'text']
The view:
def recipe_new(request):
form = RecipeForm()
return render(request, 'recipe_edit.html', {'form:': form})
The template:
{% extends 'base.html' %}
{% block content %}
<h1>New recipe</h1>
<form method="post" class="recipe-form">{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="save btn btn-default">Save</button>
</form>
{% endblock %}
But only the title "New recipe" and "Save" button is rendered. If I try to print the form variable in my terminal, it prints out correctly. However, the response to the request always comes without the input fields (whether I use form.as_p or just form). Am I passing the form to the template incorrectly or is the form itself wrong?
You wrote:
{'form:': form}
notice the colon (:) in boldface in the key (you write the colon twice, once in the key, and once as a key-value separator). As a result you pass a variable with a name that has a colon in it. Changing the template might help, but I would strongly advice against it, since such characters have typically a specific meaning for template filters, etc. I would therefore advice to only use valid Python identifiers.
Since the name of the variable was form:, using form in the template did not make any sense, since - according to the Django render engine - that variable does not exists, so it resolves it with the string_if_invalid setting of the render engine (by default the empty string).
It should be:
def recipe_new(request):
form = RecipeForm()
return render(request, 'recipe_edit.html', {'form': form})

Django template isn't rendering dynamic form errors

I have a Django 1.8 form that contains a paragraph tag that renders either some feedback or a question submitted by a user. It also contains a textarea input 'response_text' and a pair of radio buttons 'close_issue'. This response input can be used to send an optional response to the user. If the user submitted some feedback, the admin should be able to click the 'close issue' radio button and submit the form with no response. However, if the textarea input contains a question, then the form should render an error telling the admin that he/she can't submit the form without typing an answer into the response input. The problem I'm having is that I can't get the form to cause the template to render an error message if the user submitted a question but the admin didn't type in a response. My view, model, form, and template are shown below. forms.py shows all the ways (all commented out) I have tried to make the response input field required if the user submitted a question so that the template will display an error. I also tried overriding the default 'clean' method with one that would raise a ValidationError if the user submitted a question and the response input is blank but that didn't work either. Can anyone tell me what I'm doing wrong?
Thanks.
# view.py
def review_feedback_or_question(request, template, *args, **kwargs):
fqid = kwargs['fqid']## Heading ##
submission = FeedbackQuestion.objects.get(pk=fqid)
if request.method == 'POST':
form = FeedbackQuestionResponseForm(request.POST, submission=submission)
if form.is_valid():
# process the form
return redirect('review-feedback-or-question-queue')
else:
pass
form = FeedbackQuestionResponseForm(submission=submission)
context = {'form': form, 'submission': submission,}
return render(request, template, context)
# models.py
class FeedbackQuestion(models.Model):
SELECT = ''
FEEDBACK = 'feedback'
QUESTION = 'question'
SUBMISSION_TYPE_CHOICES = (
(SELECT , '-- Select --'),
(FEEDBACK, 'Feedback'),
(QUESTION, 'Question'),
)
user = models.ForeignKey(User, related_name="user")
submission_type = models.CharField(max_length=8,
choices=SUBMISSION_TYPE_CHOICES,
default=SELECT)
submission_text = models.TextField()
date_submitted = models.DateTimeField(auto_now_add=True)
response_text = models.TextField()
respondent = models.ForeignKey(User, related_name='respondent')
date_responded = models.DateTimeField(auto_now=True)
issue_closed = models.BooleanField(default=False)
class Meta:
db_table = 'feedback_question'
# forms.py
class FeedbackQuestionResponseForm(forms.Form):
TRUE = 1
FALSE = 0
BLANK = ''
CHOICES = ( (TRUE, 'Yes'), (FALSE, 'No') )
response_text = forms.CharField(
required=False,
label='',
widget=forms.Textarea(attrs={'placeholder': 'Enter response...'}))
close_issue = forms.TypedChoiceField(
choices=CHOICES,
label='Close this issue?',
widget=forms.RadioSelect(renderer=HorizontalRadioRenderer),
coerce=int)
def __init__(self, *args, **kwargs):
if 'submission' in kwargs:
submission = kwargs.pop('submission')
if submission.submission_type == 'question':
# NONE OF THESE WORKED!
#self.fields.get('response_text').required = True
#self.declared_fields['response_text'].required = self.TRUE
#self.declared_fields['response_text'].required = self.TRUE
#self.declared_fields['response_text'].required = True
#self._errors['response_text'] = "You must enter a response"
pass
super(FeedbackQuestionResponseForm, self).__init__(*args, **kwargs)
# template.html
<p>{{ submission.submission_text }}</p>
<form action="" method="post">{% csrf_token %}
{{ form.non_field_errors }}
{% if form.errors %}
{% if form.errors.items|length == 1 %}
Please correct the error below.
{% else %}
Please correct the errors below.
{% endif %}
</p>
{% endif %}
{{ form.response_text.errors }}
{{ form.response_text.label_tag }} {{ form.response_text }}
{{ form.close_issue.errors }}
{{ form.close_issue }} {{ form.close_issue.label_tag }}
<input type="submit" value="Submit" class="" />
</form>
You're not passing submission into the form when you instantiate it on POST, so the required attribute is never being set.
Daniel Roseman was correct in that I need to pass 'submission' into the form when I instantiate the form on POST. But there were still two other problems. First, I need to instantiate the form inside the else block. If this isn't done and the form doesn't validate, then you're passing an unbound form back to the viewer and any errors won't be displayed. Also, it isn't necessary to pass 'submission' to the form when you instantiate it here:
...
else:
form = FeedbackQuestionResponseForm()
context = {...}
...
The next problem was that the order of my statements inside the init method was incorrect. It appears that I needed to execute 'super()' before trying to reference the 'response_text' field. I'll need to locate and study this method in the Django source code to understand exactly why. In any case, this works:
def __init__(self, *args, **kwargs):
if 'submission' in kwargs:
submission = kwargs.pop('submission')
else:
submission = False
super(FeedbackQuestionResponseForm, self).__init__(*args, **kwargs)
if submission:
if submission.submission_type == 'question':
self.fields['response_text'].required = True
else:
self.fields['response_text'].required = False
When the above changes are implemented, the form will make the response_text field required if the user submits a question and an error will be displayed if the admin doesn't enter a response before submitting the form. Many thanks again to Daniel for getting me back on track towards finding a solution.

Using forms with non-string objects

I have pretty complicated form items ;)
Here's my view for serving my form:
def view(request):
if request.method == "POST":
form = forms.ProfileEditForm(request.POST)
else:
form = forms.ProfileEditForm(initial={'country': request.user.profile.country})
return direct_to_template(request, "name/of/template.html",
{"form": form, "countries": Country.objects.all()})
Here's what the country model looks like:
class Country(models.Model):
iso2 = models.CharField()
iso3 = models.CharField()
name = models.CharField()
Here's what my form's clean() method looks like (highly simplified):
def clean(self):
self.cleaned_data['country'] = Country.objects.get(iso2=self.cleaned_data['country'])
return self.cleaned_data
Here's how I'm rendering it in a template:
<select name="country"{% if form.country.value %} data-initialvalue="{{form.country.value.iso2}}"{% endif %}>
{% for country in countries %}
<option value="{{country.iso2}}"{% if country.iso2==form.country.value.iso2 %} selected{% endif %}>{{country.name}}</option>
{% endfor %}
</select>
However, I'm noticing that what actually ends up being delivered to my template isn't a Country object, but a string of my input data, such as "US". When I initially render my template with the initial data, things look fine, but when validation fails, things get messed up. What should I do, and what am I doing wrong?
Just use ModelChoiceField for your country field.
And don't forget to define __unicode__ method of the model.