Django form is being validated before it is submitted [duplicate] - python

This question already has an answer here:
Django: form validation errors on form creation
(1 answer)
Closed 3 years ago.
I have a custom bootstrap form which is displayed in the Dashboard. The problem is, that when the user goes to the dashboard, he sees validation errors right away, even though he did not submit the form yet (the text field should be required).
I do not understand why this is happening, any help is appreciated! :)
Picture of the problem (I do not want the red "The field is required" message to display now, only after submitting):
Form:
class MinitaskForm(forms.ModelForm):
class Meta:
model = Minitask
fields = ()
def __init__(
self,
data=None,
files=None,
auto_id="id_%s",
prefix=None,
initial=None,
error_class=ErrorList,
label_suffix=None,
empty_permitted=False,
instance=None,
use_required_attribute=None,
minitask=None,
):
super().__init__(
data,
files,
auto_id,
prefix,
initial,
error_class,
label_suffix,
empty_permitted,
instance,
use_required_attribute,
)
assert minitask is not None
self.fields["selected_choice"] = forms.CharField(
widget=forms.RadioSelect(
choices=[(val, val) for val in minitask.choices]
),
required=False,
label="Which of these emotions best describes your day?",
)
self.fields["reason"] = forms.CharField(label="Why?")
Views:
#login_required(login_url="login")
def dashboard(request):
task = request.user.current_weekly_task()
user_id = request.user.id
solutions = Solution.objects.filter(user_id=user_id)
minitask = request.user.current_minitask()
minitasks = request.user.minitask_set.filter(selected_choice__isnull=False)
if request.method == "POST":
if "minitask" in request.POST:
form = MinitaskForm(request.POST, minitask=minitask)
if "selected_choice" not in request.POST:
form.add_error("selected_choice", "Can't be blank")
messages.error(request, "You must pick your today's emotion")
if form.is_valid():
minitask.reason = request.POST["reason"]
minitask.selected_choice = request.POST["selected_choice"]
minitask.user = request.user
minitask.save()
messages.success(request, "Your daily status has been updated.")
return redirect("dashboard")
else:
mini_form = MinitaskForm(
minitask=minitask,
data={
"reason": minitask.reason,
"selected_choice": minitask.selected_choice,
},
)
return render(
request,
"app/dashboard.html",
{
"solutions": solutions,
"task": task,
"mini_form": mini_form,
"minitask": minitask,
"minitasks": minitasks,
"user": request.user,
},
)
Template:
<form method="POST" class="post-form">
{% csrf_token %}
{% bootstrap_form mini_form %}
<button
type="Submit"
id="minitask-button"
class="save btn btn-pink"
name="minitask"
>Save minitask</button>
</form>

In your views:
if request.method == "POST":
...
else:
mini_form = MinitaskForm(
minitask=minitask,
data={
"reason": minitask.reason,
"selected_choice": minitask.selected_choice,
},
)
According to the docs:
A Form instance is either bound to a set of data, or unbound.
Most of the times, a form gets its data from the user through a POST request. (bound form)
The GET request provides the user with the form in order to fill it with data. (unbound form)
Therefore, through a GET request, you need to provide the user with an unbound form.
In your code, you declare that if the request is not POST (a GET request is not POST), then return a bound form populated with data you programmatically provide.
This does not make sense.
Chances are that if you insert a breakpoint() after else, render the page with ./manage.py runserver and type in the prompt provided in the console:
minitask.reason == None the result will be True.
The above, mean that you bound your form with data that contain an empty reason which is not allowed.
If you want to provide initial data in your unbound form, you can do it using initial:
mini_form = MinitaskForm(
minitask=minitask,
initial={
"reason": minitask.reason,
"selected_choice": minitask.selected_choice,
},
)

Related

Create multiple choice field dynamically

My purpose is to create a django form to select devices filtered by country and club fields.
My form is this:
class MyForm(Form):
country = ChoiceField(choices=some_choices, initial=None)
club = CharField(widget=Select())
expiration_date = DateField()
sales_info = ChoiceField(choices=SALES_TYPES, initial=None)
devices = MultipleChoiceField(widget=CheckboxSelectMultiple(choices=[]))
def clean_devices(self):
devices = self.cleaned_data.get("devices")
if not devices:
raise ValidationError("At least one device must be selected.")
return devices
def save(self):
...
views.py
class MyFormView(LoginRequiredMixin, FormView):
template_name = "store/my_template.html"
form_class = MyFormView
def get_success_url(self):
return reverse('init_device')
def form_valid(self, form):
init_device = form.save()
if init_device:
return super().form_valid(form)
return super().form_invalid(form)
def form_invalid(self, form):
logger.error(form.errors)
my_template.html
<form action="{% url 'init_device' %}" method="post">
{% csrf_token %}
...
<select id="id_devices" name="devices" multiple>
I populate select field via javascript in this way:
let device_select = document.getElementById("id_devices");
serialIds.forEach(serialId => {
let option = document.createElement("option");
option.text = serialId;
option.value = serialId;
device_select.add(option);
});
I obtained filtered devices form db using a websocket by now I can't pass them to the form because this error raises:
ValueError: The view path.view didn't return an HttpResponse object. It returned None instead.
Then I print form.errors and this is showed <ul class="errorlist"><li>devices<ul class="errorlist"><li>Select a valid choice. device_attriubte is not one of the available choices.</li></ul></li></ul>
Can anyone help me?
Too much at once?
First get the user's choice of country and club and validate them as far as possible. When done, redirect to an URL such as
app/choose_device/country/club
and in the view which gets invoked, use kwargs country and club in the queryset of its form's ModelMultipleChoiceField. This normalizes to a list of model instances (device objects) when the form validates.
(I'm assuming that devices are objects in your DB. THis is implied by your use of "filtered" but isn't explicit).

Why does my Submit button renders a page that is blank when it is supposed to contain the data that was just updated?

I'm trying to update the values of my database using a HTML Form.
When I Click Edit it brings me to the edit the values above.
However as I am clicking the submit button, it returns me a database but with no other values.
Is there anyone that can help me understand what I did wrong and point me to the right documentation (if any)
editclaims.html:
<div class="arrange2">
<h1>Edit Claim Form - #{{claims.id}} </h1>
</div>
<form method="POST" action="/update/{{claims.id}}">
{% csrf_token %}
views.py:
def editclaims(request,id):
context = initialize_context(request)
user = context['user']
claims = SaveClaimForm.objects.get(id=id)
if request.method == 'POST':
name = request.POST['name']
email = request.POST['email']
claim = request.POST['claim']
claimtype = request.POST.get('claimtype')
description = request.POST['description']
receipt = request.FILES['receipt']
cheque = request.POST.get('Cheque')
form = SaveClaimForm(name=name, email=email, claim=claim, claimtype=claimtype, description=description, receipt=receipt, cheque=cheque)
form.save()
return render(request, "Login/editclaims.html", {'claims':claims, 'user':user})
urls.py:
urlpatterns = [
path('existingclaims/', views.viewclaims, name='existingclaims'),
path('editclaims/<int:id>', views.editclaims, name='editclaims'),
path('update/<int:id>', views.updateclaims, name='updateclaims'),
]
It may not resolve all your problems but it will be more readable as answer.
When you get data from HTML then you create new object SaveClaimForm and it will have new ID and you will have the same object in two rows.
You have to get original Claim from database and update values in this object and save it - and then it will save it with original ID and you will have only one `object in database
def editclaims(request,id):
context = initialize_context(request)
user = context['user']
# get original object
claims = SaveClaimForm.objects.get(id=id)
if request.method == 'POST':
# update original object
claims.name = request.POST['name']
claims.email = request.POST['email']
claims.claim = request.POST['claim']
claims.claimtype = request.POST.get('claimtype')
claims.description = request.POST['description']
claims.receipt = request.FILES['receipt']
claims.cheque = request.POST.get('Cheque')
# save it with original `ID`
claims.save()
return render(request, "Login/editclaims.html", {'claims':claims, 'user':user})
BTW:
Django has special class ModelForm to create forms in HTML. It may also have methods to check if data in HTML are correct - ie. if fields are not empty, if email is correctly constructed (name#domain.com), if phone has only numbers, etc. So using ModelForm can be more useful then writing all manually in code.

Dynamic ChoiceField unable to be validated in form

I have a form that's being given a dictionary of selection, it populates it correctly but on form submit it is not valid. When attempting to print errors, non_field_errors there are just blanks. When I am redirected to the form, now the choice field is populated by one choice and the csrf token from previous submit.
I've tried assigning choices in different ways such as self.fields['calendar'] = forms.ChoiceField(choices=choice_list) directly assign in a different way. self.fields['calendar'].choices = choice_list, a custom validator that ignores the validation, and inline debugging.
Form model:
class CalendarSelectionForm(forms.Form):
calendar = forms.ChoiceField(label="Calendar")
def __init__(self, calendars=None, *args, **kwargs):
super(CalendarSelectionForm, self).__init__(*args, **kwargs)
choice_list = [(calendar_id, calendar_name) for calendar_id, calendar_name in calendars.items()]
if calendars:
self.fields['calendar'].choices = choice_list
View:
if request.method == "POST":
print(request.POST)
cal_sync_form = CalendarSelectionForm(request.POST)
print("Non-field errors " + str(cal_sync_form.non_field_errors()))
print("Reg form errors " + str(cal_sync_form.errors))
# print("Field val " + str(cal_sync_form.calendar))
print("Field data " + str(cal_sync_form.data))
print("Field fields " + str(cal_sync_form.fields) + " Form is " + str(cal_sync_form.is_valid()))
if cal_sync_form.is_valid():
data = cal_sync_form.cleaned_data
print(data)
return render(request, 'management/gcal_sync_dashboard.html')
else:
return render(request, 'management/acct_select.html', {'form': cal_sync_form})
Form template:
<form class="form-import" action="/manage/gcal/sync/" method="post" id = "">
{% csrf_token %}
{{ form.calendar }}
{{ form.errors }}
{{ form.non_field_errors }}
<div class="push clearfix"></div>
<div class="col-sm-6 no-pad push"><input class="btn btn-brand btn-little button filson push-half" type="submit" value="Select email"><i class="fa fa-plus"></i></input>
</div>
</form>
The goal is to validate a posted form, the current print statements print out
<QueryDict: {'csrfmiddlewaretoken': ['sJHE8JJAzmeS0nRjaYZg5KdMlevJiInYY0G4YFJeITH1cVjciIdR1Dq1N28loUIL'], 'calendar': ['email#email.io']}>
Non-field errors
Reg form errors
Field data {}
Field fields OrderedDict([('calendar', <django.forms.fields.ChoiceField object at 0x117323080>)]) Form is False
In your view, you make a call to the CalendarSelectionForm constructor with request.POST as first positional argument. So that means that you call the __init__ function, and request.POST is passed as the calendars parameter.
You can fix this by constructing your form with named parameters. You furthermore will need to pass the same parameter to calendars as you did when you rendered the form with the GET request, since otherwise the choices do not per se match, and the user might have picked an option that is in that case not available during the POST request. Like:
if request.method == 'POST':
cal_sync_form = CalendarSelectionForm(calendars=my_calendars, data=request.POST)
# ...
with my_calendars the same value you pass when you constructed the form in the GET case.

invalid django form makes is_valid method always return false

My django form is invalid and so the .is_valid method never returns true. As a result, I am getting an "Expected HttpResponse but received None" type of error because my code never executes what is within the if-condition. I am wondering how to make my form valid. I am new to django so I am probably missing something obvious. Here is my code:
views.py
template_name1 = 'multiplication/detail.html'
template_name2 = 'multiplication/multiplied.html'
class myForm(forms.Form):
quantity1 = forms.IntegerField(required=False)
quantity2 = forms.IntegerField(required=False)
form = myForm()
def get(request):
return render(request,template_name1,{'form': form} )
def multiply_two_integers(x,y):
return x*y
def post(request):
if (form.is_valid()):
x = request.POST.get('quantity1')
y = request.POST.get('quantity2')
product = multiply_two_integers(x, y)
return render(request, template_name2, {'form': form, 'product':
product })
template_name1
<h1>Multiplication Function</h1>
<form action = "{% url 'multiplication:post' %}" method = "post">
{{ form.as_p }}
{% csrf_token %}
<input type = "submit" value ="Multiply">
<!--<button type="submit"> Multiply </button>-->
<h1>{{product}}</h1>
</form>
template_name2
<h1>{{product}}</h1>
urls/multiplication
from django.urls import path
from multiplication import views
app_name = 'multiplication'
urlpatterns = [
# /multiplication/
path('', views.get, name = 'get'),
path('multiplied', views.post, name='post')
]
This code is very strange. You seem to have a set of functional views, but are trying to randomly use some concepts from class-based views.
The reason why your form is not valid is because you never pass any data to it; an unbound form cannot be valid. You should not be instantiating the form outside of a view; you need to do it in the view, and when the request is a POST you should pass the POST data to it.
In function-based views you should not define separate functions for get and post. Combine them, as sown in the Django docs.
There is another point that you have missed about the error message; your reaction to it telling you that you have not returned a response if the form is invalid is to ask "why isn't it valid", but you should also do what it says and return a response in this case; the form will sometimes be actually invalid, and you should deal with this case.
Finally, to get the data from the form you should use form.cleaned_data, not request.POST.
def multiply_two_integers(x,y):
return x*y
def my_view(request):
if request.method == 'POST':
form = MyForm(request.POST)
if (form.is_valid()):
x = form.cleaned_data['quantity1']
y = form.cleaned_data['quantity2']
product = multiply_two_integers(x, y)
return render(request, template_name2, {'product': product })
else:
form = MyForm()
return render(request,template_name1,{'form': form} )

save() generating new entry instead updating Django

I am not understanding why, but an instance always get created copying the one I am trying to edit. Also, as I can see FormSet I am using does not have an "instance" parameter to be added to its constructor. Anyways, the problem is that an instance of both Offer and OfferItem gets generated when I am editing an object.
def manage_offer(request, number=None):
param_offer = Offer.objects.filter(id=number).first()
param_items = OfferItem.objects.filter(offer=param_offer).values()
if request.method == 'POST':
offer_form = OfferForm(request.POST, instance=param_offer)
item_formset = OfferItemFormSet(request.POST, initial=param_items)
if offer_form.is_valid() and item_formset.is_valid():
# User selected go back and correct something
if request.POST.get('back', False):
return render(request,
'offer_edit.html',
{
'forms': offer_form,
'formset': item_formset,
'offer_edit': True,
})
# Proceeds with either saving or submitting request
offer = offer_form.save(commit=False)
offer.tax = offer_form.cleaned_data['tax'].value
#Sotres items to be sent back to commit part
offer_items = []
#Gets the items from the form and stores them to conf. page
for item_in_formset in item_formset.forms:
item = item_in_formset.save(commit=False)
item.item_code = get_item_code(item_in_formset.cleaned_data['name'])
item.type = get_item_type(item_in_formset.cleaned_data['name'])
offer.update_total(item.calc_total())
# Adds items into list for invoice_ready page
offer_items.append(item)
# Request goes to confirmation page
if request.POST.get('proceed', False):
return render(request,
'offer_edit.html',
{
'offer_form': offer_form,
'item_formset': item_formset,
'offer_ready': True,
'offer': offer,
'items': offer_items,
})
# Passes confirmation page and saves offer
offer.save()
# Makes sure the value is correct by recalculating
offer.reset_total()
for obj_item in offer_items:
obj_item.offer = offer
offer.update_total(obj_item.calc_total())
#commits to DB
offer.save()
obj_item.save()
return render(request,
'offer_edit.html',
{
'success_add_offer': True,
'offer': offer,
},
)
# GET generates a blank or instanced page
else:
offer_form = OfferForm(initial=
{'company': Company.objects.filter(is_default=True).first(),
'tax': Tax.objects.filter(is_default=True).first()
}, instance=param_offer)
item_formset = OfferItemFormSet(initial=param_items)
return render(request, 'offer_edit.html',
{
'forms': offer_form,
'formset': item_formset,
'edit_offer': number,
})
Forms.py
class OfferItemForm(ModelForm):
class Meta:
model = OfferItem
# Some widgets and stuff ...
class RequiredFormSet(BaseFormSet):
def __init__(self, *args, **kwargs):
super(RequiredFormSet, self).__init__(*args, **kwargs)
for form in self.forms:
form.empty_permitted = False
OfferItemFormSet = formset_factory(OfferItemForm, formset=RequiredFormSet)
I found the problem: since I am using the same View function to either edit or add a new entry, on my template form I must make sure I am also identifying if I am editing or not, because the function takes a parameter in case I am editing. In that case I must change the URL in the Post form.
Just a silly mistake take took me a few hours to find out.

Categories

Resources