Cookies in Django

HTTP is a stateless protocol. In other words when a request is sent to the server, it has has no idea whether you are requesting the page for the first time or you are same person who has visited this page thousand times before. This lack of statelessness was a big problem among developers of e-commerce website, because persistenace amoung request could be used to recommend products or display products in a shopping cart. To mitigate this problem Cookie was invented.

Cookie was first implmented by a programmer named Louis Montulli in 1994 at Netscape Communications in their Netscape Browser.

What is Cookie ?

Cookie is a small piece of data stored in the user's browser which is sent by the server. They are commonly used to store user preferences.

This is how cookies works in general:

1) Browser sends the request to the server.

2) The server sends the response along with one or more cookie to the browser.

3) The browser saves the cookie it received from the server. From now on, browser will send this cookie to the server, every time a request is made to the server. The browser will keep sending the cookie to the server along with each request until the cookie expires.

4) When cookie expires, it is removed from the browser.

Creating cookies

We can create cookies using set_cookie() method of the request object. The syntax of the method is:

Syntax: set_cookie(name, value, max_age=None)

  • name: name of the cookie
  • value: value you want to store in the cookie
  • max_age: age of the cookie in seconds. After that it will expire. It is optional argument, if not given then the cookie will exists until the browser is closed.

Reading cookies

Every request object in Django has a COOKIE attribute which acts like a dictionary. We can use COOKIE to access a cookie value like this:

request.COOKIE['cookie_name']

When you read cookie data, it will be returned as strings. That means if you store a integer 100 in a cookie, it will be returned as '100'.

To make things more concrete lets take an example:

Open blog app's views.py and append the test_cookies() view to the end of the file:

def test_cookie(request):   

    if not request.COOKIES.get('color'):
        response = HttpResponse("Cookie Set")
        response.set_cookie('color', 'blue')
        return response
    else:
        return HttpResponse("Your favorite color is {0}".format(request.COOKIES['color']))

Open blog's urls.py and add the following url pattern at the beginning of urlpatterns list.

urlpatterns = [    
    url(r'^cookie/$', views.test_cookie, name='cookie'),
    ...
]

Here is how the above code works:

The first time you send a request to http://127.0.0.1:8000/cookie/, the code inside the if block is executed and the server sends the a response along with a cookie named "color" to the browser. We are not setting max_age here, so cookie will last until the browser session. To make it last longer, we can specify the time in seconds like this:

response.set_cookie('color', 'blue', 3600 * 24 * 365 * 2)

Here the cookie expiry time is 3600 * 24 * 365 * 2 seconds or 2 years.

With the cookie set in the browser, each subsequent request to http://127.0.0.1:8000/cookie/(or any other pages of http://127.0.0.1:8000/), the browser will send the cookie to the server.

When browser sends the cookie to server, we can access it using the server side code. This is essentially what are doing in the else block of the test_cookie() view.

Point you browser to http://127.0.0.1:8000/cookie/, you should see a page like this:

[]

To view the cookie sent by the server, follow theses steps in Chome.

1) Hit Ctrl+Shift+J, this will open Developer Console.

2) Click the Application tab, on the left hand pane, Under Storage click Cookies. You should see our color cookie there like this:

[]

Notice that Expires/Max-Age column is set to Session, which means cookie will last until the browser is closed.

Refresh the page, this will send the request along with the cookie(received from the server in the previous request) to the server. Response from the server should look like this:

[]

Although the above example is trivial, nontheless it is perfectly illustrates how cookies work.

With the help of cookie now we can solve the problem of statelessness of HTTP. In the following section we are creating a view which will track the user how many times he/she visits the site.

A view to count the visits

Open views.py file and add the following view to the end of the file.

def track_user(request):
    if not request.COOKIES.get('visits'):
        response = HttpResponse("This is your first visit to the site. "
                                "From now on I will track your vistis to this site.")
        response.set_cookie('visits', '1', 3600 * 24 * 365 * 2)
    else:
        visits = int(request.COOKIES.get('visits')) + 1
        response = HttpResponse("This is your {0} visit".format(visits))
        response.set_cookie('visits', str(visits),  3600 * 24 * 365 * 2)

    return response

After that, add the following url pattern to the beginning urlpatterns list.

urlpatterns = [    
    url(r'^track_user/$', views.track_user, name='track_user'),
    ...
]

Here are few important points about this view:

1) unlike previous example we are updating value of the cookie and sending it to the browser every time the user visits the site, which is neccessay otherwise we wouldn't be able to count visits made by the user.

2) Every time the user visits http://127.0.0.1:8000/track_user/, we are updating cookie expiry date as well, which starts from that request.

In the above example we are creating response directly using HttpResponse() class. The following is the re-write of the track_user() view using a template named track_user.html.

def track_user(request):

    response = render(request, 'blog/track_user.html') # store the response in response variable

    if not request.COOKIES.get('visits'):        
        response.set_cookie('visits', '1', 3600 * 24 * 365 * 2)
    else:
        visits = int(request.COOKIES.get('visits', '1')) + 1
        response.set_cookie('visits', str(visits),  3600 * 24 * 365 * 2)

    return response

track_user.html

{% extends "base.html"  %}

{% block title %}
    Tracking User - {{ block.super }}
{% endblock %}

{% block content %}

        <div class="content">
            <div class="section-inner clearfix">

                {% if not request.COOKIES.visits %}
                    <p>This is your first visit to the site. From now on I will track your visits to this site.</p>
                {% else %}
                    <p>
                        This is your {{ request.COOKIES.visits|add:'1' }} visit
                    </p>
                {% endif %}

            </div>
        </div>

{% endblock %}

Only new thing here is the add filter in the template. The add filter increments the value of the variable by the specified amount. Its syntax is:

Syntax: variable|add:"value"

But why we are incrementing the value of request.COOKIES.visits by 1 ?

Because the new updated value of visits cookie will not be available to us until the next request.

Unlike the previous definition here we are not creating HttpResponse response after testing value of request.COOKIES.get('visits').

Conside the following example:

When use visits the page http://127.0.0.1:8000/track_user/ for the first time, the if statement inside the track_user() is executed and it creates a cookie named visits with value the of 1.

When the user visits http://127.0.0.1:8000/track_user/ for second time, the browser sends the cookie (with value of 1) to the server. Then the render() method is called to create response. Inside the template request.COOKIES.visits still contains 1, although this the user's second visit. To circumvent this issue we have added add filter.

Deleting a cookie

To delete a cookie simply call delete_cookie() method of response object with name of the cookie to delete.

Syntax: response.delete_cookie(cookie_name)

Lets give our user's an option to disable tracking.

Open views.py and add the stop_tracking() view at the end of the file.

def stop_tracking(request):

    if request.COOKIES.get('visits'):
       response = HttpResponse("Cookies Cleared")
       response.delete_cookie("visits")
    else:
        response = HttpResponse("We are not tracking you.")
    return response

Next, add the following url pattern just above url named track_user.

urlpatterns = [    
    url(r'^stop-tracking/$', views.stop_tracking, name='stop_tracking'),
    ...
]

In the track_user.html template add a link to stop tracking just below the if tag.

<div class="content">
    <div class="section-inner clearfix">

        {% if not request.COOKIES.visits %}
            <p>This is your first visit to the site. From now on I will track your visits to this site.</p>
        {% else %}
            <p>
                This is your {{ request.COOKIES.visits|add:'1' }} visit
            </p>
        {% endif %}

        <a href="{% url 'stop_tracking' %}">Enough! Stop Tracking Me</a>

    </div>
</div>

Visit http://127.0.0.1:8000/track_user/ and you should see a link Enough! Stop Tracking Me like this:

[]

Click the link to delete visits cookie. To verify the cookie has been deleted, in Chrome open Javascript Console by hitting Ctrl+Shift+J and click on Application tab.

[]

As you can see, indeed our cookie has been deleted.

Now you should have a better idea of how to use cookie and situations in which they can be useful. However there are few things to watch out for before you start using cookies in your projects:

1) Never ever use cookies to store sensitive data like passwords. Cookies store data in plain text as a result anybody can read/modify them.

2) Most browsers don't allow cookies to store more than 4KB of data (i.e 4KB for each cookie). Further most browsers accept no more than 30 cookies per website. Actually the exact number of cookies per website vary from browser to browser click here for more details.

3) Recall that once cookie is set in the browser, it will be sent along with each request to the server. Lets say we have added 20 cookies each of size 4KB, that comes out to be 80KB. That means with every request to the server the browser would need to send 80KB of additional data with every request!

4) Users can delete the cookies at their will.

5) User can even configure their browsers to do not accepts cookies at all.

Some of the above mentioned problems can be solved by using sessions which is discussed in the next chapter.