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.
Related
I am trying to store a User's ID and the ID of a Listing in a table. I am new to web development and to me, this seems like a good time to use a ManyToManyField:
class Watchlist(models.Model):
id = models.AutoField(primary_key=True)
user = models.ManyToManyField(User)
listing = models.ManyToManyField(Listing)
When I try to save a new entry in the database, by doing this:
listing_id = self.kwargs['pk']
item = Listing.objects.get(id=listing_id)
user_object = self.request.user
add_to_watchlist = Watchlist(user = user_object, listing = item)
add_to_watchlist.save()
I get the error:
TypeError: Direct assignment to the forward side of a many-to-many set is prohibited. Use user.set() instead.
I am not sure what I am doing wrong, I have followed the example in the documentation as much as possible.
You can not directly assign a value to a ManyToManyField, but use the .add(…) method [Djang-doc] as the error indicates:
from django.shortcuts import get_object_or_404
item = get_object_or_404(Listing, pk=self.kwargs['pk'])
watchlist = Watchlist.objects.create()
watchlist.user.add(request.user)
watchlist.listing.add(item)
Note: It is often better to use get_object_or_404(…) [Django-doc],
then to use .get(…) [Django-doc] directly. In case the object does not exists,
for example because the user altered the URL themselves, the get_object_or_404(…) will result in returning a HTTP 404 Not Found response, whereas using
.get(…) will result in a HTTP 500 Server Error.
Note: Since a ManyToManyField refers to a collection of elements,
ManyToManyFields are normally given a plural name. You thus might want
to consider renaming user to users.
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
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.
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.
I know how to get it in views.py....
request.META['REMOTE_ADDR']
However, how do I get it in models.py when one of my forms is being validateD?
You can pass the request object to the form/model code that is being called: this will then provide access to request.META['REMOTE_ADDR']. Alternatively, just pass that in.
Ona possible way, but i am not sure if it is the best or not...
define your own clean method,
class someForm(forms.Form):
afield = CharField()
def clean(self, **kwargs):
cleaned_data = self.cleaned_data
afield = cleaned_data.get('afield')
if 'ip' in kwargs:
ip = kwargs['ip']
# ip check block, you migth use your cleaned data in here
return cleaned_data
some_info = {'afield':123} #you will wish to use post or gt form data instead, but tihs iis for example
form = someForm(some_info)
if form.is_valid():
data = form.clean({'ip':request.META['REMOTE_ADDR']}) # you pass a dict with kwargs, which wwill be used in custom clean method
If you are validating at form level or at model level, both instances know nothing about the HTTP request (where the client IP info is stored).
I can think of two options:
Validate at the view level where you can insert errors into the form error list.
You can put the user IP (may be encrypted) in a hidden field at your form.