using a ModelForm to sanitize post data in Django - python

I've come across How to create object from QueryDict in django? , which answers what I want to do. However I want to sanitize the data. What does the Brandon mean by "using a ModelForm" to sanitize posted data?

ModelForm are very helpful when you want to create just model instances. If you create a form that closely looks like a model then you should go for a model form instead. Here is an example.
Going by the example provided in the Django website.
In your forms.py
class ArticleForm(ModelForm):
class Meta:
model = Articels #You need to mention the model name for which you want to create the form
fields = ['content', 'headline'] #Fields you want your form to display
So in the form itself you can sanitize your data as well. There are 2 ways of doing that.
Way 1: Using the clean function provided by Django using which you can sanitize all your fields in one function.
class ArticleForm(ModelForm):
class Meta:
model = Articels #You need to mention the model name for which you want to create the form
fields = ['content', 'headline'] #Fields you want your form to display
def clean(self):
# Put your logic here to clean data
Way 2: Using clean_fieldname function using which you can clean your form data for each field separately.
class ArticleForm(ModelForm):
class Meta:
model = Articels #You need to mention the model name for which you want to create the form
fields = ['content', 'headline'] #Fields you want your form to display
def clean_content(self):
# Put your logic here to clean content
def clean_headline(self):
# Put your logic here to clean headline
Basically you would use clean and clean_fieldname methods to validate your form. This is done to raise any error in forms if a wrong input is submitted. Let's assume you want the article's content to have at least 10 characters. You would add this constraint to clean_content.
class ArticleForm(ModelForm):
class Meta:
model = Articels #You need to mention the model name for which you want to create the form
fields = ['content', 'headline'] #Fields you want your form to display
def clean_content(self):
# Get the value entered by user using cleaned_data dictionary
data_content = self.cleaned_data.get('content')
# Raise error if length of content is less than 10
if len(data_content) < 10:
raise forms.ValidationError("Content should be min. 10 characters long")
return data_content
So here's the flow:
Step 1: User open the page say /home/, and you show the user a form to add new article.
Step 2: User submits the form (content length is less than 10).
Step 3: You create an instance of the form using the POST data. Like this form = ArticleForm(request.POST).
Step 4: Now you call the is_valid method on the form to check if its valid.
Step 5: Now the clean_content comes in play. When you call is_valid, it will check if the content entered by user is min. 10 characters or not. If not it will raise an error.
This is how you can validate your form.

What he mean is that with ModelForm you can not only create model instance from QueryDict, but also do a bunch of validation on data types and it's requirements as for example if value's length correct, if it's required etc. Also you will pass only needed data from QueryDict to model instance and not whole request
So typical flow for this is:
form = ModelForm(request.POST)
if form.is_valid():
form.save()
return HttpResponse('blah-blah success message')
else:
form = ModelForm()
return HttpResponse('blah-blah error message')
And awesome Django docs for this: https://docs.djangoproject.com/en/dev/topics/forms/modelforms/#django.forms.ModelForm

Related

Is it possible to use request.POST as a dictionary input?

In my django project, I collect membership data by HTML form and insert them into the database. There are the code samples:
models.py
class member(models.Model):
name = models.CharField(max_length=100,blank=True,null=True)
gender = models.CharField(max_length=10,blank=True,null=True)
profession = models.CharField(max_length=100,blank=True,null=True)
views.py:
def manage(request):
form_values = request.POST.copy()
form_values.pop('csrfmiddlewaretoken') # I don't need it.
add_member = member(**form_values)
add_member.save()
If HTML form input is: Rafi, Male, student
Database gets in list format: ['Rafi'], ['Male'], ['student']
How can I solve this?
You can make use of the .dict() [Django-doc] method here:
def manage(request):
form_values = request.POST.copy()
form_values.pop('csrfmiddlewaretoken')
add_member = member(**form_values.dict())
add_member.save()
If there are multiple values for the same key, it will take the last one.
That being said, it might be better to take a look at a ModelForm [Django-doc] to validate data and convert it to a model object. This basically does what you do here, except with proper validation, removing boilerplate code, and furthermore it will not use the other keys. If here a user would "forge" a POST request with extra key-value pairs, the server will raise a 500 error.

How can I reference a form when overriding a GET request in a class based view?

I overrode the GET request function handler in this class based view. In my else statement, I need to pass, as context data, the form that the class naturally creates (if I had not overridden the GET function). How can I do that?
I did not create a form at forms.py to create a form for the Post model. I let the create class based view handle the form creation for me. So, how can I get this form and pass as context data.
The only way I can think of doing this is creating a function based view and avoid using this class based view in this circumstance.
class PostCreateView(LoginRequiredMixin, CreateView):
model = Post
fields = ["title", "content", "payment_option", "price"]
def get(self, request):
card_set = BillingProfile.objects.get(user=request.user).card_set.all()
if not card_set.exists():
# The user does NOT have an inserted payment method.
return redirect("/billing/payment-method?next=/post/new/")
else:
# The user DOES have an inserted payment method.
form = "???"
return render(request, "posting/post_form.html", {"form":form})
You could use the method the class provides, which is self.get_form().
But this actually wouldn't be the right thing to do. What your should really do is to delegate to the default implementation of get and let it do what it normally would.
if not card_set.exists():
# The user does NOT have an inserted payment method.
return redirect("/billing/payment-method?next=/post/new/")
else:
return super().get(request)

Django How to add a model other than a form

I am making an app using Django2.0. But There are things that I do not know even if I investigate.
There are models as follows.
class Picture(models.Model):
place = models.ForeignKey(Place, on_delete=models.CASCADE)
title = models.CharField(max_length=255)
image = models.ImageField(upload_to='image/')
using the following form
class PictureForm(forms.ModelForm):
class Meta:
model = Picture
fields = ['place', 'title', 'image']
And run following code on views.py
obj = Picture()
pic = PictureForm(request.POST, request.FILES, instance=obj)
pic.save()
I can add model peacefully. But in this way, I should select place and title on form every time. Beforehand I decide place and title, I will only select image on the form. It reduces my work.
Since there are several place and title, I do not want to set them to default values.
I succeeded in making a form.
class PictureForm(forms.ModelForm):
class Meta:
model = Picture
fields = ['image',]
But I don't know how to add title and place on views.py
I'm not good at English. Thank you for seeing the bad sentensecs to the end
Your issue comes from the view code, where you instantiate your new model.
If you want to create a new model you don't need to pass the insance argument, just edit the lines to get the following :
pic = PictureForm(request.POST, request.FILES)
pic.save()
See https://docs.djangoproject.com/en/2.0/topics/forms/modelforms/#the-save-method
To be able to set only a few fields you should update your model declaration, with for example null=True, Blank=True, default=None in the optional fields arguments.
PS : the django documentation is transalated in many languages, but if you don't find yours try the IRC channels.
If I understand your problem clearly,
You have predecided the place and the title and want the user to enter only the image. Only image should be kept in the form, but you want to enter the title and the place in the view.If that's the case,do this :
Your view function:
def some_view(request):
if request.method == "POST":
T,P = get_title_and_place()
# This fuction will return a tuple with the title and the place that you need.
# Or Alternatively, you can pass the title and place as argument to the view
obj = Picture(title=T,place=P)
#Initialise object with title and place received from function or arguments
pic_form = PictureForm(request.POST, request.FILES)
#The form contains only the image field as in your last block of code
if pic_form.is_valid():
obj.image = request.FILES['image']
obj.save()
#Save the object
return redirect('some_url')
else:
return render('xyz.html',{"form":pic_form})
#Incoming image is invalid. Throw back the invalid form to the user
else:
#Incoming method is not POST, give user the form to fill the image
pic_form = PictureForm()
return render('xyz.html',{"form:pic_form})
On submission of the form from xyz.html, you should redirect back to this view. This is the reason why I made a seperate function to get the title and place so that user doesn't know about it via the url.
However be careful with this method. You have to take care of validating the title and place. Otherwise the obj.save() will throw error

django forms, cleaned_data is empty

I have been playing around with forms a little and cant seem to understand why cleaned_data is not giving me any usable output (aka the dict appears to be completely empty). What id like to do is have a form on a page with two date selector so the user can select a from and to date that Django will then query a database that has periodic thermocouple measurements and create a table.
views.py
def temperature_data(request):
date_select_form = CalLabDateSelect(request.POST)
if request.method == 'POST':
if date_select_form.is_valid(): # All validation rules pass
print "this should be some date/time data from date_select_form:", date_select_form.cleaned_data
#return HttpResponseRedirect('/test_page/') # Redirect after POST
raw_data = Callab.objects.all().using('devices').order_by('-time')
return render_to_response("temperature_display.html",
locals(),
context_instance=RequestContext(request))
forms.py
def make_custom_datefield(f):
formfield = f.formfield()
if isinstance(f, models.DateField):
formfield.widget.format = '%m/%d/%Y'
formfield.widget.attrs.update({'class':'datePicker', 'readonly':'true'})
return formfield
class CalLabDateSelect(forms.Form):
formfield_callback = make_custom_datefield
when i visit the page and select a date then submit the form i see this outputted to the console:
QueryDict: {u'date': [u'10/04/2014'], u'csrfmiddlewaretoken': [u'C5PPlMU3asdFwyma9azFDs4DN33CMmvK']}
this should be some date/time data from date_select_form: {}
all i notice is that the dictionary is empty {} but the request.POST data shows 10/04/2014???
any ideas why this is happening??
And thank you all very much for any help in understand this!!
Your form doesn't actually define any fields, so I don't know what you're expecting to get in cleaned_data. formfield_callback is only useful in a ModelForm, where it operates on the fields already defined by a model: but your form is not based on a model.
Either use a model form, or define your form fields explicitly in your form class.

KeyError from customized clean() method in BaseModelFormset

I have read over the Forms and Formset Django documentation about 100x. To make this very clear, this is probably the first time I've ever used super() or tried to overload/inherit from another class (big deal for me.)
What's happening? I am making a django-model-formset in a view and I am passing it to a template. The model that the formset is inheriting from happens to be a ManyToMany relationship. I want these relationships to be unique, so that if my user is creating a form and they accidentally choose the same Object for the ManyToMany, I want it to fail validation.
I believe I have written this custom "BaseModelFormSet" properly (via the documentation) but I am getting a KeyError. It's telling me that it cannot find cleaned_data['tech'] and I am getting the KeyError on the word 'tech' on the line where I commented below.
The Model:
class Tech_Onsite(models.Model):
tech = models.ForeignKey(User)
ticket = models.ForeignKey(Ticket)
in_time = models.DateTimeField(blank=False)
out_time = models.DateTimeField(blank=False)
def total_time(self):
return self.out_time - self.in_time
The customized BaseModelFormSet:
from django.forms.models import BaseModelFormSet
from django.core.exceptions import ValidationError
class BaseTechOnsiteFormset(BaseModelFormSet):
def clean(self):
""" Checks to make sure there are unique techs present """
super(BaseTechOnsiteFormset, self).clean()
if any(self.errors):
# Don't bother validating enless the rest of the form is valid
return
techs_present = []
for form in self.forms:
tech = form.cleaned_data['tech'] ## KeyError: 'tech' <-
if tech in techs_present:
raise ValidationError("You cannot input multiple times for the same technician. Please make sure you did not select the same technician twice.")
techs_present.append(tech)
The View: (Summary)
## I am instantiating my view with POST data:
tech_onsite_form = tech_onsite_formset(request.POST, request.FILES)
## I am receiving an error when the script reaches:
if tech_onsite_form.is_valid():
## blah blah blah..
Isn't the clean method missing a return statement ? If I remember correctly it should always return the cleaned_data. Also the super call returns the cleaned_data so you should assign it there.
def clean(self):
cleaned_data = super(BaseTechOnsiteFormset, self).clean()
# use cleaned_data from here to validate your form
return cleaned_data
See: the django docs for more information
I used the Django shell to call the forms manually. I found that I was executing the clean() method on all of the forms returned from the view. There were 2 filled out with data, and 2 blank. When my clean() method was iterating through them all, it returned a KeyError when it got to the first blank one.
I fixed my issue by using a try-statement and passing on KeyErrors.

Categories