Edit/Add objects using the same django form - python

I already used the answer to this question, but for some reason I'm not getting a good result.
I'm trying to use the same template for my edit form and my add form. Here's my urls.py:
url(r'^app/student/new/$', 'edit_student', {}, 'student_new'),
url(r'^app/student/edit/(?P<id>\d+)/$', 'edit_student', {}, 'student_edit'),
And my views.py:
def edit_student(request, id=None, template_name='student_edit_template.html'):
if id:
t = "Edit"
student = get_object_or_404(Student, pk=id)
if student.teacher != request.user:
raise HttpResponseForbidden()
else:
t = "Add"
student = Student(teacher=request.user)
if request.POST:
form = StudentForm(request.POST, instance=student)
if form.is_valid():
form.save()
# If the save was successful, redirect to another page
redirect_url = reverse(student_save_success)
return HttpResponseRedirect(redirect_url)
else:
form = StudentForm(instance=student)
return render_to_response(template_name, {
'form': form,
't': t,
}, context_instance=RequestContext(request))
And my forms.py:
class StudentForm(ModelForm):
class Meta:
model = Student
exclude = ('teacher',)
And finally my template student_edit_template.html:
<h1>{{ t }} Student</h1>
<form action="/app/student/edit/{{ student.id }}" method="post"> {% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Submit" />
</form>
For some reason, this is throwing a 404:
Page not found (404)
Request Method: POST
Request URL: http://192.168.1.3:5678/app/student/edit/
I'm probably missing something easy here, but at this point I need another set of eyes on it at the very least.
Thanks in advance!

You're getting the 404 because /student/edit/ requires an id at the tail end otherwise there's no route, and when you're coming from /student/new/ you don't have an id yet. Create a route and view for /student/edit/ and put logic in there to handle the case for when you're creating a record on POST.

Related

Django checkout not accessible: Page not found (404)

I'm trying to develop an e-commerce site with Django. So I'm at this point where, users can add items to their cart, but when I try to proceed to checkout, for some reason, my checkout form is not displayed rather, it says:
Page not found (404)
I made sure that I have registered my models, and ran migrations.
What is the problem?
My views.py:
#login_required
def checkout(request):
address_form = UserAddressForm(request.POST or None)
if address_form.is_valid():
new_address = address_form.save(commit= False)
new_address.user = request.user
new_address.save()
else:
raise Http404
print(form.errors)
context = {"address_form": address_form}
template = "orders/checkout.html"
return render(request, template, context)
My checkout.html:
<form method="POST" action=''>
{% csrf_token %}
<fieldset class="form-group">
{{ address_form|crispy }}
</fieldset>
<div class="form-group">
<input type="submit" class="btn btn-outline-dark" value="Place Order"/>
</div>
</form>
My urls.py:
from orders import views as orders_views
path('checkout/', orders_views.checkout, name='checkout'),
You've implemented GET request handling incorrectly, for reference see this example from the docs. In your case form was always invalid because in case of GET request it was initialized with none. However you don't even have to validate empty form on GET request.
Your code updated:
#login_required
def checkout(request):
if request.method == 'POST':
address_form = UserAddressForm(request.POST)
if address_form.is_valid():
new_address = address_form.save(commit= False)
new_address.user = request.user
new_address.save()
return # TODO : return what?
else:
# otherwise (if GET request) we get here
address_form = UserAddressForm()
context = {"address_form": address_form}
return render(request, "orders/checkout.html", context)
And you need to specify what is supposed to happen when the form is valid: redirect for example.

Having two different forms in a Django template

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

Update view not working? Django

The users are able to create an aircraft post at this url:
url(r'^upload/aircraft/$', aircraft_create, name="aircraft_create"),
I've created a summary page where all of the users posts are displayed. They're able to edit and delete their posts here. The url:
url(r'^account/uploads/$', upload_overview, name="account_uploads"),
However, I want the user to be able to edit their posts on their summary page. The way I got it set it now, is that they can edit at upload/aircraft/edit, but I want to to be account/uploads/edit.
I've set it up like that but it's not doing anything? Any clues as to what it might be?
Aircraft/views.py
def aircraft_create(request):
form = aircraft_form(request.POST or None)
if form.is_valid():
instance = form.save(commit=False)
instance.user = request.user
instance.save()
messages.success(request, "Your upload has been successfully added!")
return HttpResponseRedirect(instance.get_absolute_url())
else:
messages.error(request, "There seems to be something wrong. Have a look again..!")
context = {"form":form,}
return render(request,'aircraft/aircraft_form.html', context)
Update view
def aircraft_update(request, id=None):
aircraft = get_object_or_404(Aircraft, id=id)
form = aircraft_form(request.POST or None, instance=aircraft)
if form.is_valid():
form.save()
messages.success(request, "Your edit has been successfully been saved!")
return HttpResponseRedirect(aircraft.get_absolute_url())
return render(request,'aircraft/aircraft_form.html',
{"aircraft": aircraft, "form": form})
Template
{% if UploadedAircraft %}
{% for upload in UploadedAircraft %}
<div class="col-lg-offset-0 col-md-4 col-sm-3 item">
<div class="box"><img src="{{ upload.image.url }}" width="200px" height="200px" alt="{{ upload.title }}"/>
<h3 class="name">{{ upload.name }}</h3>
<button class="btn">Edit</button>
<button class="btn">Delete</button>
</div>
Summary page view
def upload_overview(request):
uploaded_aircraft = Aircraft.objects.filter(user=request.user)
return render(request,'account/upload_overview.html',{'UploadedAircraft':uploaded_aircraft)
url.py
#aircraft/detail/1
url('^aircraft/detail/(?P<id>\d+)/$', aircraft_detail, name='aircraft_detail'),
#account/uploads (Display Users uploads)
url(r'^account/uploads/$', upload_overview, name="account_uploads"),
#upload/aircraft (Create Aircraft)
url(r'^upload/aircraft/$', aircraft_create, name="aircraft_create"),
#Edit/aircraft
url('^account/uploads/$', aircraft_update, name='aircraft_update'),
EDIT
def airline_update(request, id=None):
airline = get_object_or_404(Airline, id=id)
form = airline_form(request.POST or None, instance=airline)
if form.is_valid():
form.save()
return HttpResponseRedirect(airline.get_absolute_url())
return render(request,'airline/airline_form.html',
{"airline": airline, "form": form})
Url
#account/upload/edit/ (Edit airline)
url(r'^account/uploads/edit/(?P<id>[0-9]+)$', airline_update, name='airline_update'),
Template
<button class="btn">Edit</button>
Assuming you're trying to edit an aircraft with a specific ID, you would need something like this in url.py (assuming that the IDs are integers):
url('^account/uploads/edit/(?P<id>[0-9]+)$', aircraft_update, name='aircraft_update')
And in your template you would need to update your anchor link to include the ID:
<a href="{% url 'aircraft_update' id=upload.id %}">
NOTE that this assumes that the upload object (in your template's loop) includes an id property, and that the id property corresponds to the aircraft ID that you want to update. (It is possible that you have named this property something else.)
EDIT: This would be sufficient for executing a GET request. However, I notice that your view definition for aircraft_update also attempts to check whether a form is valid. Where is this form in your template? It does not appear to be in your loop.

Django form in the base template. How to display validation error?

I use Django 1.8.14. I have Search form on every page of my website. I pass Search form to base template through context processor. Each time form sends data to /search/ view. And there is a problem. Django raises ValidationError on form, but it doesn't display anywhere. What is the correct way to display form errors in template, when form passes to base template through context processor and sends data to one view?
form.py:
class SearchForm(forms.Form):
search = forms.CharField(required = True,
max_length=255,
widget = forms.TextInput({'class':'form-control', 'type':'search','required':''})
)
def clean_search(self):
search = self.cleaned_data.get("search")
regex = myregex
if not re.match(regex, search):
print("ValidationError")
raise forms.ValidationError(u'Please enter a valid value')
return search
context processor:
from myproject.forms import SearchForm
def form_context(request):
context_dict = {}
context_dict['search_form'] = SearchForm()
return(context_dict)
my base template:
<form method="post" action="/search/">
{% csrf_token %}
{{ search_form.non_field_errors }}
{{ search_form.errors }}
{{ search_form.search }}
{{ search_form.search.errors }}
<button type="submit" class="btn btn-find">Search</button>
</form>
my seach view:
def search(request, template):
if request.method == 'POST':
search_form = SearchForm(request.POST)
if search_form.is_valid():
domen = search_form.cleaned_data['search']
try:
site = SitePage.objects.get(domen=domen)
path="/"+site.domen +"/"
return HttpResponseRedirect(path)
except:
site = None
else:
print search_form.errors
return render(request, template, context_dict)
If you Django is raising validation error how you want it to and now all you need is to display that validation error on your html templates then i suppose what you are looking for is Django Messages
See the official documentation for the same -> https://docs.djangoproject.com/en/1.8/ref/contrib/messages/
You need to pass the form once it is bound to the data (the POST request) and validated; right now your context only has a blank form which is why no errors are being displayed.
from django.shortcuts import redirect
def search(request, template):
search_form = SearchForm(request.POST or None, request.FILES or None)
if search_form.is_valid():
domen = search_form.cleaned_data['search']
results = SitePage.objects.filter(domen=domen)
if results.exists():
return redirect('/{}/'.format(results.domen))
return render(request, template, {'form': search_form})

Break up several actions into multiple view functions

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')

Categories

Resources