How to pass data from one view to the next - django

Summary: I am trying to build a job site. On index.html the user enters a zip code into a form to see jobs in that zip code, this form is handled with the job_query view. This brings them to another page(search.html) where at first you only see jobs in that specific zip code but I am trying to add a filter that lets the user see jobs within X miles. How can I pass the zip code value entered in the from on index.html to the next page?
index.html:
<h2>Find a Job</h2>
<!--Search Bar-->
<form method = "GET" action = "{% url 'search' %}" >
<div id = "form_grid">
<input name="query" type="text" placeholder="Zip Code">
<button type="submit">Search</button>
</div>
</form>
search.html:
<form method = "GET" action = "{% url 'search' %}" >
<input class="search_bar" name="query" type="text" placeholder="Zip Code">
<button class="search_btn btn btn-outline-success " type="submit">Find Jobs</button>
</form>
<form id="within_miles_form" method = "GET" action = "{% url 'within_miles' %}" >
<input class="search_bar" name="miles" type="text" placeholder="Within X miles of Zip Code">
<button type="submit">Filter</button>
</form>
<!--code to display jobs-->
views.py:
def job_query(request):
if request.method == "GET":
query = request.GET.get('query')
jobs_matching_query = Job.objects.filter(zip_code__iexact = query) | Job.objects.filter(city__iexact=query) | Job.objects.filter(state__iexact=query)
number_of_results = 0
for job in jobs_matching_query:
number_of_results = number_of_results + 1
return render(request, 'core/search.html', {'query': query ,'jobs_matching_query': jobs_matching_query, 'number_of_results': number_of_results})
def within_miles(request):
miles = request.GET['miles']
#how can i use value of the zip code entered?
urls.py:
url(r'^search$', views.job_query, name="search"),
url(r'within_miles', views.within_miles, name="within_miles"),
I think I included all the relevant info but if I am missing something please let me know, thanks in advance for any help.

You can encode the entered ZIP in a URL, pass it through cookies, store it in the session variables, or use a (hidden) input element that forces the browser to pass it through a GET and POST request.
Encode it in the URL
In that case we can rewrite the URL to:
url(r'^within_miles/(?P<zip>[0-9]{5})/$', views.within_miles, name="within_miles"),
So now one can no longer fetch your.domain.com/within_miles, but your.domain.com/within_miles/12345. It makes it easy for a user to "manipulate" the URL, but since the user can probably provide any ZIP, there is probably not much gain to protect that.
In the form, the URL that is generated is thus:
{% url 'within_miles' zip=query %}
(you can use another variable that is more strictly a ZIP code)
You should thus ensure that query is here a five digit string (or otherwise change the expression in the url(..) part such that it allows all possible queries).
Using hidden form elements
We can also encode content in hidden form elements, for example here we can create an element in the form:
<form id="within_miles_form" method = "GET" action = "{% url 'within_miles' %}" >
<input class="search_bar" name="miles" type="text" placeholder="Within X miles of Zip Code">
<input type="hidden" name="zip_code" value="{{ query }}">
<button type="submit">Filter</button>
</form>
We thus add a form element, fill it with some data, and let the browser submit the value again to the next view. Note that again it is the browser that does this, so a user can inspect the DOM (most browsers allow that, and subsequently edit it).
Using session variables and/or cookies
You can also decide to use session variables (stored at server side, so "secure") or cookies (stored at client side, can be tampered with). A potential problem however is that these are stored in the browser, and changes to the cookies in one tab page, thus can have effect in the other tab page. Furthermore cookies and sessions will "die" after the request, and thus can create a lot of trouble in future views.
You can set a session variable in the view with:
request.session['zip_code'] = query
This will thus store an entry at the server side such that another call can retrieve that value again. The request.session acts like a dictionary that keeps some sort of state per session.
setting and obtaining session variables
In another view, you can thus query the request.session, like:
zip_code = request.session.get('zip_code')
setting and obtaining cookies
We can use a similar approach with cookies. A browser however might reject cookies, or manipulate them, so there are not that much guarantees that there is no tampering with the data (in fact there are none). You can set a cookie with:
response = render(request, 'core/search.html', {'query': query ,'jobs_matching_query': jobs_matching_query, 'number_of_results': number_of_results})
response.set_cookie('zip_code', query)
return response
Before we thus return the result of render(..), we call .set_cookie(..) on the result.
We can - for example in a later view - retrieve the content with:
zip_code = request.COOKIES.get('zip_code')
Improving the job_query view
The job_query view however looks a bit strange: it uses all kinds of "uncommon" code practices. For example the number of elements is calculated by iterating over it, instead of taking the len(..). This also looks basically like a ListView [Django-doc] and we can make the query more elengant by using Q-objects [Django-doc]. The listview then looks like:
def JobListView(ListView):
model = Job
context_object_name = 'jobs_matching_query'
template_name = 'core/search.html'
def get_context_data(self, **kwargs):
kwargs = super(JobListView, self).get_context_data(**kwargs)
kwargs.update(
number_of_results=len(kwargs['object_list'],
query = self.request.GET.get('query')
)
return kwargs
In the view, you then not pass the JobListView, but JobListView.as_view() result as a reference.

Related

Django - how to go back to previous view with parameters

I am relatively new with Django, this must be a common problem.
I have created a view to show a form to input date (using widget that returns separate fields):
when date is inserted, I call a function userPage(request, my_date)
that filters, processes and renders a page (user.html) showing a list of items.
def datePage(request):
user=request.user
context = {}
context['form'] = UserDateForm()
if request.GET:
date_yr = request.GET['food_date_year']
date_mo = request.GET['food_date_month']
date_day = request.GET['food_date_day']
my_date_string = date_yr+'-'+date_mo+'-'+date_day
my_date = datetime.strptime(my_date_string, "%Y-%m-%d").date()
return userPage(request,my_date)
return render(request, "date.html", context)
def userPage(request, my_date):
user=request.user
# process context using user, my_date
context={...:..., 'my_date': my_date}
return render(request,'user.html',context)
In user.html I include a URL to add an item:
</div>
<form action="{% url 'My_ItemCreate' %}" method="POST">
{%csrf_token%}
<button type="submit" class="btn btn-success">
<span class="glyphicon glyphicon-plus"></span>
</button>
</form>
</div>
'My_ItemCreate' points to a django.views.generic CreateView that creates an item.:
path('MyItemCreate/',views.My_ItemCreate.as_view(),name='My_ItemCreate'),
class My_ItemCreate(CreateView):
model = MyItem
fields = ...
After creating the item in the CreateView, how do I go back to the user page
after I inserted the date? I have lost the date in the new URL.
If I use URL resolver to go to userPage, how do I pass a date in the format?
It would be nice that I am able to pass initial values in the CreateView, and
make some fields read-only, how do I modify/override CreateView ?
Many Thanks for your help!
I have found an answer to my problem: using request.session
to store a value and retrieving in other views, it works fine.
I am still curious to know if there are experts who
would provide a more elegant solution, and if someone
could be so kind to answer point 2) regarding CreateView read_only fields
Thanks
D

Django update boolean field with a form

My simple web-application has two models that are linked (one to many).
The first model (Newplate) has a boolean field called plate_complete. This is set to False (0) at the start.
questions:
In a html page, I am trying to build a form and button that when pressed sets the above field to True. At the moment when I click the button the page refreshes but there is no change to the database (plate_complete is still False). How do I do this?
Ideally, once the button is pressed I would also like to re-direct the user to another webpage (readplates.html). This webpage does not require the pk field (but the form does to change the specific record) Hence for now I am just refreshing the extendingplates.html file. How do I do this too ?
My code:
"""Model"""
class NewPlate(models.Model):
plate_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
title = models.CharField(max_length=200)
created_date = models.DateTimeField(default=timezone.now)
plate_complete = models.BooleanField()
"""view"""
def publish_plates(request,plate_id):
newplate = get_object_or_404(NewPlate, pk=plate_id)
newplate.plate_complete = True
newplate.save()
#2nd method
NewPlate.objects.filter(pk=plate_id).update(plate_complete = True)
return HttpResponseRedirect(reverse('tablet:extendplates', args=[plate_id]))
"""URLS"""
path('readplates', views.read_plates, name='readplates'),
path('extendplates/<pk>/', views.show_plates, name='showplates'),
path('extendplates/<pk>/', views.publish_plates, name='publishplates'),
"""HTML"""
<form method="POST" action="{% url 'tablet:publishplates' newplate.plate_id %}">
{% csrf_token %}
<button type="submit" class="button" value='True'>Publish</button></form>
-------Added show plates view:---------
def show_plates(request,pk):
mod = NewPlate.objects.all()
newplate= get_object_or_404(mod, pk=pk)
add2plate= Add2Plate.objects.filter(Add2Plateid=pk)
return render(request, 'tablet/show_plates.html', {'newplate': newplate,'add2plate': add2plate})
Thank you
The problem is two of your urls have the same pattern 'extendplates/<pk>/'. Django uses the first pattern that matches a url. I suppose that one of these view views.show_plates is meant to display the form and the other views.publish_plates is meant to accept the posted form data.
This means that simply both of these views should simply be a single view (to differentiate if the form is submitted we will simply check the requests method):
from django.shortcuts import redirect, render
def show_plates(request, plate_id):
newplate = get_object_or_404(NewPlate, pk=plate_id)
if request.method == "POST":
newplate.plate_complete = True
newplate.save()
return redirect('tablet:extendplates', plate_id)
context = {'newplate': newplate}
return render(request, 'your_template_name.html', context)
Now your url patterns can simply be (Note: Also captured arguments are passed as keyword arguments to the view so they should be consistent for your view and pattern):
urlpatterns = [
...
path('readplates', views.read_plates, name='readplates'),
path('extendplates/<uuid:plate_id>/', views.show_plates, name='showplates'),
...
]
In your form simply forego the action attribute as it is on the same page:
<form method="POST">
{% csrf_token %}
<button type="submit" class="button" value='True'>Publish</button>
</form>
You should avoid changing state on a get request like your view does currently.
Handle the POST request and change the data if the request is valid (ensuring CSRF protection).
def publish_plates(request,plate_id):
newplate = get_object_or_404(NewPlate, pk=plate_id)
if request.method == "POST":
newplate.plate_complete = True
newplate.save(update_fields=['plate_complete']) # a more efficient save
#2nd method
NewPlate.objects.filter(pk=plate_id).update(plate_complete=True)
return HttpResponseRedirect(reverse('tablet:extendplates', args=[plate_id]))
You could also put a hidden input in the form, or make a form in Django to hold the hidden input, which stores the plate_id value and that way you can have a generic URL which will fetch that ID from the POST data.
Now the real problem you've got here, is that you've got 2 URLs which are the same, but with 2 different views.
I'd suggest you change that so that URLs are unique;
path('extendplates/<pk>/', views.show_plates, name='showplates'),
path('publish-plates/<pk>/', views.publish_plates, name='publishplates'),

Reverse for *view* with arguments '('',)' not found

I am trying to create a simple search form(search by zip code), but am struggling to pass the user's input to a view:
<form action="{% url 'search_results' query %}" method="post">
<div>
{% csrf_token %}
<input type = 'text' name = 'query' placeholder="Zip Code" />
<button type="submit" name="button">Find Jobs</button>
</div>
</form>
urls.py:
path('search_results/<str:query>', job_views.search_results, name = 'search_results'),
views.py:
def search_results(request, query):
query = request.GET['query']
return HttpResponse(query) # just trying to see if this view has access to 'query'
I'm not sure what is going on here. This returns
raise NoReverseMatch(msg)
django.urls.exceptions.NoReverseMatch: Reverse for 'search_results' with a
rguments '('',)' not found. 1 pattern(s) tried: ['search_results\\/(?P<que
ry>[^/]+)$']
Thanks for any help!
In your form, at the line: action="{% url 'search_results' query %}", you are trying to pass query to the url tag, which would be fine if query had a value, but in your case query isn't defined.
Instead, you're form should look like this:
<form action="{% url 'search_results' %}" method="GET">
<div>
<input type = "text" name = "zip_code" placeholder="Zip Code" />
<button type="submit" name="button">Find Jobs</button>
</div>
</form>
Notice that I removed query from your url tag, and changed the method to GET, see this question for details.
Now change your path:
path('search_results', job_views.search_results, name = 'search_results')
You don't need to add query to the path, because Django will do it for you in your view:
def search_results(request):
zip_code = request.GET.get('zip_code')
print(zip_code)
...
That's not how templates work.
Templates are used to produce a response, which gets returned to the requester. In this case, your main view produces an HTML document which will be passed to a browser. That document contains results of the url template tag, which is determined before the user even sees it. Template tags are not a way to refer to the state of DOM objects!
Instead, you should usually have a view with a stable URL, not varying with the query. Typically you'll then extract the query text from the form data representation - here, it'd be the request.POST dictionary-like object or (usually better) a Django form that you bind to the POST data, because your form uses method="post".
It would be possible to use Javascript to edit the DOM and change your form action prior to submitting it to include the query text, but that's not the usual pattern for working with forms. And template tags will never do that - they're only used to generate the response.

Django URL/Views extra parameters

In Django1.6, is there a way to pass a dynamic parameter to my view or URL without parsing the URL?
Ideally I would want a urls.py that looks like:
url(r'^dash/$',
dash_view.account_modify,
{'account': **dynamic_account_identifier_here**}
name='dash_account_modiy')
And in views.py:
def account_modify(request, account,
template_name='profile.html,
change_form=AccountModifyForm):
...
:param account:
comes from model:
class Dash(models.Model):
name = models.Charfield()
account = models.IntegerField()
....
Basically, I would really like to avoid a urls.py with the account identifier as part of the string, like:
url(r'^dash/(?P<account>\w+)/$',
dash_view.account_modify,
name='dash_account_modiy')
Any suggestions on how I can pass these values from the a template to the processing view for use in the AccountModifyForm (which expects that 'account' parameter)?
url(r'^dash/$',
dash_view.account_modify,
{'account': **dynamic_account_identifier_here**}
name='dash_account_modify')
You can't dynamically evaluate anything there, because the dictionary is evaluated only once, when the URL conf is loaded.
If you want to pass information from one view to another your three options are:
in the URL, which you don't seem to want to do
as GET or POST data
store it in the session in one view and retrieve it from the session in the next
If anyone cares...figured it out...
In the template:
{% for dash in dashes %}
blah blah blah
<form action="..." method="POST">
<input type="hidden" name="id" value="{{ dash.account }}">
{{ form.as_ul }}
<input type="submit" value="Do stuff">
</form>
{% endfor %}
In the views:
if request.method == 'POST'
account = request.POST['id']
# be sure to include checks for the validity of the POST information
# e.g. confirm that the account does indeed belong to whats-his-face
form = AccountModifyForm(request.POST, account,
user=request.user)
....

How do I get variables not submitted by the user back from a template

I'm new to django and for practice I'm trying to program my own version of the all familiar 'todo list' app.
I have some page that displays all todo list items the user has entered, along with a button to edit each one. The edit button sends the user to another page with a form to enter in the changes to the item.
It's possible for the user to change everything about the item. Obviously request.POST gives me all the information the user just put into the form, but I want this information to rewrite the info of the item they originally clicked on. So how do I write the view code to find out what that original item was?
I guess I could format my form submit button to:
<button type="submit" name="save" value={{ item.pk }}>Save</button>
and get the primary key that way
but lets say I had passed two items to the edit page and I wanted to give the user the ability to combined them. Again, I could figure out what those items were by doing:
<button type="submit" name="save" value='{{ item1.pk }} {{ item2.pk }}'>Save</button>
then
request['save'].split(' ')
this seems kinda stupid though. Is there some other, less brute force, way?
like a:
request.tell_me_all_items_passed_to_the_template
kind of thing?
In case someone else has the same question
So instead of doing this:
template
<form method="post" action="/list/saving/">
<!--- form fields --->
<button type="submit" name="save" value='{{ item.pk }}'>Save</button>
</form>
url.py
(r'^list/saving/$', save)
views.py
def save(request):
....
item = Item.objects.get(pk=request.POST['save'])
....
do this:
template
<form method="post" action="{% url todolist.views.save item.pk %}">
<!--- form fields --->
<button type="submit" name="save">Save</button>
</form>
url.py
(r'^list/saving/(\d+)/$', save)
views.py
def save(request, pk):
....
item = Item.objects.get(pk=pk)
....
More info on url reversing
If you really need to, just use hidden inputs. But, this is unnecessary anyways. The user may be able to change "everything" about the item, but they can't change the pk (or at least they shouldn't be able to... don't give them the option to). The pk is what Django uses to identify an object, so this is already handled for you. Not sure what the issue here is.