I have one large view function where a user can Add, Edit, Delete, and Update his education. I am currently doing this all in one view because I haven't yet learned how to split up views by function. Here is what I currently have --
I have a single URL pointing to the view --
url(r'^profile/edit/education/$', 'views.edit_education', name='edit_education')
Here is my model/modelform --
class Education(models.Model):
school = models.CharField(max_length=100)
class_year = models.IntegerField(max_length=4, blank=True, null=True, choices=YEAR)
degree = models.CharField(max_length=100, blank=True)
user = models.ForeignKey('UserProfile')
class EducationForm(ModelForm):
class Meta:
model = Education
exclude = ('user',)
Here is my view --
#login_required
def edit_education(request, edit=0):
"""
In the edit profile page, allows a user to edit his education
and add multiple school entries.
"""
profile = request.user.get_profile()
education = profile.education_set.order_by('-class_year') # for the template. display all eduation entries
# unindented for legibility
if request.method == 'POST':
if 'Add School' in request.POST.values():
form = EducationForm(data=request.POST, request=request) # passing request to form to do validation based on request.user
if form.is_valid():
new_education = form.save(commit=False)
new_education.user = profile
new_education.save()
return redirect('edit_education')
if 'Delete' in request.POST.values():
for education_id in [key[7:] for key, value in request.POST.iteritems() if key.startswith('delete')]:
Education.objects.get(id=education_id).delete()
return redirect('edit_education')
if 'Edit' in request.POST.values():
for education_id in [key[5:] for key, value in request.POST.iteritems() if value == 'Edit' and key.startswith('edit')]:
edit = 1
school_object = Education.objects.get(id = education_id)
form = EducationForm(instance = school_object, request=request)
return render_to_response('userprofile/edit_education.html', {'form': form, 'education':education, 'edit': edit, 'education_id': education_id}, context_instance=RequestContext(request))
if 'Cancel' in request.POST.values():
return redirect('edit_education')
if 'Save Changes' in request.POST.values():
form = EducationForm(request.POST, request=request, edit=1)
if form.is_valid():
Education.objects.get(id=request.POST['education_id']).delete() # is there a way to update instead of delete and re-add?
new_education = form.save(commit=False)
new_education.user = profile
new_education.save()
return redirect('edit_education')
else:
form = EducationForm(request=request)
return render_to_response('userprofile/edit_education.html', {'form': form, 'education': education, }, context_instance=RequestContext(request))
And finally, my template --
<h3>Edit education info for {{user.get_full_name}}</h3>
<form action="." method="post"> {% csrf_token %}
{% if education %}
{% for education in education %}
<p><b>{{ education.school }}</b> {% if education.class_year %}{{ education.class_year|shorten_year}}, {% endif %} {{ education.degree}}
<input type="submit" name="edit_{{education.id}}" value='Edit' />
<input type="submit" name="delete_{{education.id}}" value="Delete" /></p>
{% endfor %}
{% endif %}
<table>
<input type="hidden" name="education_id" value="{{education_id}}" />
<tr><td>School:</td><td>{{form.school}}{{form.school.errors}}</td></tr>
<tr><td>Class Year</td><td>{{form.class_year}}{{form.class_year.errors}}</td></tr>
<tr><td>Degree:</td><td>{{form.degree}}{{form.degree.errors}}</td></tr>
<tr>{{form.non_field_errors}}</tr>
</table>
{% if not edit %}
<p><input type="submit" name="add" value="Add School" ></p>
{% else %}
<p><input type="submit" name="save" value="Save Changes" >
<input type="submit" name="cancel" value="Cancel" ></p>
{% endif %}
</form>
And the end is here. How would I separate one of these actions in the view into separate view functions using separate URLs? One or two examples would be more than enough. Thank you very much for your help.
A few ideas:
You could split your one big html form element into chunks
You could use AJAX submit handler to change URL based on pressed submit button
You could do what user Cerales suggested, but instead of redirecting which loses POST data you could just call add_school() and other methods, possibly having also dictionary map of actions mapped to their handlers: action_map = {'Add School': add_school, ...} - this would eliminate chain of conditions
You could use class-based view which would be basically a class-based version of #3. Django docs for generic class-based views are here
I can elaborate on any of those ideas if you will.
--
EDIT:
Answering your question from comments:
from django.views.generic.base import View
class MySchoolView(View):
def post(self, request, *kargs, **kwargs):
if 'Add School' in request.POST:
return self.add_school(request, *kargs, **kwargs)
# (...)
def add_school(self, request, *kargs, **kwargs):
# (...)
Then in urls.py:
(r'^schools/add/$', MySchoolView.as_view())
Note that the above is not tested so might require some tweaks to work. View class source code is here.
There's a couple of ways you could do this.
This could be a part of your view:
if request.method == 'POST':
if 'Add School' in request.POST.values():
return HttpResponseRedirect('/add_school/')
Then this could be part of another view, corresponding with the /add_school/ url:
def add_school(request):
if request.method=='POST':
form = EducationForm(data=request.POST, request=request) # passing request to form to do validation based on request.user
if form.is_valid():
new_education = form.save(commit=False)
new_education.user = profile
new_education.save()
return redirect('edit_education')
Related
In my project, i have a template where i'm trying to put two forms for different use cases. I've never come across this problem before, so i don't really know where to go from here to use two forms in the same page.
At first i thought of creating another view to handle each form, but i think that this solution would create problems with the rendering of my templates, other than not being sustainable if i should have this problem again with another template.
After making some research, i found a solution but it works for class based views, but i'd like to avoid that since my view is already a function based view, and i would have to make a lot of changes in my code. However, if CBV is the best way to go, i can make the change.
Every advice is appreciated
First field
class FirstForm(forms.ModelForm):
firstfield = forms.CharField()
secondfield = forms.CharField()
class Meta:
model = MyModel
fields = ("firstfield", "secondfield")
def save(self, commit=True):
send = super(FirstForm, self).save(commit=False)
if commit:
send.save()
return send**
Second Form
class SecondForm(forms.ModelForm):
firstfield = forms.FloatField()
secondfield = forms.Floatfield()
thirdfield = forms.CharField()
class Meta:
model = MyModelTwo
fields = ("firstfield", "secondfield", "thirdfield")
def save(self, commit=True):
send = super(SecondForm, self).save(commit=False)
if commit:
send.save()
return send
Template
<h3> First Form </h3>
<form method="post" novalidate>
{% csrf_token %}
{% include 'main/includes/bs4_form.html' with form=form %}
<button type="submit" class="btn btn-danger" style="background-color: red;">SUBMIT</button>
</form>
<h3> Second Form </h3>
<form method="post" novalidate>
{% csrf_token %}
{% include 'main/includes/bs4_form.html' with form=form %}
<button type="submit" class="btn btn-danger" style="background-color: red;">SUBMIT</button>
</form>
views.py
def myview(request):
# if this is a POST request we need to process the form data
if request.method == 'POST':
# create a form instance and populate it with data from the request:
form = FirstForm(request.POST)
# check whether it's valid:
if form.is_valid():
# process the data in form.cleaned_data as required
# ...
# redirect to a new URL:
send = form.save()
send.save()
messages.success(request, f"Success")
# if a GET (or any other method) we'll create a blank form
else:
form = FirstForm()
return render(request,
"main/mytemplate.html",
context={"form":form})
I have been told to use a context in my view, but i don't know how to integrate it in my view. Is this a doable solution, or is there a better way to do this?
context = {
'first_form': TradingForm(request.POST or None),
'second_form': LimitSellForm(request.POST or None),
}
Here's one approach. Add a name attribute to your buttons, like this:
<button name="button1" type="submit" class="btn btn-danger" style="background-color: red;">SUBMIT</button>
...
<button name="button2" type="submit" class="btn btn-danger" style="background-color: red;">SUBMIT</button>
Then in your view, you can check which form has been submitted by looking for the button name in the post:
def myview(request):
if request.method == 'POST':
if 'button1' in request.POST:
form1 = FirstForm(request.POST)
if form1.is_valid():
# do what needs to be done and redirect
if 'button2' in request.POST:
form2 = form = SecondForm(request.POST)
if form2.is_valid():
# do what needs to be done and redirect
else:
form1 = FirstForm()
form2 = SecondForm()
return render(request, "main/mytemplate.html",
context={'form1': form1, 'form2': form2})
you can use TemplateView instead for normal view function and add this below
def get_context_data(self, **kwargs):
context = {
'first_form': TradingForm(request.POST or None),
'second_form': LimitSellForm(request.POST or None),
}
you can check in the documentation:
https://docs.djangoproject.com/en/2.2/ref/class-based-views/base/#templateview
my Save button does not save the user entered data into the database in a django form, where is the problem?
models.py
class Cemetery(models.Model):
id = models.AutoField(primary_key=True)
name=models.CharField(verbose_name="Cemetery Name",max_length=100)
city=models.CharField(max_length=30)
zipcode=models.CharField(max_length=5)
date_created=models.DateTimeField(editable=False, auto_now_add=True)
date_modified= models.DateTimeField(editable=False, auto_now=True)
created_by=models.ForeignKey('auth.User')
def __str__(self):
return str(self.id) +'-' + self.name + ' - ' + self.city
forms.py
class CemeteryForm(forms.ModelForm):
class Meta:
model=Cemetery
fields=('name','city','zipcode',)
views.py
def cemetery_add(request):
if request.method=="POST":
form=CemeteryForm(request.POST)
if form.is_valid():
cemetery=form.save(commit=False)
cemetery.name=request.name
cemetery.city=request.city
cemetery.zipcode=request.zipcode
cemetery.created_by=request.user
cemetery.date_created=timezone.now()
cemetery.save()
return redirect('cemetery_list')
else:
form=CemeteryForm
return render(request,'heaven/edit_cemetery.html',{'form':form})
template
{% extends 'heaven/base.html' %}
{% block content %}
<!-- Edit Cemetery -->
<h2>New Cemetery</h2>
<form method="POST" class="cemetery-form">{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="save btn btn-default">Save</button>
</form>
{% endblock %}
when I push the save button, this is the error I receive:
AttributeError at /cemetery_add/
'WSGIRequest' object has no attribute 'name'
In your form, you're looking up values directly on the request
request.name
You need to look in the form's cleaned data
form.cleaned_data.get('name')
Although, this isn't actually needed since the value is already set from the bound form.
If you really want to use the post data then it would be found in the POST dictionary
request.POST.get('name')
In short, ignore resetting these values and just add the fields you need
cemetery=form.save(commit=False)
cemetery.created_by=request.user
cemetery.date_created=timezone.now()
cemetery.save()
This one worked, I had an authentication problem, since my login session was expired I could not save:
def cemetery_add(request):
if request.method=='POST':
form=CemeteryForm(request.POST)
if form.is_valid():
cemetery=form.save(commit=False)
cemetery.name=request.POST.get('name')
cemetery.city=request.POST.get('city')
cemetery.zipcode=request.POST.get('zipcode')
cemetery.date_created=timezone.now()
if request.user.is_authenticated:
cemetery.created_by=request.user
else:
return login(request)
cemetery.save()
return redirect('cemetery_list')
else:
form=CemeteryForm
return render(request,'heaven/edit_cemetery.html',{'form':form})
First of all I'm glad to be here, I read you lately and i found useful answers here.
This is my first post so please be kind with me, I'm a newbie in programming.
So, I'm writing my 1st web application in Django - a todo app and I don't know how to write the function that does this this. I found something in Django docs and in other related discussions but it doesn't work.
Here's my code:
#models.py
class Task(models.Model):
user = models.ForeignKey(User)
task = models.CharField(max_length=200)
initialized_at = models.DateTimeField(auto_now_add=True)
due_date = models.DateField(default=datetime.now)
done = models.BooleanField(default=False)
def __unicode__(self):
return self.task
#views.py
def edit_task(request, id):
if request.method == 'POST':
task_to_edit = Task.objects.get(pk=task_id)
form = TaskForm(request.POST, instance=task_to_edit)
form.save()
if form.is_valid():
task_to_edit = form.save()
return HttpResponseRedirect('/')
else:
form = TaskForm()
return render(request, 'todo/edit_task.html', {'form': form})
#urls.py
url(r'^edit_task/(?P<task_id>\w+)/$', 'todo.views.edit_task')
#edit_task.html
{% block content %}
<form action="/edit_task/" method="post">{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Submit" />
</form>
{% endblock content %}
When I submit the updated form I get this error:
Page not found (404)
Request Method: POST
Request URL: hxxp://127.0.0.1:8000/edit_task/
Using the URLconf defined in jbz.urls, Django tried these URL patterns, in this order:
^admin/
^$ [name='index']
^(?P<task_id>\d+)/$
^(?P<task_id>\d+)/$
^add-task/$
^delete-task/(?P<task_id>\w+)/$
^edit_task/(?P<id>\w+)/$
^done/(?P<task_id>\d*)/$
The current URL, edit_task/, didn't match any of these.
and the root urls.py looks like:
url(r'', include('todo.urls'))
#edit_task.html
{% block content %}
<form action="/edit_task/{{task.id}}" method="post">{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Submit" />
</form>
{% endblock content %}
Notice how I added {{task.id}} expression in <form action="/edit_task/{{task.id}}" method="post">
IMPORTANT NOTE: Substitute {{task.id}} to whatever variable accomplishes this in your template.
The reason why you get the error is because edit_task/ is not getting the other part, task_id to match the regular expression:
url(r'^edit_task/(?P<task_id>\w+)/$', 'todo.views.edit_task')
UPDATE: Also your edit_task view has potential errors as well>
def edit_task(request, id):
task_to_edit = Task.objects.get(pk=id)
if request.method == 'POST':
form = TaskForm(request.POST, instance=task_to_edit)
form.save()
if form.is_valid():
task_to_edit = form.save()
return HttpResponseRedirect('/')
else:
form = TaskForm(instance=task_to_edit)
# you don't pass any task variable to the view so the form view
# won't know which task to edit, you'll have to handle that
return render(request, 'todo/edit_task.html', {'form': form, 'task':task_to_edit})
Note: I corrected the code in the view a little. Now the task_to_edit is passed also to the Form to fill the fields when the view is requested via GET. Notice that in order to access to this view, the url in the browser should look like this http://www.example.com/edit_task/2
If other wise you try to access http://www.example.com/edit_task without passing the id you'll get Error 404.
Hope this helps!
I think your pattern for edit task expects an id - task name. Try changing your URL pattern:
'^edit_task/(?P<task_id>\w+)/$'
to
'^edit_task/$'
or providing the task id that you want to edit.
Just add name space to your url and according update your template.
#urls.py
url(r'^edit_task/(?P<task_id>\w+)/$', 'todo.views.edit_task', name= "edit_task")
#edit_task.html
{% block content %}
<form action="{% url 'edit_task' task_id %}" method="post">{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Submit" />
</form>
{% endblock content %}
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
I'm familar with using templates to collect the data, but on displaying is there a smart way that Django will display the fields and populate them with the right values. I can do it manually of course, but the model knows the field type. I didn't see any documentation on this. For example I collect data from the template with:
<strong>Company Name</strong>
<font color="red">{{ form.companyname.errors }}</font>
{{ form.companyname }}
where form is my company model containing all the fields. How would I go about ensuring that I could use this type of methodology such that Django would render the text fields and populate with the current values. For example is there a way to send in values in the following way:
myid = int(self.request.get('id'))
myrecord = Company.get_by_id(myid)
category_list = CompanyCategory.all()
path = os.path.join(os.path.dirname(__file__), 'editcompany.html')
self.response.out.write(template.render(path, {'form': myrecord, 'category_list': category_list}))
Can I do the same this with records and will the template populate with values sent in? Thanks
It sounds like you may be confused about the difference and proper usage of Form vs ModelForm
Regardless of which type of form you use, the templating side of forms stays the same:
Note: all of the values in your form (as long as its bound to POST or has an instance) will be prepopulated at render.
<form class="well" action="{% url member-profile %}" method="POST" enctype="multipart/form-data">{% csrf_token %}
<fieldset>
{{ form.non_field_errors }}
{{ form.display_name.label_tag }}
<span class="help-block">{{ form.display_name.help_text }}</span>
{{ form.display_name }}
<span class="error">{{ form.display_name.errors }}</span>
{{ form.biography.label_tag }}
<span class="help-block">{{ form.biography.help_text }}</span>
{{ form.biography }}
<span class="error">{{ form.biography.errors }}</span>
<input type="submit" class="button primary" value="Save" />
</fieldset>
</form>
if you want to be populating a form from a record (or submit a form as a record) its probably best to use ModelForm
EX a profile form that doesn't display the User FK dropdown:
class ProfileForm(forms.ModelForm):
"""Profile form"""
class Meta:
model = Profile
exclude = ('user',)
The View:
def profile(request):
"""Manage Account"""
if request.user.is_anonymous() :
# user isn't logged in
messages.info(request, _(u'You are not logged in!'))
return redirect('member-login')
# get the currently logged in user's profile
profile = request.user.profile
# check to see if this request is a post
if request.method == "POST":
# Bind the post to the form w/ profile as initial
form = ProfileForm(request.POST, instance=profile)
if form.is_valid() :
# if the form is valid
form.save()
messages.success(request, _(u'Success! You have updated your profile.'))
else :
# if the form is invalid
messages.error(request, _(u'Error! Correct all errors in the form below and resubmit.'))
else:
# set the initial form values to the current user's profile's values
form = ProfileForm(instance=profile)
return render(
request,
'membership/manage/profile.html',
{
'form': form,
}
)
notice that the outer else initializes the form with an instance: form = ProfileForm(instance=profile) and that the form submit initializes the form with post, BUT still binds to instance form = ProfileForm(request.POST, instance=profile)
If you're looking at forms, it would seem like a good idea to start with Django's forms framework, specifically forms for models.