Two views in a Django page - django

I'm having problems displaying the input box of my page. On this same page I would like to have two views, one that accesses and retrieves data and the other a form. As it's an event sign-up page, the first view show the details of a specific event and the second is a view to remove a specific user from the event itself.
My views.py
def ShowSpecificEvent(request, eventslug):
if request.method == 'POST':
form = RemovalForm(request.POST)
if form.is_valid():
event = Event.objects.get(slug=eventslug)
context = {'event': event,}
updated_event = event.signed_up.remove(for_removal = form.cleaned_data['for_removal'],)
updated_event.save()
return render_to_response('base_specific_event.html', context, context_instance=RequestContext(request))
event = Event.objects.get(slug=eventslug)
context = {'event': event,}
return render_to_response('base_specific_event.html', context, context_instance=RequestContext(request))
def Remove(request, eventslug):
if request.method == 'POST':
form = RemovalForm(request.POST)
if form.is_valid():
event = Event.objects.get(slug=eventslug)
context = {'event': event,}
updated_event = event.signed_up.remove(for_removal = form.cleaned_data['for_removal'],)
updated_event.save()
return render_to_response('base_specific_event.html', context, context_instance=RequestContext(request))
return HttpResponseRedirect('base_remove_user.html')
My template
{% block content %}
event details...
{% if user.is_authenticated %}
{% if event.sign_up_is_live %}
<p> Sign me up!</p>
<form action='' method='post'>
{% csrf_token %}
{% if form.errors %}<p>Please correct the following fields:</p>{% endif %}
<div class='register_div'>
{% if form.for_removal.errors %} <p class='error'>{{ form.for_removal.errors }}</p>{% endif %}
<p><label for='for_removal'{% if form.for_removal.errors %}class='error'{% endif %}>User to be removed:</label></p>
<p>{{ form.for_removal }}</p>
</div>
<p><input type='submit'></p>
</form>
{% endif %}{% endif %}
{% endblock %}
I've read this which I think is the most relevant but the method prescribed doesn't really help.
I've also thought of splitting up the two pages, but I won't be able to retrieve the eventslug in my remove user page.
Thanks in advance :)
Update:
gutrt's answer was a great help and I've managed to get the input box to appear by changing ShowSpecificEvent to the following:
def ShowSpecificEvent(request, eventslug):
event = Event.objects.get(slug=eventslug)
form = RemovalForm(request.POST or None)
context = {'event': event, 'form': form,}
if request.method == 'POST':
if form.is_valid():
updated_event = event.signed_up.remove(request.POST.get('for_removal'))
updated_event.save()
return HttpResponseRedirect('base_user_removed.html')
else:
return render_to_response('base_specific_event.html', context, context_instance=RequestContext(request))
return render_to_response('base_specific_event.html', context, context_instance=RequestContext(request))
However, after submitting the form, I get a ValueError(invalid literal for int() with base 10: 'a_technicolor_skye') where a_technicolor_skye is the user I'm trying to remove. Does anyone have any ideas? Btw, event.signed_up is a many to many field and I've set it to null=True and blank=True.

You can pass all of the information into the context variable:
context = {'event': event, 'form': my_form}
Then both are available in your template. Note that this means you are going to end up using 1 view but computing all the information for the page in that view or its methods.

Related

request.method and request.GET in Django

I am following a tutorial and I am unable to understand some lines in it:
from django.shortcuts import render, get_object_or_404
from django.http import HttpResponseRedirect
from . models import Page
from .forms import ContactForm
def index(request, pagename):
pagename = '/' + pagename
pg = get_object_or_404(Page, permalink=pagename)
context = {
'title': pg.title,
'content': pg.bodytext,
'last_updated': pg.update_date,
'page_list': Page.objects.all(),
}
# assert False
return render(request, 'pages/page.htm', context)
def contact(request):
submitted = False
if request.method == 'POST':
form = ContactForm(request.POST)
if form.is_valid():
cd = form.cleaned_data
#assert False
return HttpResponseRedirect('/contact?submitted=True')
else:
form = ContactForm()
if 'submitted' in request.GET:
submitted = True
return render(request,'pages/contact.htm',{'form': form, 'page_list': Page.objects.all(), 'sbmitted': submitted})
The above is pages/view.py file
{% extends "pages/page.htm" %}
{% block title %} Contact us {% endblock title %}
{% block content %}
<h1>Contact us</h1>
{% if submitted %}
<p class="success">
Your message was submitted successfully. Thankm you.
</p>
{% else %}
<form action="" method="post" novalidate>
<table>
{{ form.as_table }}
<tr>
<td>&NonBreakingSpace;</td>
<td><input type="submit" value="Submit"></td>
</tr>
</table>
{% csrf_token %}
</form>
{% endif %}
{% endblock content %}
The above is pages/contact.htm file
So, what is the meaning of
if requested.method == 'POST':
and why is there the following check?
if submitted in request.GET:
submitted=True
request.method gives which method is to submit the form so the first
thing checks if the form is submitted with the post method
request.GET returns a context(similar to dictionary in python) of all the variables passed by GET method
And there should be
if request.GET.get('submitted') == "True":
submitted = True
Instead of
if submitted in request.GET:
submitted=True
request.GET.get('submitted') gives value of submitted passed in url
And the thing to note is that both submitted in above code are different the former one is a key in context(similar to dictionary ) and the later one is a variable in views.py
you can send the data via GET or POST. With GET you send the data through the URL. e.g.
www.mydomain.com/Form?Key1=xxxxx&Key2=yyyyyyy
With POST the data is sent "hidden". For example, in a login form you don't want the password to be visible in the url. That's why in these forms is used as a method of sending POST.
if request.method == 'POST': that validates that the data you are sending is in POST format
2.
else:
form = ContactForm()
if 'submitted' in request.GET:
submitted = True
This means that if the sending method was not POST, but GET, look if within the information that was sent there is a submitted parameter and if so, set its value as True.

Context Variable renders the previous value upon refresh

I have created a form that accepts a file upload and fetches some interesting data from the file upon POST.
However, upon refreshing the page, the form is back to its initial state but the data from the previous file remains. How do I fix it?
Here's my code:
forms.py
choices = (('all', 'all'),
('one', 'one'))
class TicketDetailForm(forms.Form):
file = forms.FileField()
type = forms.ChoiceField(choices=choices)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
views.py
def home(request):
detail = []
if request.method == 'POST':
form = TicketDetailForm(request.POST, request.FILES)
if form.is_valid():
if form.cleaned_data['type'] == 'all':
file = form.cleaned_data['file'].read()
detail.append([str(file, 'utf-8')])
# more workaround with the file
else:
form = TicketDetailForm()
return render(request, 'home.html', {'form': form,
'detail': detail})
home.html
{% extends 'base.html' %}
{% block content %}
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
<p>{{form.as_p}}</p>
<input type="submit" value="Submit"/>
</form>
{% if detail %}
<div class="row">
<p>The detail is as follows:</p>
{% for d in detail %}
{{ d }}
{% endif %}
</div>
{% endif %}
{% endblock %}
This is because you re-post the form when you refresh the page, since your view just renders the template even if the form is valid. The proper behaviour for a server is to always redirect after successfully submitting a POST. In other words, inside if form.is_valid() you should end by return redirect('home').
Your views should always do the following:
For a GET request, render the template
For a POST request and invalid form, render the template (with the invalid form so the user can fix errors). Hitting refresh is ok here, it will just re-submit the invalid form and therefore won't cause issues.
For a POST request and valid form, redirect, so that the last request is a GET request and the user cannot re-submit when refreshing. This also avoids the valid form to be rendered again.

Django: Return to template along with template variables

I am using Django to build a student-teacher portal.
I have two groups of users - teachers and students. If user is a teacher, i need to provide a different template (a form for selecting student's registration number). I followed this link to do the same.
Here are the code snippets:
home.html
{% if is_teacher %}
<p style="color:blue; text-align:center; font-size:160%"><b>Course taken: <span style="color:green">IT000</span></b></p>
<form action="/" method="post" align="center">
{% csrf_token %}
<div align="center">{{ form }}</div>
<input type="submit" value="Get student's results!" class="btn btn-primary" style="margin-top:10px"/>
</form>
{% else %}
<p style="color:blue; text-align:center; font-size:160%"><b>Performance for the subject <span style="color:green">IT000</span> is shown below.</b></p>
{% endif %}
views.py
#login_required(login_url="login/")
def home(request):
is_teacher = request.user.groups.filter(name='teachers').exists()
if is_teacher:
if request.method == 'POST':
form = Regno(request.POST)
if form.is_valid():
selected_reg = Student.objects.filter(regno=request.POST.get('regno'))
return render(request, 'home.html',{'selected_reg': selected_reg,'form':form})
else:
form = Regno()
return render(request, 'home.html', {'form': form,'user':request.user,'is_teacher':is_teacher})
else:
selected_reg = Student.objects.filter(regno=request.user)
return render(request, 'home.html', {'user':request.user,'is_teacher':is_teacher,'selected_reg':selected_reg})
Here, Regno is a Form for the teacher to enter student's registration no.
When a teacher initially logs in, he is displayed the form. However, after he submits the form, it is not displaying the form. It executes the {% else %} part of the template. How do I make sure the is_teacher template variable is passed on to this template after the teacher submits the form?
I read about Django sessions but I'm not sure if it can help.
Instead of passing the is_teacher every time, you should use RequestContext to pass it along with your requests.
Looks like in your post call on form submit, you are not passing is_teacher variable. (in below code)
if form.is_valid():
selected_reg = Student.objects.filter(regno=request.POST.get('regno'))
return render(request, 'home.html',{'selected_reg': selected_reg,'form':form})
You should add is_teacher in above.
Also, better way to handle it via custom template tag -
register = template.Library()
#register.filter(name='has_group')
def has_group(user, group_name):
group = Group.objects.get(name=group_name)
return True if group in user.groups.all() else False
and in the template -
{% if request.user|has_group:'teachers' %}
....
{% else %}
....
{% endif %}
You can avoid passing it with each call. Hope it helps.

Displaying validation errors at the right position in a Django page containing multiple form fields

In a Django social networking website I built, users can chat in a general room, or create private groups.
Each user has a main dashboard where all the conversations they're a part of appear together, stacked over one another (paginated by 20 objects). I call this the unseen activity page. Every unseen conversation on this page has a text box a user can directly type a reply into. Such replies are submitted via a POST request inside a <form>.
The action attribute of each <form> points to different urls, depending on which type of reply was submitted (e.g. home_comment, or group_reply). This is because they have different validation and processing requirements, etc.
The problem is this: If a ValidationError is raised (e.g. the user typed a reply with forbidden characters), it gets displayed on multiple forms in the unseen_activity page, instead of just the particular form it was generated from. How can I ensure all ValidationErrors solely appear over the form they originated from? An illustrative example would be great!
The form class attached to all this is called UnseenActivityForm, and is defined as such:
class UnseenActivityForm(forms.Form):
comment = forms.CharField(max_length=250)
group_reply = forms.CharField(max_length=500)
class Meta:
fields = ("comment", "group_reply", )
def __init__(self,*args,**kwargs):
self.request = kwargs.pop('request', None)
super(UnseenActivityForm, self).__init__(*args, **kwargs)
def clean_comment(self):
# perform some validation checks
return comment
def clean_group_reply(self):
# perform some validation checks
return group_reply
The template looks like so:
{% for unseen_obj in object_list %}
{% if unseen_obj.type == '1' %}
{% if form.comment.errors %}{{ form.comment.errors.0 }}{% endif %}
<form method="POST" action="{% url 'process_comment' pk %}">
{% csrf_token %}
{{ form.comment }}
<button type="submit">Submit</button>
</form>
{% if unseen_obj.type == '2' %}
{% if form.group_reply.errors %}{{ form.group_reply.errors.0 }}{% endif %}
<form method="POST" action="{% url 'process_group_reply' pk %}">
{% csrf_token %}
{{ form.group_reply }}
<button type="submit">Submit</button>
</form>
{% endif %}
{% endfor %}
And now for the views. I don't process everything in a single one. One function takes care of generating the content for the GET request, and others take care handling POST data processing. Here goes:
def unseen_activity(request, slug=None, *args, **kwargs):
form = UnseenActivityForm()
notifications = retrieve_unseen_notifications(request.user.id)
page_num = request.GET.get('page', '1')
page_obj = get_page_obj(page_num, notifications, ITEMS_PER_PAGE)
if page_obj.object_list:
oblist = retrieve_unseen_activity(page_obj.object_list)
else:
oblist = []
context = {'object_list': oblist, 'form':form, 'page':page_obj,'nickname':request.user.username}
return render(request, 'user_unseen_activity.html', context)
def unseen_reply(request, pk=None, *args, **kwargs):
if request.method == 'POST':
form = UnseenActivityForm(request.POST,request=request)
if form.is_valid():
# process cleaned data
else:
notifications = retrieve_unseen_notifications(request.user.id)
page_num = request.GET.get('page', '1')
page_obj = get_page_obj(page_num, notifications, ITEMS_PER_PAGE)
if page_obj.object_list:
oblist = retrieve_unseen_activity(page_obj.object_list)
else:
oblist = []
context = {'object_list': oblist, 'form':form, 'page':page_obj,'nickname':request.user.username}
return render(request, 'user_unseen_activity.html', context)
def unseen_group_reply(group_reply, pk=None, *args, **kwargs):
#similar processing as unseen_reply
Note: the code is a simplified version of my actual code. Ask for more details in case you need them.
Following the discussion in the comments above:
What I suggest is that you create a form for each instance in the view. I have refactored your code to have a function which returns object lists which you can use in both unseen_reply and group_reply functions:
def get_object_list_and_forms(request):
notifications = retrieve_unseen_notifications(request.user.id)
page_num = request.GET.get('page', '1')
page_obj = get_page_obj(page_num, notifications, ITEMS_PER_PAGE)
if page_obj.object_list:
oblist = retrieve_unseen_activity(page_obj.object_list)
else:
oblist = []
# here create a forms dict which holds form for each object
forms = {}
for obj in oblist:
forms[obj.pk] = UnseenActivityForm()
return page_obj, oblist, forms
def unseen_activity(request, slug=None, *args, **kwargs):
page_obj, oblist, forms = get_object_list_and_forms(request)
context = {
'object_list': oblist,
'forms':forms,
'page':page_obj,
'nickname':request.user.username
}
return render(request, 'user_unseen_activity.html', context)
Now, you need to access the form in template using the object id from forms dict.
{% for unseen_obj in object_list %}
<!-- use the template tag in the linked post to get the form using obj pk -->
{% with forms|get_item:unseen_obj.pk as form %}
{% if unseen_obj.type == '1' %}
{% if form.comment.errors %}{{ form.comment.errors.0 }}{% endif %}
<form method="POST" action="{% url 'process_comment' pk %}">
{% csrf_token %}
{{ form.comment }}
<button type="submit">Submit</button>
</form>
{% elif unseen_obj.type == '2' %}
{% if form.group_reply.errors %}{{ form.group_reply.errors.0 }}{% endif %}
<form method="POST" action="{% url 'process_group_reply' pk %}">
{% csrf_token %}
{{ form.group_reply }}
<button type="submit">Submit</button>
</form>
{% endif %}
{% endwith %}
{% endfor %}
While processing the reply, you again need to attach the form which throws error with the particular object pk:
def unseen_reply(request, pk=None, *args, **kwargs):
if request.method == 'POST':
form = UnseenActivityForm(request.POST,request=request)
if form.is_valid():
# process cleaned data
else:
page_obj, oblist, forms = get_object_list_and_forms(request)
# explicitly set the form which threw error for this pk
forms[pk] = form
context = {
'object_list': oblist,
'forms':forms,
'page':page_obj,
'nickname':request.user.username
}
return render(request, 'user_unseen_activity.html', context)

django view redirects to URL it shouldn't

I have the following view
def edit_booking(request, pk=None):
if not request.user.is_authenticated:
raise Http404
agent = Agent.objects.get(user=request.user)
booking = get_object_or_404(Booking, pk=pk)
form = BookingForm(request.POST or None, instance=booking)
if form.is_valid():
instance = form.save(commit=False)
instance.save()
return HttpResponseRedirect(instance.get_absolute_url())
elif form.errors:
messages.error(request,"There was a problem, please try again")
context = {
"form": form,
}
return render(request,'booking_form.html', context)
I use the following urls.py
urlpatterns = [
url(r'^booking/create', create_booking, name="create-booking"),
url(r'^booking/$', booking_list, name="booking-list"),
url(r'^booking/(?P<pk>\d+)/$', booking_detail, name="booking-detail"),
url(r'^booking/(?P<pk>\d+)/edit', edit_booking, name="edit-booking"),
]
For some reason when I try to submit the form after editing some booking (e.g. http://127.0.0.1:8000/booking/24/edit) I am automatically redirected to (http://127.0.0.1:8000/booking/24/).
As far as I can tell django is not processing any further code in the view. I tried to figure out with simple print("something") to see where in the code it ends up but it just goes to the url right away as soon as I submit from the template. For completeness sake this is the template:
{% extends 'base.html' %}
<div class="col-sm-6 col-sm-offset 3">
{% block content %}
<form method="POST" action=".">{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Save"/>
</form>
{% endblock %}
</div>
".", which you used as the action of the form, is interpreted by browsers as "the base of the current path directory". Since you have not used a trailing slash in your /edit URL, the browser submits the form to the nearest base, ie /booking/24.
You should always use a trailing slash:
url(r'^booking/create/$', create_booking, name="create-booking"),
url(r'^booking/$', booking_list, name="booking-list"),
url(r'^booking/(?P<pk>\d+)/$', booking_detail, name="booking-detail"),
url(r'^booking/(?P<pk>\d+)/edit/$', edit_booking, name="edit-booking"),
You need to check for the request method otherwise it will redirect on initial form rendering because django uses the same view for initial rendering and submitting the form.
if request.method == 'POST':
if form.is_valid():
instance = form.save(commit=False)
instance.save()
return HttpResponseRedirect(instance.get_absolute_url())
elif form.errors:
messages.error(request,"There was a problem, please try again")
else:
context = {
"form": form,
}
return render(request,'booking_form.html', context)