Django not displaying correct URL after reverse - django

There's lots of documentation about Django and the reverse() method. I can't seem to locate my exact problem. Suppose I have two urlconfs like this:
url(r'ParentLists/$', main_page, name = "main_page"),
url(r'ParentLists/(?P<grade_level>.+)/$', foo, name = "foo")
and the two corresponding views like this:
def main_page(request):
if request.method == 'POST':
grade_level = request.POST['grade_picked']
return HttpResponseRedirect(reverse('foo', args = (grade_level,)))
else:
return render(request, 'index.html', context = {'grade_list' : grade_list})
def foo(request, grade_level):
grade_level = request.POST['grade_picked']
parent_list = # get stuff from database
emails = # get stuff from database
return render(request, 'list.html', context = {'grade_list' : grade_list, 'parent_list' : parent_list})
Here, list.html just extends my base template index.html, which contains a drop down box with grade levels. When the user goes to /ParentLists, the main_page view renders index.html with the drop down box as it should.
When the user picks a grade level from the drop down box (say 5th Grade), the template does a form submit, and main_page once again executes - but this time the POST branch runs and the HttpResponseRedirect takes the user to /ParentLists/05. This simply results in an HTML table pertaining to grade 5 being displayed below the drop down box.
The problem is, when the user now selects say 10th Grade, the table updates to show the grade 10 content, but the URL displayed is still /ParentLists/05. I want it to be /ParentLists/10.
Clearly, after the first selection, the main_page view never executes again. Only foo does...and so the HttpResponseRedirect never gets called. How should I reorganize this to get what I'm looking for? Thanks in advance!

As you correctly mentioned you will never redirect to foo() from foo().
So the simple way to fix this is just add similar code as in main_page() view:
def foo(request, grade_level):
if request.method == 'POST':
grade_level = request.POST['grade_picked']
return HttpResponseRedirect(reverse('foo', args = (grade_level,)))
else:
parent_list = # get stuff from database
emails = # get stuff from database
return render(request, 'list.html', context = {'grade_list' : grade_list, 'parent_list' : parent_list})
Please note that I remove grade_level = request.POST['grade_picked'] because as Nagkumar Arkalgud correctly said it is excessively.
Also instead of combination of HttpResponseRedirect and reverse you can use shortcut redirect which probably little easy to code:
from django.shortcuts redirect
...
return redirect('foo', grade_level=grade_level)

I would suggest you to use kwargs instead of args.
The right way to use the view is:
your_url = reverse("<view_name>", kwargs={"<key>": "<value>"})
Ex:
return HttpResponseRedirect(reverse('foo', kwargs={"grade_level": grade_level}))
Also, you are sending "grade_level" to your view foo using the URL and not a POST value. I would remove the line:
grade_level = request.POST['grade_picked']
as you will override the grade_level sent to the method from the url.

Related

How to modify url in a django get request

I have a web page which displays some data (with get parameters to filter in the view) and I use a pagination.
To go through the data i use a link to change the page in the paginator :
<li class="paginator-data">Next</li>
Here is the view :
#login_required
def search_and_export(request):
# initialisation
page_obj = Tree.objects.all()
# FORM
if request.method == 'GET':
## Here I get GET parameters or a default value like
data = str(request.GET.get('data', 1))
page = str(request.GET.get('page', 1))
## creating a queryDict
querydict = QueryDict('data=' data + '&page=' + page)
big_request_form = BigRequestForm(querydict)
if big_request_form.is_valid():
# Getting data from front endda
data = big_request_form.cleaned_data['data']
# Extracting QuerySet
page_obj = filtering_function(data)
paginator = Paginator(page_obj,25)
page_obj = paginator.get_page(page)
page_obj.adjusted_elided_pages = paginator.get_elided_page_range(number = page, on_each_side=1, on_ends=1)
else:
big_request_form = BigRequestForm()
# return values
context = {
'big_request_form' : big_request_form,
'page_obj' : page_obj,
}
return render(request, 'filtered_stats.html', context)
Here is the problem : url looks like that after browsing a few pages :
http://127.0.0.1:8000/?data=x&page=2&page=3&page=4&page=5&page=6
The view is working well (It displays me the last page) but I got this ugly link.
I tried something like that :
request.GET.get = querydict
(with querydict a QueryDict object with only one page data)
But It didn't work. I also thought of parsing data in the template but maybe there is a better way.
What you want to do in HTML template is to get current URL. But actually you don't have to do it - relative path can be used instead. So just remove it:
<li class="paginator-data">Next</li>
The browser knows current URL and will use href attribute relative to it.
UPD
alternative - to keep other parameters in the URL is to construct "next URL" inside the view and pass it in context as next_url.
I.e. something like so:
In views:
next_url = request.get_full_path()
next_url.replace(f'page={current}', f'page={page_obj.next_page_number}')
context['next_url'] = next_url
In template:
<li class="paginator-data">Next</li>

How can I called a view within another view django

Currently, I have a view that essentially closes a lead, meaning that it simply copies the information from one table (leads) to another (deals), now what I really would like to do is that after clicking close, the user is redirected to another page where the user can update some entries (sales forecast), I have a view that updates the lead, so I thought that I can do something like below:
#login_required
def close_lead(request):
id = request.GET.get('project_id', '')
keys = Leads.objects.select_related().get(project_id=id)
form_dict = {'project_id': keys.project_id,
'agent': keys.agent,
'client': keys.point_of_contact,
'company': keys.company,
'service': keys.services,
'licenses': keys.expected_licenses,
'country_d': keys.country
}
deal_form = NewDealForm(request.POST or None,initial=form_dict)
if request.method == 'POST':
if deal_form.is_valid():
deal_form.save()
obj = Leads.objects.get(project_id=id)
obj.status = "Closed"
obj.save(update_fields=['status'])
## Changing the Forecast Table Entry
forecast = LeadEntry.objects.filter(lead_id=id)
for i in forecast:
m = i
m.stage = "Deal"
m.save(update_fields=['stage'])
messages.success(request, 'You have successfully updated the status from open to Close')
update_forecast(request,id)
else:
messages.error(request, 'Error updating your Form')
return render(request,
"account/close_lead.html",
{'form': deal_form})
This view provides the formset that I want to update after closing the lead
#login_required
def update_forecast(request,lead_id):
# Gets the lead queryset
lead = get_object_or_404(Leads,pk=lead_id)
#Create an inline formset using Leads the parent model and LeadEntry the child model
FormSet = inlineformset_factory(Leads,LeadEntry,form=LeadUpdateForm,extra=0)
if request.method == "POST":
formset = FormSet(request.POST,instance=lead)
if formset.is_valid():
formset.save()
return redirect('forecast_lead_update',lead_id=lead.project_id)
else:
formset = FormSet(instance=lead)
context = {
'formset':formset
}
return render(request,"account/leadentry_update.html",context)
As you can see I’m calling this function update_forecast(request,id) after validating the data in the form, and I would have expected to be somehow redirected to the HTML page specified on that function, however, after clicking submit, the form from the first view is validated but then nothing happens, so I'm the function doesn't render the HTML page
My question how can I leverage existing functions in my views?, obviously, I will imagine that following the DRY principles you can do that in Django, so what am I doing wrong ?, how can I call an existing function within another function in views?
A view returns a response object. In your current code, you're calling a second view but not doing anything with its response. If you just wanted to display static content (not a form that might lead to an action that cares about the current URL) you could return the response object from the second view - return update_forecast(request, id).
But since your second view is displaying a form, you care about what the action for the view from the second form is. The typical Django idiom is to have forms submit to the current page's URL - that wouldn't work if you just call it and return its response object. You could customize the action in the second view, say adding an optional parameter to the view, but the usual idiom for form processing is to redirect to the view you want to show on success. Just as you do in the update_forecast view. Something like this:
messages.success(request, 'You have successfully updated the status from open to Close')
return redirect('update_forecast', lead_id=id)

Django redirect by calling function view?

I need to redirect while keeping the original request. Is this approach inline with Django 3 logic or am I creating some furute catastrofe?
def final_view(request):
# use some_new_variable to do stuff and then display it in the template
return render(request, another_template_name, {})
def original_view(request):
# do stuff
request.some_new_variable = "x"
return final_view(request)
Not sure exactly what you're trying to accomplish, but you could always pass "some_new_variable" via the URL
For instance:
views.py
def original_view(request):
x = # do your stuff here
return redirect('final_view_url', var=x)
def final_view(request, var)
final_var = # do some additional stuff with var here
return render(request, another_template_name)
urls.py
path('/final_view/<var>', views.final_view, name="final_view_url")
So you send your original request to "original_view", do some stuff, then pass that variable into the URL to "final_view"

Django: restrict access to a view, dependent upon referring url

I'm making a school records webapp. I want staff users to be able to view the user data pages for any pupil by going to the correct url, but without allowing pupils access to each others' pages. However I'm using the same view function for both urls.
I have a working #user_is_staff decorator based on the existence of a user.staff object. Pupil users have a user.pupil object instead. These are discrete, naturally, as no user can have both a .staff and a .pupil entry.
urls.py
(r'^home/(?P<subject>[^/]+)/$', 'myproject.myapp.views.display_pupil')
(r'^admin/user/(?P<user>\d+)/(+P<subject>[^/]+)/$', 'myproject.myapp.views.display_pupil')
views.py
#login_required
def display_pupil(request, subject, pupil=None):
if pupil:
try:
thepupil = get_object_or_404(Pupil, id = pupil, cohort__school = request.user.staff.school)
except Staff.DoesNotExist:
return HttpResponseForbidden()
else:
thepupil = request.user.pupil
thesubject = get_object_or_404(Subject, shortname = subject)
# do lots more stuff here
return render_to_response('pupilpage.html', locals(), context_instance=RequestContext(request))
Doing it this way works, but feels very hacky, particularly as my '#user_is_staff' decorator has a more elegant redirect to a login page than the 403 error here.
What I don't know is how to apply the #user_is_staff decorator to the function only when it has been accessed with the pupil kwarg. There's a lot more code in the real view function, so I don't want to write a second one as that would be severely non-DRY.
Sounds like you want two separate views - one for a specific pupil and one for the current user - and a utility function containing the shared logic.
#login_required:
def display_current_pupil(request, subject):
thepupil = request.user.pupil
return display_pupil_info(request, subject, thepupil)
#user_is_staff
def display_pupil(request, subject, pupil):
thepupil = get_object_or_404(Pupil, id=pupil, cohort__school=request.user.staff.school)
return display_pupil_info(request, subject, thepupil)
def display_pupil_info(request, subject, thepupil):
thesubject = get_object_or_404(Subject, shortname=subject)
# do lots more stuff here
return render_to_response('pupilpage.html', locals(), context_instance=RequestContext(request))

Want to print out a list of items from another view django

I have a view which displays a list items.
def edit_order(request, order_no):
try:
status_list = models.Status.objects.all()
order = models.Order.objects.get(pk = order_no)
if order.is_storage:
items = models.StorageItem.objects.filter(orderstoragelist__order__pk = order.pk)
else:
items = models.StorageItem.objects.filter(orderservicelist__order__pk = order.pk)
except:
return HttpResponseNotFound()
I want to put these list of item in another view. Unfortunately this is proving to be trickier then I thought.
#login_required
def client_items(request, client_id = 0):
client = None
items = None
try:
client = models.Client.objects.get(pk = client_id)
items = client.storageitem_set.all()
item_list = models.StorageItem.objects.filter(orderstoragelist__order__pk = order.pk)
except:
return HttpResponse(reverse(return_clients))
return render_to_response('items.html', {'items':items, 'client':client, 'item_list':item_list}, context_instance = RequestContext(request))
I thought maybe I can just paste the definition of items and just call that item_list but that does not work. Any ideas
items.html
{% for item in item_list %}
{{item.tiptop_id}
{% endfor %}
From your comment:
I get a white screen with the url printed on the screen. /tiptop/client in this case.
Because that's what you've asked for:
except:
return HttpResponse(reverse(return_clients))
This means that if there are any bugs or problems in the above, your view will simply output a response containing just that URL. Maybe you meant to use HttpResponseRedirect, so the browser actually redirects to the URL - but still you should not use a blank except, as it prevents you from seeing what is actually going wrong.
To answer the main question, think about what your edit_order view returns: it gives you a complete HTML response with a rendered template. How could you use that as an element in a query in another view? You need to think logically about this.
One possible solution would be to define a separate function which just returns the data you want - as a plain queryset - and both views can call it. Does that do what you want?