Django - differences between using request.GET and request.POST - django

From all the HTML books i've read, I know that POST should be used when changing, adding or removing from the database and when handling sensitive information like passwords. GET should be used when you just want to search through a database without making any changes. With that said, I am reading a book on Django and up until now, to handle forms, we did it like this:
def RegistrationFormView(request):
form = RegistrationForm()
if request.method == "POST": #if the user has clicked the 'submit' button on the form and is sending data
form = RegistrationForm(request.POST)
which makes sense. The book goes on to teach how to create a search page which searches through the database. For this, we use GET, which makes sense. This is the form:
class SearchForm(forms.Form):
query = forms.CharField(
label='Enter a keyword to search for',
widget=forms.TextInput(attrs={'size': 32})
)
But this is the view (and this is what confused me):
def search_page(request):
form = SearchForm()
bookmarks = []
show_results = False #Only show results if the user has searched something
if request.GET.has_key('query'): #check if the user submitted GET data
show_results = True #return results since the user has submitted GET data
query = request.GET['query'].strip()
if query:
form = SearchForm({'query' : query})
I want to clarify four things here.
1) Would it be exactly the same if I just did
if request.method == "GET":
instead of
if request.GET.has_key('query'):
2) in the line
if request.GET.has_key('query'):
according to the Djangobook, it says "has_key Returns True or False, designating whether request.GET or request.POST has the given key." Now, what exactly is a 'key'? Is a key a field in the form, and
if request.GET.has_key('query'):
checks to see if the user has filled out the formField which is called 'query'?
3) Am I allowed to call form.is_valid() when the method is GET? Because what I was thinking was doing
form = SearchForm(request.GET)
if form.is_valid():
query = form.cleaned_data['query']
Is that allowed?
4) why does the book do
if query:
after
query = request.GET['query'].strip()
? Doesn't the line
if request.GET.has_key('query'):
already verify that the 'query' field is filled in?

No. if request.method == "GET": is in no way equivalent to if request.GET.has_key('query'):
request.GET and request.POST are dictionary subclasses and has_key is part of the built-in dictionary interface http://docs.python.org/2/library/stdtypes.html#dict.has_key however it is deprecated in favor of 'query' in request.GET.
Forms do not care about the request method or that there is a request at all. Forms validate dictionaries whatever the source might be.
In the case of ?query= or ?query=%20 the key query would evaluate to '' and ' ' which would both be False after running through strip(). if request.GET.has_key('query'): only checks that the key is present and does not look at the value.

Related

Form Validation Error when a required field is remained blank in django

I want to have errors as a label above a field if it is not filled.
This is my views.py:
#login_required(login_url='user_profile:login')
def NewWriting(request):
if request.method=="POST":
form=WritingForm(request.POST)
if form.is_valid():
post=form.save(commit=False)
post.author=request.user
post.save()
return redirect('user_profile:index')
else:
form = WritingForm()
subject = Subject.objects.all()
return render(request,'user_profile/writing_form.html', {'form':form , 'subject':subject})
what should I add to my code?
Thanks
Without seeing your form class ...
Option 1:
If you really want the user to be able to submit the form with empty data and then specifically show them that error using the form, set the required=False kwarg for the specific field in your WritingForm class. Then override the clean_<fieldname> (link) method and then you could do:
def clean_<fieldname>:
if self.cleaned_data['<fieldname>'].strip() == '':
raise ValidationError('This field cannot be blank!')
return self.cleaned_data['<fieldname>']
Replacing <fieldname> with whatever that fieldname is.
Option 2:
The default for any form is to make all fields required (IE: required=True kwarg on the field). So in general, if the field is required most browsers will at least move the cursor to the empty field and won't allow the form to be submitted while there is no data in the field.
You also need to return a bound form in the case where form.is_valid() returns False or you won't ever see the errors (right now you don't return anything if the form is invalid). Please see the django docs here for a common functional view pattern using forms.
You need to add another all to render if the form is not valid, and in your template, you need to make use of form.errors. Something like this should work so that form validation errors are then passed back to the UI/template for display to the user:
#login_required(login_url='user_profile:login')
def NewWriting(request):
form = None
if request.method=="POST":
form=WritingForm(request.POST)
if form.is_valid():
post=form.save(commit=False)
post.author=request.user
post.save()
return redirect('user_profile:index')
if form is None:
form = WritingForm()
subject = Subject.objects.all()
return render(request,'user_profile/writing_form.html', {'form':form , 'subject':subject})

what exactly does django request.POST do and how to use it?

I'm starting to learn django and started watching tutorials on how to create forms and i've seen a lot of places where the form is created like this.
def create(request):
if request.POST:
form = ArticleForm(request.POST)
if form.is_valid:
form.save()
else:
form = ArticleForm()
args = {}
args.update(csrf(request))
args['form'] = form
return render_to_response('create_article.html', args)
Now, assuming that I created a model called Article and then created an ArticleForm from that model, what exactly is going on here (in the code I provided above)? I understand the if form.is_valid: form.save() part, and according to what I read, request should always be the first parameter, but can someone explain what request as a parameter does and what the first two lines of the function are doing? And what exactly is going on in the else statement and after the else statement (the args part)?
EDIT: Also, suppose the Article model has a field called name = models.CharField(max_length=20), is there a way for me to get / access what the user entered for that particular section of the form? Suppose I want to get the name and see if the name already exists in my database, would there be a way for me to do that?
request.POST among other things (like the CSRF token value) contains all the data the user has entered in the form.
if request.POST
checks if the user actually validated the form, otherwise there is no POST data in the request.
form = ArticleForm(request.POST)
looks strange at first but when the user validates the form, the same page is loaded but the POST data is processed in the django form for data validation (like checking if a required field was left blank, etc…) in order to display errors in the form.
If there is no error (form.is_valid()) then the view program continues.
I hope you are familiar with HTTP methods like GET and POST.
request object represents a single request by any user agent. So it can be a request that's sent from browser from you when you browse a particular page or from a crawler from a search engine. Read more about request here
request.POST is an attribute of this request object, it's a QueryDict (much similar to a normal Python dict). It contains the HTTP POST parameters that are sent to your view.
In short in your example:
def create(request):
if request.POST: # check if the request is POST request and it contains any parameter
form = ArticleForm(request.POST) # then pass all those parameters to the form
if form.is_valid: # process the form to check if it's valid
form.save() # save the data if it's valid
else:
form = ArticleForm() # if not valid data, initialize an new / empty form
args = {} # create a dict to pass to the template
args.update(csrf(request)) # add the CSRF token
args['form'] = form # add the 'form' above to the 'args' dict
return render_to_response('create_article.html', args) # pass that dict to template
Not so sure why you have this example, normally I would do the last part like this:
def create(request):
.... your code ....
else:
form = ArticleForm()
return render(request, 'create_article.html', { form: form })
Hope it helps.
There are some mistakes in the code and it seems it's copy-pasted from a SO question. I would recommend going through the excellent Django documentation, especially the Django tutorial.
Your example should rather look like this example from the Django docs.
Here are some comments:
def create(request):
if request.POST:
form = ArticleForm(request.POST)
if form.is_valid:
form.save()
# after successful POST
# we want to redirect to a different page here
else:
form = ArticleForm()
args = {}
# you really don't need the following necessarily
# just use `{% csrf_token %}` inside the form in your template
args.update(csrf(request))
args['form'] = form
# using just `render` like in the example linked to above is more modern
return render_to_response('create_article.html', args)

How to check whether or not a Django form is correctly bound?

Situation
Using Django 1.5, I am using forms.ModelForms to let the user edit database contents. However I can't get the form to update the database upon form.save().
Each of my models correspond to a setting form (the application is a the direct porting of a desktop software in which the user can store several settings). I needed to implement a Reset to default feature, so I thought of having a default object (imported with Django fixtures) which I would use only to reset a second one. The user would only interact with the second model.
pk=1 refers to the base object
pk=2 refers to the custom object
I have several forms on the same page (only foobar here), so basically this what I planned to do:
No POST data
Building form from either pk=1 or pk=2, depending pk=2 has been found or not
Rendering the forms to the template
AJAX request, with POST datas
Getting form content
Checking whether or not the user has permission to edit the model (checksum)
Update the model form POST datas
Returning AJAX response
Code
I have put two debug prints to illustrate the issue I am facing. The form I fetch doesn't seem to be bound to my model.
# Response codes to use in the template
RESPONSES = {
200: {'code':'0xB16B00B5', 'message':'Success'},
400: {'code':'0x8BADF00D', 'message':'Form is not valid'},
403: {'code':'0xBAADF00D', 'message':'No permission to edit the database'},
501: {'code':'0xDEADC0DE', 'message':'POST datas not found'},
}
# Those are the setting labels
TYPES = {
'foobar': {'model':FooBar, 'form':FooBarForm },
}
def index(request):
# Handling form datas
if request.method == 'POST':
response = HttpResponse(simplejson.dumps({'code':RESPONSES[501]['code']}), 'application/json')
for label in TYPES:
# Filtering the right form to handle
if label in request.POST:
model = _fetch_setting(label, mode='model')
form = _fetch_setting(label, mode='form', post=request.POST)
checksum = model.checksum # Somehow, 'form.is_valid()' is altering 'model', need to backup the checksum
if form.is_valid():
# The user has permission to edit the model
if form.cleaned_data['checksum'] == checksum:
if form.has_changed():
print form.cleaned_data['foo'] # Outputs the form data, as expected
form.save()
print model.foo # Outputs the old data
model.checksum = str(uuid4()).replace('-', '')
model.save()
response = HttpResponse(simplejson.dumps({'code':RESPONSES[200]['code']}), 'application/json')
# This one does not
else:
response = HttpResponse(simplejson.dumps({'code':RESPONSES[403]['code']}), 'application/json')
break # We are still inside the label loop
# The form is not valid
else:
response = HttpResponse(simplejson.dumps({'code':RESPONSES[400]['code']}), 'application/json')
# Form not submitted yet, building the HTML forms
else:
forms = {}
label = 'foobar'
for label in TYPES:
forms[label] = _fetch_setting(label, mode='form')
context = {'errors':RESPONSES, 'forms':forms}
response = render(request, 'home/index.html', context)
return response
# Return a setting object (model or form) corresponding to the given label
def _fetch_setting(label, mode='model', post=None):
try:
result = None
default = TYPES[label]['model'].objects.get(pk=1)
try:
model = TYPES[label]['model'].objects.get(pk=2)
except TYPES[label]['model'].DoesNotExist:
model = TYPES[label]['model'].objects.create(
checksum = default.checksum,
foo = default.foo,
bar = default.bar,
)
if mode == 'model':
result = model
if mode == 'form':
print model
result = TYPES[label]['form'](data=post, instance=model) # The 'instance' attribute doesn't seem to be applied
except KeyError:
result = None
finally:
return result
Update
07.10
It does work when I pass the instance to bound with to _fetch_setting. So I guess this issue is coming from the form validation.
def _fetch_setting(label, mode='model', post=None, instance=None):
# ...
if mode == 'form':
if instance:
model = instance
result = TYPES[label]['form'](data=post, instance=model)
# ...
As I commented in my code, form.is_valid() seems to alter the object.
Will flag as answered if no one come with a clean solution.
The issue is, you are creating a new model object with each form.save()
You need to update the same model object with commit=False
if form.cleaned_data['checksum'] == checksum:
if form.has_changed():
print form.cleaned_data['foo'] # Outputs the form data, as expected
model = form.save(commit=False)
model.checksum = str(uuid4()).replace('-', '')
model.save()
From the fabulous manual:
The first time you call is_valid() or access the errors attribute of a ModelForm triggers form validation as well as model validation. This has the side-effect of cleaning the model you pass to the ModelForm constructor. For instance, calling is_valid() on your form will convert any date fields on your model to actual date objects. If form validation fails, only some of the updates may be applied. For this reason, you’ll probably want to avoid reusing the model instance passed to the form, especially if validation fails.

Populate Django form from 3rd party POST with different field names

My Django app will be handling post data from a 3rd party payment processor. I would like to use a Django form to sanitize and deal with this data, but the POST variables have names that are not valid names for Django form fields. How can I re-map these to my form?
So, I have a form:
class PayResponseForm(forms.Form):
status = forms.Charfield()
message = forms.CharField()
avs_code = forms.CharField(max_length=1)
And the post I get from the 3rd part site looks like:
request.POST['FinalStatus']
request.POST['MErrMsg']
request.POST['avs-code']
How do I map these post variable to my form fields? Will it cause problems if, in my view, I just do something like request.POST['status'] = request.POST['FinalStatus']?
You can create your own dict for form init:
if request.method == 'POST':
if need_to_convert_post(request):
post = {
'field1': None,
'field2': None,
'field3': None,
}
post['field1'] = request.POST.get('SomeOtherField1', None)
else:
post = request.POST
form = Form1(post)
else:
form = Form1()
and detect when you need to change post:
def need_to_convert_post(request):
if 'uniqueCheckField' in request.POST:
return True
return False
For tidyness, you could use request.POST['status'] = request.POST.pop('FinalStatus'), otherwise it's perfectly fine to use your approach.

django inline editing - inline form only required if at least one field is filled out

I created a view which returns a form including a contact form and two phone_number forms, following this example:
multiple forms
The phone number forms should only be validated if the user inserts at least a value for one field in a phone number form. For example: a phone number has a type and a number. If the user is selecting the type, the number is required.
Now I'm wondering how i can check in the view whether the user inserted a value / selected a type or inserted a number. It should work like in the admin for inline editing a model.
my view looks like this:
def contact_add(request):
user = request.user
if request.method == 'POST':
cform = ContactForm(request.POST)
pforms = [PhoneNumberForm(request.POST, prefix=str(x)) for x in range(0,3)]
if cform.is_valid() and all([pf.is_valid() for pf in pforms]):
new_contact = cform.save(commit=False)
new_contact.created_by = user
new_contact.save()
for pf in pforms:
new_phone_number = pf.save(commit=False)
new_phone_number.contact = new_contact
new_phone_number.save()
request.user.message_set.create(message='Contact %s has been added.' % new_contact.__str__())
return HttpResponseRedirect("/crm/contacts/?oby=1")
else:
cform = ContactForm()
pforms = [PhoneNumberForm(prefix=str(x)) for x in range(0,3)]
return render_to_response(
'crm/contact_add.html',
{'cform': cform, 'pforms': pforms,},
context_instance = RequestContext(request),
)
Edit after first response below:
I tried to accomplish this task with custom validation but did not come to a satisfying end. To ease my task I changed the use-case a bit. I create a form which includes one Contact Form and one Address Form. The Address Form should only be validated if at least one field of the Address Form is filled in, since it should be possible to create a contact without creating a corresponding Address.
First I tried to use custome validation, which looked like this:
class AddressForm(forms.ModelForm):
class Meta:
model = Address
exclude = ('contact',)
def clean(self):
cleaned_data = self.cleaned_data
street = cleaned_data.get("street")
postal_code = cleaned_data.get("postal_code")
city = cleaned_data.get("city")
country = cleaned_data.get("country")
if not street and not postal_code and not city and not country:
#searching a better idea here
return 0
else:
return cleaned_data
But this does not really help, since this way I do not get rid of the validation errors.
This lead me to the idea that the clean method is the wrong place to do this validation, I think I have to check already in the POST.request whether all values for the Address Form are missing. And if they are missing, I do not call is_valid() for the Address Form and just ignore it. If at least one value is available, I just do the normal validation of the Address Form, without overriding the clean() method..
Good or bad idea?
If it is a good idea, how can I easily check the POST request for the values of my Address Form.
Probably I`m thinking way to complicated :-)
Edit: The solution using FormSets:
#login_required
def contact_add(request):
user = request.user
if request.method == 'POST':
cform = ContactForm(request.POST)
phonenumberformset = PhoneNumberFormSet(request.POST)
if cform.is_valid() and classificationformset.is_valid() and addressformset.is_valid() and phonenumberformset.is_valid():
new_contact = cform.save(commit=False)
new_contact.created_by = user
new_contact.save()
new_phonenumber_instances = phonenumberformset.save(commit=False)
for new_phonenumber in new_phonenumber_instances:
new_phonenumber.contact = new_contact
new_phonenumber.save()
request.user.message_set.create(message='Contact %s has been added.' % new_contact.__str__())
return HttpResponseRedirect("/crm/contacts/?oby=1")
else:
cform = ContactForm()
#By default, when you create a formset from a model, the formset will use
#a queryset that includes all objects in the model (e.g., Author.objects.all()).
#Here we want to present an empty formset in order to add a new object
phonenumberformset = PhoneNumberFormSet(queryset=PhoneNumber.objects.none())
return render_to_response(
'crm/contact_add.html',
{'cform': cform, 'phonenumberformset': phonenumberformset,},
context_instance = RequestContext(request),
)
Please note that this can also be accomplished using an inlineformset_factory, see my other post for more details: link
Note that if you are using FormSets you have to include a management_form for each form_set in your template. docs
Otherwise you get this error:
[u'ManagementForm data is missing or has been tampered with']
Using a formset inside a view is as easy as using a regular Form class. The only thing you will want to be aware of is making sure to use the management form inside the template.
{{ context.phonenumberformset.management_form }}
You should be using formsets rather than messing around with dynamic prefixes for your PhoneNumber subform - it will make everything much easier, and this is indeed how the admin manages inline forms (see also the model formsets documentation).
Formsets are intelligent enough that if no information is entered in one form of the formset, it does not enforce the required elements - but if one element is filled, it will enforce all the validation requirements. This sounds like it should solve your problem.
What you want to do is define custom validation on the form.
class PhoneNumberForm(forms.Form):
# Everything as before.
...
def clean(self):
cleaned_data = self.cleaned_data
phone1 = cleaned_data.get("phone1")
if phone1:
# validate manually, and if it doesn't pass:
self._errors["phone1"] = ErrorList(["Hey, this field is wrong."])
del cleaned_data["phone1"]
# Always return the full collection of cleaned data.
return cleaned_data
Then in the view, you want to rely on Django's built-in error form validation error handling:
{{ pforms.phone1 }}
{{ pforms.phone1.errors }}