Saving data using Django ModelForms - python

I used this code previously it worked fine and i was suggested to use ModelForm by another member, it did make sense to use the form.is_valid() function etc.. so thought of giving it a try.
I went through some other examples on the internet but mine does not seem to work for some reason, or may be I am not doing it right, I get the following when I print the form in the view, and it goes to the else statement, so my form does not get saved
<input id="id_product" type="text" name="product" value="aassddf" maxlength="250" />
FAIL
My model.py
from django.db import models
from django.forms import ModelForm
class Category(models.Model):
name = models.CharField(max_length=250)
def __unicode__(self):
return self.name
class Product(models.Model):
category = models.ForeignKey(Category)
product = models.CharField(max_length=250)
quantity = models.IntegerField(default=0)
price = models.FloatField(default=0.0)
def __unicode__(self):
return self.product
class ProductForm(ModelForm):
class Meta:
model = Product
My views.py
from models import *
from django.shortcuts import render_to_response
from django.http import HttpResponseRedirect
def index(request):
...
...
def add_product(request):
if request.method == 'POST':
form = ProductForm(request.POST)
print form['product']
if form.is_valid():
form.save()
return HttpResponseRedirect('/product')
else:
print 'FAIL'
return HttpResponseRedirect('/product')
My html
<form method="post" action="add_product/">
{% csrf_token %}
<label for="category">Category</label>
<select name="category" id="category">
{% for category in category_list %}
<option> {{ category.name }} </option>
{% endfor %}
</select>
<label for="product">Product</label>
<input type="text" name="product" id="product">
<label for="quantity">Quantitiy</label>
<input type="text" name="quantity" id="quantity">
<label for="price">Price</label>
<input type="text" name="price" id="price">
<input type="submit" value="Add New product" id="create">
</form>
Is there a better way i could save the data, using ModelForms ??
Thanks in advance for the help.

You should read the documentation. If the form is not valid, it will have a whole set of errors associated with it, which will tell you exactly why. But you just throw that away, and redirect to /product. The docs show exactly how to redisplay the form with the errors.
Also you should not write HTML form field tags directly in your template: use the form object from the view - {{ form.product }}, etc - as these will be repopulated with the appropriate values on redisplay.

Thanks to Daniel Roseman and Anuj Gupta I think I finally re-worked on my code on got it working in a standard way so it will generate the html form and validate errors.
So for anyone else who is trying to work django forms here is the code I worked on.
My model.py is was almost the same one i posted on the question but i removed
class ProductForm(ModelForm):
class Meta:
model = Product
I created a new form.py here is the code-
from django import forms
from models import Category
class ProductForm(forms.Form):
# Put all my Categories into a select option
category = forms.ModelChoiceField(queryset=Category.objects.all())
product = forms.CharField()
quantity = forms.IntegerField()
price = forms.FloatField()
My views.py changed had a lot of changes -
def add_product(request):
success = False
if request.method == "POST":
product_form = ProductForm(request.POST)
if product_form.is_valid():
success = True
category = Category.objects.get(name=product_form.cleaned_data['category'])
product = product_form.cleaned_data['product']
quantity = product_form.cleaned_data['quantity']
price = product_form.cleaned_data['price']
new_product = Product(category = category, product = product, quantity = quantity, price = price )
new_product.save()
new_product_form = ProductForm()
ctx2 = {'success':success, 'product_form':new_product_form}
return render_to_response('product/add_product.html', ctx2 , context_instance=RequestContext(request))
else:
product_form = ProductForm()
ctx = {'product_form':product_form}
return render_to_response('product/add_product.html', ctx , context_instance=RequestContext(request))
Finally in my html page i used {{ product_form.as_p }} so it created the forms dynamically
{% if success %}
<h3> product added successfully </h3>
{% endif %}
<form method="post" action=".">
{% csrf_token %}
{{ product_form.as_p }}
<input type="submit" value="Add New product" id="create">
<input type="reset" value="reset" id="reset">
</form>
This may not be the perfect solution, but for a starter like me this sounds good, and at times you just get lost while reading the docs lol, hope it helps some one.
Cheers

Try:
<form method="post" action="add_product/">
{% csrf_token %}
{{ form.as_p }}
</form>
in your template, instead of hand-coding the form's input tags. This shortcut will generate the form html for you, as well as print validation errors.
Make sure you return the form object to the template when:
There is no request.POST (form has not been submitted)
form.is_valid() fails (form has validation errors)
Of course, this is only to get you started. You really should read the docs

Related

How to dynamically update form data in django

I am new to django and I am trying to create a review system, whereby each team member reviews all the other members within their team.
Here is my models.py file:
from django.db import models
from django.contrib.auth.models import User
class Team(models.Model):
name = models.CharField(max_length=25)
def __str__(self):
return self.name
class Trait(models.Model):
name = models.CharField(max_length=25)
def __str__(self):
return self.name
class Review(models.Model):
reviewer = models.ForeignKey(User, on_delete=models.CASCADE,
related_name='reviewer_id')
reviewee = models.ForeignKey(User, on_delete=models.CASCADE,
related_name='reviewee_id')
trait = models.ForeignKey(Trait, on_delete=models.CASCADE)
trait_score = models.IntegerField()` return
This is my views.py file:
from django.shortcuts import render, redirect
from review.forms import ReviewForm
from django.http import HttpResponse
from django.contrib.auth.models import User
from accounts.models import UserProfile
def positivity_review(request):
if request.method == 'POST':
form = ReviewForm(request.POST)
if form.is_valid():
form.save()
return redirect('/review/relationships')
else:
form = ReviewForm()
users = UserProfile.objects.filter(review_team=1)
args = {'form': form, 'team_members': users}
return render(request, 'review/positivity.html', args)` return
This is my forms.py file:
from django import forms
from django.forms.widgets import NumberInput
from review.models import Team, Review
class RangeInput(NumberInput):
input_type = 'range'
class ReviewForm(forms.ModelForm):
trait_score = forms.IntegerField(widget=RangeInput, min_value=0,
max_value=100, label='')
class Meta:
model = Review
fields = (
'trait_score',
)` return
This is the HTML file:
{% extends 'base.html' %}
{% block head %}
<title>Review</title>
{% endblock %}
{% block content %}
<br>
<h1>Review</h1>
<h2>Do they foster a postive climate?</h2>
<div class="row">
<div class="col-md-2">
<p>Exhibits a lack of awareness for a positive climate. Resistance to prompting.</p>
</div>
<div class="col-md-2">
<p>Cooperates at times, within structured activities and friendly under prompting.</p>
</div>
<div class="col-md-2">
<p>Cooperates within the team environment without prompting.</p>
</div>
<div class="col-md-2">
<p>Cooperates well with others, enthusiastic and positve. Occationally prompts others to engage positively.</p>
</div>
<div class="col-md-4">
<p>Seeks to continuously and consistently create a positive environment. Acts as a role model for the team through prompting being supportive and encouraging and showing genuine concern for others.</p>
</div>
</div>
<div>
<form method="post">
{% for user in team_members %}
<p>Reviewing: {{ user.first_name }} {{ user.last_name }}</p>
{% csrf_token %}
{{ form.as_p }}
{% endfor %}
<button class="btn btn-primary" type="submit">Next</button>
</form>
</div>
{% endblock %}`
Currently I am passing in the queryset through the views.py into the html file and looping through it to load the relevant number of team members.
Since I am loading a form each time for each individual in the team, how can I make the form submit so that it knows who is being reviewed? For example, submitting the reviewer, trait and score is simple as most of that can be passed directly into the view, however, submitting the reviewee (person being reviewed) is the part im not sure how to handle, as they are loaded within the form using the template tagging. I was wondering if it is possible to submit some kind of data back into the form such as first + last name or thier user id, anything so that when I go to publish the results I have a way of filtering individuals results.
Hopefully the description is sufficient. Thanks in advance!
If I understand your question correctly, this might be the answer.
First, create a simple form (rather than ModelForm) and add this, among other things:
pk = forms.CharField(
widget=forms.TextInput(
attrs={
'type': 'hidden',
}
),
label=''
)
This will hold the pk of the reviewee and won't be visible to reviewer.
Then, in the html file, I think you have to generate a separate form for each user rather than only the input (I'm not sure, try it). You can do this:
{% for user in team_members %}
<form method="post" id="review_form_{{user.pk}}">
<p>Reviewing: {{ user.first_name }} {{ user.last_name }}</p>
{% csrf_token %}
{{ form.as_p }}
<button class="btn btn-primary" type="submit">Next</button>
</form>
<script type="text/javascript">
$('#review_form_{{user.pk}}').children('#id_pk').val("{{user.pk}}");
</script>
{% endfor %}
Remember that when django generates a form, each input will have to get an id and django adds id_ to the beginning of the name of the field you create in fields.py
And lastly, when the reviewer submit a form, you can find your reveiwee in views.py this way:
form = ReviewForm(request.POST)
if form.is_valid():
reviewee_id = request.POST.get('pk')
reviewee = User.objects.get(pk=reviewee_id)

django can't submit comment to database

I was trying to add a comment function to my django app, but whatever I did, the comment submitted just wouldn't show up in database, can't figure out why. Here're some codes I worked with, please have a look.
the models.py part
class Comment(models.Model):
name = models.CharField(max_length=80)
email = models.EmailField()
text = models.TextField()
post = models.ForeignKey(Post, related_name='comments')
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
active = models.BooleanField(default=True)
class Meta:
ordering = ('created',)
verbose_name="评论"
verbose_name_plural="评论"
def __str__(self):
return 'Comment by {} on {}'.format(self.name, self.post)
the forms.py part
from django import forms
from .models import Comment
class CommentForm(forms.ModelForm):
class Meta:
model=Comment
fields=('name','email','text')
the views.py part
def post_comment(request,id):
post=get_object_or_404(Post,id=id)
if request.method=='POST':
form=CommentForm(request.POST)
if form.is_valid():
name=request.POST.get('name','')
email=request.POST.get('email','')
text=request.POST.get('text','')
comment=Comment(name=name,email=email,text=text,post=14)
comment.save()
return redirect(post)
else:
form=CommentForm()
comment_list = post.comments.all()
context={
'post':post,
'form':form,
'comment_list':comment_list
}
return render(request,'post_detail.html',context=context)
return redirect(post)
and the post_detail.html part
<form action="" method="POST" class="mb-5">{% csrf_token %}
<div class="form-group">
<label for="exampleInputPassword1">名称</label>
<input name="name" type="name" class="form-control" id="exampleInputPassword1" placeholder="Name">
</div>
<div class="form-group">
<label for="exampleInputEmail1">邮箱</label>
<input name="email" type="email" class="form-control" id="exampleInputEmail1" aria-describedby="emailHelp" placeholder="Enter email">
<small id="emailHelp" class="form-text text-muted">We'll never share your email with anyone else.</small>
</div>
<div class="form-group">
<label for="exampleFormControlTextarea1">评论</label>
<textarea name="text" class="form-control" id="exampleFormControlTextarea1" placeholder="Comment area..." rows="5"></textarea>
</div>
<button type="submit" class="btn btn-primary" value="post_comment">提交</button>
</form>
Any suggestions? Thanks.
You are doing two things wrong here. The first is that you are rendering the form manually and you do not have anything in your HTML to display form errors. You need to add the errors to your HTML as described in the documentation - or until yo get this sorted out use {{ form.as_p }}
Even after that, it still wont work because your python code in your view is wrong.
if form.is_valid():
name=request.POST.get('name','')
email=request.POST.get('email','')
text=request.POST.get('text','')
comment=Comment(name=name,email=email,text=text,post=14)
comment.save()
return redirect(post)
else:
form=CommentForm()
The last two lines of code above results in a new form being created when teh form is invalid. That discards any form data that was posted. Which also means any errors you had will be discarded along the way. The correct code will look something like the following:
post=get_object_or_404(Post,id=id)
if request.method=='POST':
form=CommentForm(request.POST)
if form.is_valid():
# don't directly use the request post data. Always use form.cleaned_data
name= form.cleaned_data['name'
.....
comment=Comment(name=name,email=email,text=text,post=14)
comment.save()
# the following redirect is wrong. Here post is an object
# you retrieved from a database, we usually redirect to a URL
return redirect(post)
else:
form = CommentForm()
comment_list = post.comments.all()
context={
'post':post,
'form':form,
'comment_list':comment_list
}
return render(request,'post_detail.html',context=context)
try this to creating comment class:
class Comment(models.Model):
post = models.ForeignKey('blog.Post', on_delete=models.CASCADE, related_name='comments')
author = models.CharField(max_length=200)
text = models.TextField()
created_date = models.DateTimeField(default=timezone.now)
approved_comment = models.BooleanField(default=False)
def approve(self):
self.approved_comment = True
self.save()
def __str__(self):
return self.text
Register Comment model in the admin panel
To register the Comment model in the admin panel, go to blog/admin.py and add this line:
admin.site.register(Comment)
directly under this line:
admin.site.register(Post)
Remember to import the Comment model at the top of the file, too, like this:
from django.contrib import admin
from .models import Post, Comment
admin.site.register(Post)
admin.site.register(Comment)
Make our comments visible HTML:
<hr>
{% for comment in post.comments.all %}
<div class="comment">
<div class="date">{{ comment.created_date }}</div>
<strong>{{ comment.author }}</strong>
<p>{{ comment.text|linebreaks }}</p>
</div>
{% empty %}
<p>No comments here yet :(</p>
{% endfor %}

Using Django FormPreview the right way

My Goal
I have a django project with a form, and I want to display a preview page before the user submits.
The problem
I can display a preview page using a Django FormPreview, but not all form data is displayed properly. Specifically, if I have a field with choices, the string values of these choices aren't displayed. I'm also having problems applying template filters to date fields. The end result is that some data on the preview page is visible but other data is blank:
However, if I display the same data for posts that have actually been submitted, then everything displays properly:
My Code
models.py:
class Game(models.Model):
# Game Choices
FOOTBALL = 0
BASKETBALL = 1
TENNIS = 2
OTHER = 3
GAME_CHOICES = (
(FOOTBALL, 'Football'),
(BASKETBALL, 'Basketball'),
(TENNIS, 'Tennis'),
(OTHER, 'Other')
)
game_id = models.AutoField(primary_key=True)
location = models.CharField(max_length=200, verbose_name="Location")
game = models.IntegerField(choices=GAME_CHOICES, default=FOOTBALL)
game_date = models.DateField(verbose_name='Game Date')
forms.py
class GameForm(ModelForm):
class Meta:
model = Game
fields = (
'location',
'game',
'game_date'
)
I'm pretty sure that the problem is in my views.py: I'm not sure that I'm processing the POST request the right way to feed all data to the preview page.
views.py
def form_upload(request):
if request.method == 'GET':
form = GameForm()
else:
# A POST request: Handle Form Upload
form = GameForm(request.POST) # Bind data from request.POST into a GameForm
# If data is valid, proceeds to create a new game and redirect the user
if form.is_valid():
game = form.save()
return render(request, 'games/success.html', {})
return render(request, 'games/form_upload.html', {
'form': form,
})
preview.py
class GameFormPreview(FormPreview):
form_template = 'games/form_upload.html'
preview_template = 'games/preview.html'
def done(self, request, cleaned_data):
# Do something with the cleaned_data, then redirect
# to a "success" page.
return HttpResponseRedirect('/games/success')
form_upload.html
...
<form method="post">
{% csrf_token %}
<ul><li>{{ form.as_p }}</li></ul>
<button type="submit">Preview your post</button>
</form>
...
preview.html
{% load humanize %}
...
<h1>Preview your submission</h1>
<div>
<p>Location: {{ form.data.location }}</p>
<p>Game Date: {{ form.data.game_date|date:"l, F d, Y" }}</p>
<p>Game Type: {{ form.data.get_game_display }}</p>
</div>
<div>
<form action="{% url 'form_upload' %}" method="post">
{% csrf_token %}
{% for field in form %}
{{ field.as_hidden }}
{% endfor %}
<input type="hidden" name="{{ stage_field }}" value="2" />
<input type="hidden" name="{{ hash_field }}" value="{{ hash_value }}" />
<!-- Submit button -->
<button type="submit">Submit your post</button>
<!-- Go back button -->
<button type="submit">
<a href="{% url 'form_upload' %}"
onClick="history.go(-1);return false;" >
Go back and edit your post
</a>
</button>
</div>
</form>
</div>
...
Two issues
Essentially, I'm having these two issues:
String values for choices are not displayed. If I use the get_FOO_display() method in my preview.html template, it returns blank. However, if I use this in a page after the post has been submitted, it displays properly.
The humanize date filter doesn't work. If I apply a humanize filter ({{ form.data.game_date|date:"l, F d, Y" }}) in preview.html, it also displays blank. Again, this works for submitted posts.
My question essentially is: what's the right way to use the FormPreview here?
form.data does not have get_FOO_display attributes. When you access {{ form.data.get_game_display }} in the template, it fails silently and doesn't display anything.
The get_FOO_display are methods of the instance, so try this instead.
{{ form.instance.get_game_display }}
Wherever possible you should access data from form.cleaned_data (which is validated and 'cleaned') instead of form.data, which is the raw data submitted to the form.
The filters don't work with form.data.game_date because it's a raw string. They should work with form.cleaned_data.game_date, which has been converted to a python date object.
Finally, you haven't implemented anything in your done method, you've just copied the comment from the docs. You could create a new game using cleaned_data as follows:
def done(self, request, cleaned_data):
game = Game.objects.create(**cleaned_data)
return HttpResponseRedirect('/games/success')

Django CBV form using list of objects

I am trying to create a form that is a list of cars with one field being a BooleanField. I want this to appear as a form with the BooleanField being a checkbox. If the user checks this, then the BooleanField will be set = True and something will happen when a POST occurs and the user is redirected to the next page.
model.py:
class Car(models.Model):
year = models.IntegerField()
make = models.CharField(max_length=30)
model = models.CharField(max_length=30)
send = models.BooleanField(default=False)
currenly the email.html looks like this:
<form action="" method="post">{% csrf_token %}
{% for car in object_list %}
<input type="checkbox" name="car" id="car{{ forloop.counter }}" value="{{ car.id }}">
<label for="car{{ forloop.counter }}">{{ car.year }} {{ car.make }} {{ car.model }}</label><br>
{% endfor %}
<input type="submit" value="Preview">
</form>
views.py
class Email(ListView):
model = Car
template_name = 'cars/email.html'
Suggestions?
I ended up adding a post() function to the ListView which processes the form data, but I am having trouble redirecting without a valid HttpResponse object and am getting an error when one of the boxes isn't checked in the form. Here is the additional post() code that I added for the time being:
def post(self, request, *args, **kwargs):
cars = Car.objects.all() # initially reset "self.send" field == False
for i in range(len(cars)):
cars[i].send = False
cars[i].save()
cars = Car.objects.filter(id__in=request.POST.getlist('car'))
for i in cars:
i.send = True
i.save()
return HttpResponseRedirect(reverse('cars:email_preview'))
Any suggestions on how to make the form re-render with an error msg if no boxes are checked?
Thanks

save values in database using django

i have an custom form , whenever i fetch the form values to save in the database than it display an error ( applicationform() got an unexpected keyword argument 'job_title' ) and the values are not save in the table.
views.py :-
def applicationvalue(request):
if request.method == 'POST':
getjobtitle = request.POST['jobtitle']
getintable = applicationform(job_title=getjobtitle)
getintable.save()
print getjobtitle
return HttpResponse(getintable)
else:
return render_to_response('registration/applicationform.html')
my form is :-
<form method="POST" action="#" class="form-horizontal" id="applicationform" name="appform">
<input type="text" id="u_jobtitle" class="input-xlarge" name="jobtitle" value=" " />
<button class="btn btn-gebo" type="submit" name="usubmit">Save changes</button>
whenever i fetch the values from form to save the values in table field " job_title " than it will display an error :-
applicationform() got an unexpected keyword argument 'job_title'
Change input field name to job_title in your html
<input name="job_title" type="text" id="u_jobtitle" class="input-xlarge" value=" " />
-------------^ changed
and then in view do
def applicationvalue(request):
if request.method == 'POST':
#Dont need this
#getjobtitle = request.POST['jobtitle']
#---------------------------Use request.POST
getintable = applicationform(request.POST)
getintable.save()
print getjobtitle
return HttpResponse(getintable)
else:
return render_to_response('registration/applicationform.html')
It will be better if you use same form to render html instead of hand coding it.
The applicationform constructor should take the request.POST as argument.
But it seems to me that you are not using django forms in the "right" way. I think that your view doesn't follow the django philosophy for using form.
In your case, you should have a model:
from django.db import models
class Application(models.Model):
job_title = models.CharField(max_length=100)
Based on this model, you can declare a ModelForm:
from django import forms
from .models import ApplicationModel
class ApplicationForm(forms.ModelForm):
class Meta:
model = ApplicationModel
fields = ('job_title',)
Then you can use this form in your view
def applicationvalue(request):
if request.method == 'POST':
form = ApplicationForm(request.POST)
if form.is_valid():
#This is called when the form fields are ok and we can create the object
application_object = form.save()
return HttpResponse("Some HTML code") # or HttResponseRedirect("/any_url")
else:
form = ApplicationForm()
#This called when we need to display the form: get or error in form fields
return render_to_response('registration/applicationform.html', {'form': form})
finally you should have a registration/applicationform.html template with something like:
{% extends "base.html" %}
{% block content %}
<form action="" method="post">{% csrf_token %}
<table>
{{form.as_table}}
</table>
<input type="submit" value="Add">
</form>
{% endblock %}
I hope it helps

Categories

Resources