justin
justin

Reputation: 563

How to pass data from one view to the next

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.

Upvotes: 4

Views: 4559

Answers (1)

willeM_ Van Onsem
willeM_ Van Onsem

Reputation: 476594

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.

Upvotes: 11

Related Questions