Implementing the Django interface

I hope you're comfortable with the concepts presented in Chapter 10, Web Development Done Right which was mostly about Django. If you haven't read it, this is probably a good time, before reading on here.

The setup

In your root folder (ch12, for me), which will contain the root for the interface and the root for the API, start by running this command:

$ django-admin startproject pwdweb

This will create the structure for a Django project, which we know well by now. I'll show you the final structure of the interface project here:

$ tree -A pwdweb
pwdweb
├── db.sqlite3
├── manage.py
├── pwdweb
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
└── records
    ├── admin.py
    ├── forms.py
    ├── __init__.py
    ├── migrations
    │   ├── 0001_initial.py
    │   └── __init__.py
    ├── models.py
    ├── static
    │   └── records
    │       ├── css
    │       │   └── main.css
    │       └── js
    │           ├── api.js
    │           └── jquery-2.1.4.min.js
    ├── templates
    │   └── records
    │       ├── base.html
    │       ├── footer.html
    │       ├── home.html
    │       ├── list.html
    │       ├── messages.html
    │       ├── record_add_edit.html
    │       └── record_confirm_delete.html
    ├── templatetags
    │   └── record_extras.py
    ├── urls.py
    └── views.py

As usual, don't worry if you don't have all the files, we'll add them gradually. Change to the pwdweb folder, and make sure Django is correctly set up: $ python manage.py runserver (ignore the warning about unapplied migrations).

Shut down the server and create an app: $ python manage.py startapp records. That is excellent, now we can start coding. First things first, let's open pwdweb/settings.py and start by adding 'records', at the end of the INSTALLED_APP tuple (note that the comma is included in the code). Then, go ahead and fix the LANGUAGE_CODE and TIME_ZONE settings according to your preference and finally, add the following line at the bottom:

ENCRYPTION_KEY = b'qMhPGx-ROWUDr4veh0ybPRL6viIUNe0vcPDmy67x6CQ='

This is a custom encryption key that has nothing to do with Django settings, but we will need it later on, and this is the best place for it to be. Don't worry for now, we'll get back to it.

The model layer

We need to add just one model for the records application: Record. This model will represent each record we want to store in the database:

records/models.py

from cryptography.fernet import Fernet
from django.conf import settings
from django.db import models

class Record(models.Model):
    DEFAULT_ENCODING = 'utf-8'

    title = models.CharField(max_length=64, unique=True)
    username = models.CharField(max_length=64)
    email = models.EmailField(null=True, blank=True)
    url = models.URLField(max_length=255, null=True, blank=True)
    password = models.CharField(max_length=2048)
    notes = models.TextField(null=True, blank=True)
    created = models.DateTimeField(auto_now_add=True)
    last_modified = models.DateTimeField(auto_now=True)

    def encrypt_password(self):
        self.password = self.encrypt(self.password)

    def decrypt_password(self):
        self.password = self.decrypt(self.password)

    def encrypt(self, plaintext):
        return self.cypher('encrypt', plaintext)

    def decrypt(self, cyphertext):
        return self.cypher('decrypt', cyphertext)

    def cypher(self, cypher_func, text):
        fernet = Fernet(settings.ENCRYPTION_KEY)
        result = getattr(fernet, cypher_func)(
            self._to_bytes(text))
        return self._to_str(result)

    def _to_str(self, bytes_str):
        return bytes_str.decode(self.DEFAULT_ENCODING)

    def _to_bytes(self, s):
        return s.encode(self.DEFAULT_ENCODING)

Firstly, we set the DEFAULT_ENCODING class attribute to 'utf-8', which is the most popular type of encoding for the web (and not only the web). We set this attribute on the class to avoid hardcoding a string in more than one place.

Then, we proceed to set up all the model's fields. As you can see, Django allows you to specify very specific fields, such as EmailField and URLField. The reason why it's better to use these specific fields instead of a plain and simple CharField is we'll get e-mail and URL validation for free when we create a form for this model, which is brilliant.

All the options are quite standard, and we saw them in Chapter 10, Web Development Done Right but I want to point out a few things anyway. Firstly, title needs to be unique so that each Record object has a unique title and we don't want to risk having doubles. Each database treats strings a little bit differently, according to how it is set up, which engine it runs, and so on, so I haven't made the title field the primary key for this model, which would have been the natural thing to do. I prefer to avoid the pain of having to deal with weird string errors and I am happy with letting Django add a primary key to the model automatically.

Another option you should understand is the null=True, blank=True couple. The former allows the field to be NULL, which makes it non-mandatory, while the second allows it to be blank (that is to say, an empty string). Their use is quite peculiar in Django, so I suggest you to take a look at the official documentation to understand exactly how to use them.

Finally, the dates: created needs to have auto_add_now=True, which will set the current moment in time on the object when it's created. On the other hand, last_modified needs to be updated every time we save the model, hence we set auto_now=True.

After the field definitions, there are a few methods for encrypting and decrypting the password. It is always a very bad idea to save passwords as they are in a database, therefore you should always encrypt them before saving them.

Normally, when saving a password, you encrypt it using a one way encryption algorithm (also known as a one way hash function). This means that, once you have created the hash, there is no way for you to revert it back to the original password.

This kind of encryption is normally used for authentication: the user puts their username and password in a form and, on submission, the code fetches the hash from the user record in the database and compares it with the hash of the password the user has just put in the form. If the two hashes match, it means that they were produced by the same password, therefore authentication is granted.

In this case though, we need to be able to recover the passwords, otherwise this whole application wouldn't be very useful. Therefore, we will use a so-called symmetric encryption algorithm to encrypt them. The way this works is very simple: the password (called plaintext) is passed to an encrypt function, along with a secret key. The algorithm produces an encrypted string (called cyphertext) out of them, which is what you store in the database. When you want to recover the password, you will need the cyphertext and the secret key. You feed them to a decrypt function, and you get back your original password. This is exactly what we need.

In order to perform symmetric encryption, we need the cryptography package, which is why I instructed you to install it.

All the methods in the Record class are very simple. encrypt_password and decrypt_password are shortcuts to encrypt and decrypt the password field and reassign the result to itself.

The encrypt and decrypt methods are dispatchers for the cypher method, and _to_str and _to_bytes are just a couple of helpers. The cryptography library works with bytes objects, so we need those helpers to go back and forth between bytes and strings, using a common encoding.

The only interesting logic is in the cypher method. I could have coded it directly in the encrypt and decrypt ones, but that would have resulted in a bit of redundancy, and I wouldn't have had the chance to show you a different way of accessing an object's attribute, so let's analyze the body of cypher.

We start by creating an instance of the Fernet class, which provides us with the symmetric encryption functionality we need. We set the instance up by passing the secret key in the settings (ENCRYPTION_KEY). After creating fernet, we need to use it. We can use it to either encrypt or decrypt, according to what value is given to the cypher_func parameter. We use getattr to get an attribute from an object given the object itself and the name of the attribute. This technique allows us to fetch any attribute from an object dynamically.

The result of getattr(fernet, cypher_func), with cyper_func being 'encrypt', for example, is the same as fernet.encrypt. The getattr function returns a method, which we then call with the bytes representation of the text argument. We then return the result, in string format.

Here's what this function is equivalent to when it's called by the encrypt dispatcher:

def cypher_encrypt(self, text):
        fernet = Fernet(settings.ENCRYPTION_KEY)
        result = fernet.encrypt(
            self._to_bytes(text))
        return self._to_str(result)

When you take the time to understand it properly, you'll see it's not as difficult as it sounds.

So, we have our model, hence it's time to migrate (I hope you remember that this will create the tables in the database for your application):

$ python manage.py makemigrations
$ python manage.py migrate

Now you should have a nice database with all the tables you need to run the interface application. Go ahead and create a superuser ($ python manage.py createsuperuser).

By the way, if you want to generate your own encryption key, it is as easy as this:

>>> from cryptography.fernet import Fernet
>>> Fernet.generate_key()

A simple form

We need a form for the Record model, so we'll use the ModelForm technique we saw in Chapter 10, Web Development Done Right.

records/forms.py

from django.forms import ModelForm, Textarea
from .models import Record

class RecordForm(ModelForm):
    class Meta:
        model = Record
        fields = ['title', 'username', 'email', 'url',
                  'password', 'notes']
        widgets = {'notes': Textarea(
            attrs={'cols': 40, 'rows': 4})}

We create a RecordForm class that inherits from ModelForm, so that the form is created automatically thanks to the introspection capabilities of Django. We only specify which model to use, which fields to display (we exclude the dates, which are handled automatically) and we provide minimal styling for the dimensions of the notes field, which will be displayed using a Textarea (which is a multiline text field in HTML).

The view layer

There are a total of five pages in the interface application: home, record list, record creation, record update, and record delete confirmation. Hence, there are five views that we have to write. As you'll see in a moment, Django helps us a lot by giving us views we can reuse with minimum customization. All the code that follows belongs to the records/views.py file.

Imports and home view

Just to break the ice, here are the imports and the view for the home page:

from django.contrib import messages
from django.contrib.messages.views import SuccessMessageMixin
from django.core.urlresolvers import reverse_lazy
from django.views.generic import TemplateView
from django.views.generic.edit import (
    CreateView, UpdateView, DeleteView)
from .forms import RecordForm
from .models import Record

class HomeView(TemplateView):
    template_name = 'records/home.html'

We import a few tools from Django. There are a couple of messaging-related objects, a URL lazy reverser, and four different types of view. We also import our Record model and RecordForm. As you can see, the HomeView class consists of only two lines since we only need to specify which template we want to use, the rest just reuses the code from TemplateView, as it is. It's so easy, it almost feels like cheating.

Listing all records

After the home view, we can write a view to list all the Record instances that we have in the database.

class RecordListView(TemplateView):
    template_name = 'records/list.html'

    def get(self, request, *args, **kwargs):
        context = self.get_context_data(**kwargs)
        records = Record.objects.all().order_by('title')  #1
        for record in records:
            record.plaintext = record.decrypt(record.password) #2
        context['records'] = records
        return self.render_to_response(context)

All we need to do is sub-class TemplateView again, and override the get method. We need to do a couple of things: we fetch all the records from the database and sort them by title (#1) and then parse all the records in order to add the attribute plaintext (#2) onto each of them, to show the original password on the page. Another way of doing this would be to add a read-only property to the Record model, to do the decryption on the fly. I'll leave it to you, as a fun exercise, to amend the code to do it.

After recovering and augmenting the records, we put them in the context dict and finish as usual by invoking render_to_response.

Creating records

Here's the code for the creation view:

class EncryptionMixin:
    def form_valid(self, form):
        self.encrypt_password(form)
        return super(EncryptionMixin, self).form_valid(form)

    def encrypt_password(self, form):
        self.object = form.save(commit=False)
        self.object.encrypt_password()
        self.object.save()

class RecordCreateView(
        EncryptionMixin, SuccessMessageMixin, CreateView):
    template_name = 'records/record_add_edit.html'
    form_class = RecordForm
    success_url = reverse_lazy('records:add')
    success_message = 'Record was created successfully'

A part of its logic has been factored out in order to be reused later on in the update view. Let's start with EncryptionMixin. All it does is override the form_valid method so that, prior to saving a new Record instance to the database, we make sure we call encrypt_password on the object that results from saving the form. In other words, when the user submits the form to create a new Record, if the form validates successfully, then the form_valid method is invoked. Within this method what usually happens is that an object is created out of the ModelForm instance, like this:

self.object = form.save()

We need to interfere with this behavior because running this code as it is would save the record with the original password, which isn't encrypted. So we change this to call save on the form passing commit=False, which creates the Record instance out of the form, but doesn't attempt to save it in the database. Immediately afterwards, we encrypt the password on that instance and then we can finally call save on it, actually committing it to the database.

Since we need this behavior both for creating and updating records, I have factored it out in a mixin.

Note

Perhaps, a better solution for this password encryption logic is to create a custom Field (inheriting from CharField is the easiest way to do it) and add the necessary logic to it, so that when we handle Record instances from and to the database, the encryption and decryption logic is performed automatically for us. Though more elegant, this solution needs me to digress and explain a lot more about Django internals, which is too much for the extent of this example. As usual, you can try to do it yourself, if you feel like a challenge.

After creating the EncryptionMixin class, we can use it in the RecordCreateView class. We also inherit from two other classes: SuccessMessageMixin and CreateView. The message mixin provides us with the logic to quickly set up a message when creation is successful, and the CreateView gives us the necessary logic to create an object from a form.

You can see that all we have to code is some customization: the template name, the form class, and the success message and URL. Everything else is gracefully handled for us by Django.

Updating records

The code to update a Record instance is only a tiny bit more complicated. We just need to add some logic to decrypt the password before we populate the form with the record data.

class RecordUpdateView(
        EncryptionMixin, SuccessMessageMixin, UpdateView):
    template_name = 'records/record_add_edit.html'
    form_class = RecordForm
    model = Record
    success_message = 'Record was updated successfully'

    def get_context_data(self, **kwargs):
        kwargs['update'] = True
        return super(
            RecordUpdateView, self).get_context_data(**kwargs)

    def form_valid(self, form):
        self.success_url = reverse_lazy(
            'records:edit',
            kwargs={'pk': self.object.pk}
        )
        return super(RecordUpdateView, self).form_valid(form)

    def get_form_kwargs(self):
        kwargs = super(RecordUpdateView, self).get_form_kwargs()
        kwargs['instance'].decrypt_password()
        return kwargs

In this view, we still inherit from both EncryptionMixin and SuccessMessageMixin, but the view class we use is UpdateView.

The first four lines are customization as before, we set the template name, the form class, the Record model, and the success message. We cannot set the success_url as a class attribute because we want to redirect a successful edit to the same edit page for that record and, in order to do this, we need the ID of the instance we're editing. No worries, we'll do it another way.

First, we override get_context_data in order to set 'update' to True in the kwargs argument, which means that a key 'update' will end up in the context dict that is passed to the template for rendering the page. We do this because we want to use the same template for creating and updating a record, therefore we will use this variable in the context to be able to understand in which situation we are. There are other ways to do this but this one is quick and easy and I like it because it's explicit.

After overriding get_context_data, we need to take care of the URL redirection. We do this in the form_valid method since we know that, if we get there, it means the Record instance has been successfully updated. We reverse the 'records:edit' view, which is exactly the view we're working on, passing the primary key of the object in question. We take that information from self.object.pk.

One of the reasons it's helpful to have the object saved on the view instance is that we can use it when needed without having to alter the signature of the many methods in the view in order to pass the object around. This design is very helpful and allows us to achieve a lot with very few lines of code.

The last thing we need to do is to decrypt the password on the instance before populating the form for the user. It's simple enough to do it in the get_form_kwargs method, where you can access the Record instance in the kwargs dict, and call decrypt_password on it.

This is all we need to do to update a record. If you think about it, the amount of code we had to write is really very little, thanks to Django class-based views.

Tip

A good way of understanding which is the best method to override, is to take a look at the Django official documentation or, even better in this case, check out the source code and look at the class-based views section. You'll be able to appreciate how much work has been done there by Django developers so that you only have to touch the smallest amounts of code to customize your views.

Deleting records

Of the three actions, deleting a record is definitely the easiest one. All we need is the following code:

class RecordDeleteView(SuccessMessageMixin, DeleteView):
    model = Record
    success_url = reverse_lazy('records:list')

    def delete(self, request, *args, **kwargs):
        messages.success(
            request, 'Record was deleted successfully')
        return super(RecordDeleteView, self).delete(
            request, *args, **kwargs)

We only need to inherit from SuccessMessageMixin and DeleteView, which gives us all we need. We set up the model and the success URL as class attributes, and then we override the delete method only to add a nice message that will be displayed in the list view (which is where we redirect to after deletion).

We don't need to specify the template name, since we'll use a name that Django infers by default: record_confirm_delete.html.

With this final view, we're all set to have a nice interface that we can use to handle Record instances.

Setting up the URLs

Before we move on to the template layer, let's set up the URLs. This time, I want to show you the inclusion technique I talked about in Chapter 10, Web Development Done Right.

pwdweb/urls.py

from django.conf.urls import include, url
from django.contrib import admin
from records import urls as records_url
from records.views import HomeView

urlpatterns = [
    url(r'^admin/', include(admin.site.urls)),
    url(r'^records/', include(records_url, namespace='records')),
    url(r'^$', HomeView.as_view(), name='home'),
]

These are the URLs for the main project. We have the usual admin, a home page, and then for the records section, we include another urls.py file, which we define in the records application. This technique allows for apps to be reusable and self-contained. Note that, when including another urls.py file, you can pass namespace information, which you can then use in functions such as reverse, or the url template tag. For example, we've seen that the path to the RecordUpdateView was 'records:edit'. The first part of that string is the namespace, and the second is the name that we have given to the view, as you can see in the following code:

records/urls.py

from django.conf.urls import include, url
from django.contrib import admin
from .views import (RecordCreateView, RecordUpdateView,
                    RecordDeleteView, RecordListView)

urlpatterns = [
    url(r'^add/$', RecordCreateView.as_view(), name='add'),
    url(r'^edit/(?P<pk>[0-9]+)/$', RecordUpdateView.as_view(),
        name='edit'),
    url(r'^delete/(?P<pk>[0-9]+)/$', RecordDeleteView.as_view(),
        name='delete'),
    url(r'^$', RecordListView.as_view(), name='list'),
]

We define four different url instances. There is one for adding a record, which doesn't need primary key information since the object doesn't exist yet. Then we have two url instances for updating and deleting a record, and for those we need to also specify primary key information to be passed to the view. Since Record instances have integer IDs, we can safely pass them on the URL, following good URL design practice. Finally, we define one url instance for the list of records.

All url instances have name information which is used in views and templates.

The template layer

Let's start with the template we'll use as the basis for the rest:

records/templates/records/base.html

{% load static from staticfiles %}
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport"
          content="width=device-width, initial-scale=1.0">
    <link href="{% static "records/css/main.css" %}"
          rel="stylesheet">
    <title>{% block title %}Title{% endblock title %}</title>
  </head>

  <body>
    <div id="page-content">
      {% block page-content %}{% endblock page-content %}
    </div>
    <div id="footer">{% block footer %}{% endblock footer %}</div>
    {% block scripts %}
      <script
        src="{% static "records/js/jquery-2.1.4.min.js" %}">
      </script>
    {% endblock scripts %}
  </body>
</html>

It's very similar to the one I used in Chapter 10, Web Development Done Right although it is a bit more compressed and with one major difference. We will import jQuery in every page.

Note

jQuery is the most popular JavaScript library out there. It allows you to write code that works on all the main browsers and it gives you many extra tools such as the ability to perform asynchronous calls (AJAX) from the browser itself. We'll use this library to perform the calls to the API, both to generate and validate our passwords. You can download it at https://jquery.com/, and put it in the pwdweb/records/static/records/js/ folder (you may have to amend the import in the template).

I highlighted the only interesting part of this template for you. Note that we load the JavaScript library at the end. This is common practice, as JavaScript is used to manipulate the page, so loading libraries at the end helps in avoiding situations such as JavaScript code failing because the element needed hadn't been rendered on the page yet.

Home and footer templates

The home template is very simple:

records/templates/records/home.html

{% extends "records/base.html" %}
{% block title %}Welcome to the Records website.{% endblock %}

{% block page-content %}
  <h1>Welcome {{ user.first_name }}!</h1>
  <div class="home-option">To create a record click
    <a href="{% url "records:add" %}">here.</a>
  </div>
  <div class="home-option">To see all records click
    <a href="{% url "records:list" %}">here.</a>
  </div>
{% endblock page-content %}

There is nothing new here when compared to the home.html template we saw in Chapter 10, Web Development Done Right. The footer template is actually exactly the same:

records/templates/records/footer.html

<div class="footer">
  Go back <a href="{% url "home" %}">home</a>.
</div>

Listing all records

This template to list all records is fairly simple:

records/templates/records/list.html

{% extends "records/base.html" %}
{% load record_extras %}
{% block title %}Records{% endblock title %}

{% block page-content %}
  <h1>Records</h1><span name="top"></span>
  {% include "records/messages.html" %}

  {% for record in records %}
  <div class="record {% cycle 'row-light-blue' 'row-white' %}"
       id="record-{{ record.pk }}">
    <div class="record-left">
      <div class="record-list">
        <span class="record-span">Title</span>{{ record.title }}
      </div>
      <div class="record-list">
        <span class="record-span">Username</span>
        {{ record.username }}
      </div>
      <div class="record-list">
        <span class="record-span">Email</span>{{ record.email }}
      </div>
      <div class="record-list">
        <span class="record-span">URL</span>
          <a href="{{ record.url }}" target="_blank">
            {{ record.url }}</a>
      </div>
      <div class="record-list">
        <span class="record-span">Password</span>
        {% hide_password record.plaintext %}
      </div>
    </div>
    <div class="record-right">
      <div class="record-list">
        <span class="record-span">Notes</span>
        <textarea rows="3" cols="40" class="record-notes"
                  readonly>{{ record.notes }}</textarea>
      </div>
      <div class="record-list">
        <span class="record-span">Last modified</span>
        {{ record.last_modified }}
      </div>
      <div class="record-list">
        <span class="record-span">Created</span>
        {{ record.created }}
      </div>

    </div>
    <div class="record-list-actions">
      <a href="{% url "records:edit" pk=record.pk %}">» edit</a>
      <a href="{% url "records:delete" pk=record.pk %}">» delete
      </a>
    </div>
  </div>
  {% endfor %}
{% endblock page-content %}

{% block footer %}
  <p><a href="#top">Go back to top</a></p>
  {% include "records/footer.html" %}
{% endblock footer %}

For this template as well, I have highlighted the parts I'd like you to focus on. Firstly, I load a custom tags module, record_extras, which we'll need later. I have also added an anchor at the top, so that we'll be able to put a link to it at the bottom of the page, to avoid having to scroll all the way up.

Then, I included a template to provide me with the HTML code to display Django messages. It's a very simple template which I'll show you shortly.

Then, we define a list of div elements. Each Record instance has a container div, in which there are two other main div elements: record-left and record-right. In order to display them side by side, I have set this class in the main.css file:

.record-left { float: left; width: 300px;}

The outermost div container (the one with class record), has an id attribute, which I have used as an anchor. This allows us to click on cancel on the record delete page, so that if we change our minds and don't want to delete the record, we can get back to the list page, and at the right position.

Each attribute of the record is then displayed in div elements whose class is record-list. Most of these classes are just there to allow me to set a bit of padding and dimensions on the HTML elements.

The next interesting bit is the hide_password tag, which takes the plaintext, which is the unencrypted password. The purpose of this custom tag is to display a sequence of '*' characters, as long as the original password, so that if someone is passing by while you're on the page, they won't see your passwords. However, hovering on that sequence of '*' characters will show you the original password in the tooltip. Here's the code for the hide_password tag:

records/templatetags/record_extras.py

from django import template
from django.utils.html import escape

register = template.Library()

@register.simple_tag
def hide_password(password):
    return '<span title="{0}">{1}</span>'.format(
        escape(password), '*' * len(password))

There is nothing fancy here. We just register this function as a simple tag and then we can use it wherever we want. It takes a password and puts it as a tooltip of a span element, whose main content is a sequence of '*' characters. Just note one thing: we need to escape the password, so that we're sure it won't break our HTML (think of what might happen if the password contained a double-quote `"`, for example).

As far as the list.html template is concerned, the next interesting bit is that we set the readonly attribute to the textarea element, so as not to give the impression to the user that they can modify notes on the fly.

Then, we set a couple of links for each Record instance, right at the bottom of the container div. There is one for the edit page, and another for the delete page. Note that we need to pass the url tag not only the namespace:name string, but also the primary key information, as required by the URL setup we made in the urls.py module for those views.

Finally, we import the footer and set the link to the anchor on top of the page.

Now, as promised, here is the code for the messages:

records/templates/records/messages.html

{% if messages %}
  {% for message in messages %}
    <p class="{{ message.tags }}">{{ message }}</p>
  {% endfor %}
{% endif %}

This code takes care of displaying messages only when there is at least one to display. We give the p tag class information to display success messages in green and error messages in red.

If you grab the main.css file from the source code for the book, you will now be able to visualize the list page (yours will be blank, you still need to insert data into it), and it should look something like this:

Listing all records

As you can see, I have two records in the database at the moment. I'm hovering on the password of the first one, which is my platform account at my sister's school, and the password is displayed in the tooltip. The division in two div elements, left and right, helps in making rows smaller so that the overall result is more pleasing to the eye. The important information is on the left and the ancillary information is on the right. The row color alternates between a very light shade of blue and white.

Each row has an edit and delete link, at its bottom left. We'll show the pages for those two links right after we see the code for the templates that create them.

The CSS code that holds all the information for this interface is the following:

records/static/records/css/main.css

html, body, * {
  font-family: 'Trebuchet MS', Helvetica, sans-serif; }
a { color: #333; }
.record {
  clear: both; padding: 1em; border-bottom: 1px solid #666;}
.record-left { float: left; width: 300px;}
.record-list { padding: 2px 0; }
.fieldWrapper { padding: 5px; }
.footer { margin-top: 1em; color: #333; }
.home-option { padding: .6em 0; }
.record-span { font-weight: bold; padding-right: 1em; }
.record-notes { vertical-align: top; }
.record-list-actions { padding: 4px 0; clear: both; }
.record-list-actions a { padding: 0 4px; }
#pwd-info { padding: 0 6px; font-size: 1.1em; font-weight: bold;}
#id_notes { vertical-align: top; }
/* Messages */
.success, .errorlist {font-size: 1.2em; font-weight: bold; }
.success {color: #25B725; }
.errorlist {color: #B12B2B; }
/* colors */
.row-light-blue { background-color: #E6F0FA; }
.row-white { background-color: #fff; }
.green { color: #060; }
.orange { color: #FF3300; }
.red { color: #900; }

Please remember, I'm not a CSS guru so just take this file as it is, a fairly naive way to provide styling to our interface.

Creating and editing records

Now for the interesting part. Creating and updating a record. We'll use the same template for both, so we expect some decisional logic to be there that will tell us in which of the two situations we are. As it turns out, it will not be that much code. The most exciting part of this template, however, is its associated JavaScript file which we'll examine right afterwards.

records/templates/records/record_add_edit.html

{% extends "records/base.html" %}
{% load static from staticfiles %}
{% block title %}
  {% if update %}Update{% else %}Create{% endif %} Record
{% endblock title %}

{% block page-content %}
  <h1>{% if update %}Update a{% else %}Create a new{% endif %}
    Record
  </h1>
  {% include "records/messages.html" %}

  <form action="." method="post">{% csrf_token %}
    {{ form.non_field_errors }}

    <div class="fieldWrapper">{{ form.title.errors }}
      {{ form.title.label_tag }} {{ form.title }}</div>

    <div class="fieldWrapper">{{ form.username.errors }}
      {{ form.username.label_tag }} {{ form.username }}</div>

    <div class="fieldWrapper">{{ form.email.errors }}
      {{ form.email.label_tag }} {{ form.email }}</div>

    <div class="fieldWrapper">{{ form.url.errors }}
      {{ form.url.label_tag }} {{ form.url }}</div>

    <div class="fieldWrapper">{{ form.password.errors }}
      {{ form.password.label_tag }} {{ form.password }}
      <span id="pwd-info"></span></div>

    <button type="button" id="validate-btn">
      Validate Password</button>
    <button type="button" id="generate-btn">
      Generate Password</button>

    <div class="fieldWrapper">{{ form.notes.errors }}
      {{ form.notes.label_tag }} {{ form.notes }}</div>

    <input type="submit"
      value="{% if update %}Update{% else %}Insert{% endif %}">
  </form>
{% endblock page-content %}

{% block footer %}
  <br>{% include "records/footer.html" %}<br>
  Go to <a href="{% url "records:list" %}">the records list</a>.
{% endblock footer %}

{% block scripts %}
  {{ block.super }}
  <script src="{% static "records/js/api.js" %}"></script>
{% endblock scripts %}

As usual, I have highlighted the important parts, so let's go through this code together.

You can see the first bit of decision logic in the title block. Similar decision logic is also displayed later on, in the header of the page (the h1 HTML tag), and in the submit button at the end of the form.

Apart from this logic, what I'd like you to focus on is the form and what's inside it. We set the action attribute to a dot, which means this page, so that we don't need to customize it according to which view is serving the page. Also, we immediately take care of the cross-site request forgery token, as explained in Chapter 10, Web Development Done Right.

Note that, this time, we cannot leave the whole form rendering up to Django since we want to add in a couple of extra things, so we go down one level of granularity and ask Django to render each individual field for us, along with any errors, along with its label. This way we still save a lot of effort, and at the same time, we can also customize the form as we like. In situations like this, it's not uncommon to write a small template to render a field, in order to avoid repeating those three lines for each field. In this case though, the form is so small I decided to avoid raising the complexity level up any further.

The span element, pwd-info, contains the information about the password that we get from the API. The two buttons after that, validate-btn and generate-btn, are hooked up with the AJAX calls to the API.

At the end of the template, in the scripts block, we need to load the api.js JavaScript file which contains the code to work with the API. We also need to use block.super, which will load whatever code is in the same block in the parent template (for example, jQuery). block.super is basically the template equivalent of a call to super(ClassName, self) in Python. It's important to load jQuery before our library, since the latter is based on the former.

Talking to the API

Let's now take a look at that JavaScript. I don't expect you to understand everything. Firstly, this is a Python book and secondly, you're supposed to be a beginner (though by now, ninja trained), so fear not. However, as JavaScript has, by now, become essential if you're dealing with a web environment, having a working knowledge of it is extremely important even for a Python developer, so try and get the most out of what I'm about to show you. We'll see the password generation first:

records/static/records/js/api.js

var baseURL = 'http://127.0.0.1:5555/password';

var getRandomPassword = function() {
  var apiURL = '{url}/generate'.replace('{url}', baseURL);
  $.ajax({
    type: 'GET',
    url: apiURL,
    success: function(data, status, request) {
      $('#id_password').val(data[1]);
    },
    error: function() { alert('Unexpected error'); }
  });
}

$(function() {
  $('#generate-btn').click(getRandomPassword);
});

Firstly, we set a variable for the base API URL: baseURL. Then, we define the getRandomPassword function, which is very simple. At the beginning, it defines the apiURL extending baseURL with a replacement technique. Even if the syntax is different from that of Python, you shouldn't have any issues understanding this line.

After defining the apiURL, the interesting bit comes up. We call $.ajax, which is the jQuery function that performs the AJAX calls. That $ is a shortcut for jQuery. As you can see in the body of the call, it's a GET request to apiURL. If it succeeds (success: ...), an anonymous function is run, which sets the value of the id_password text field to the second element of the returned data. We'll see the structure of the data when we examine the API code, so don't worry about that now. If an error occurs, we simply alert the user that there was an unexpected error.

Note

The reason why the password field in the HTML has id_password as the ID is due to the way Django renders forms. You can customize this behavior using a custom prefix, for example. In this case, I'm happy with the Django defaults.

After the function definition, we run a couple of lines of code to bind the click event on the generate-btn button to the getRandomPassword function. This means that, after this code has been run by the browser engine, every time we click the generate-btn button, the getRandomPassword function is called.

That wasn't so scary, was it? So let's see what we need for the validation part.

Now there is a value in the password field and we want to validate it. We need to call the API and inspect its response. Since passwords can have weird characters, I don't want to pass them on the URL, therefore I will use a POST request, which allows me to put the password in its body. To do this, I need the following code:

var validatePassword = function() {
  var apiURL = '{url}/validate'.replace('{url}', baseURL);
  $.ajax({
    type: 'POST',
    url: apiURL,
    data: JSON.stringify({'password': $('#id_password').val()}),
    contentType: "text/plain",  // Avoid CORS preflight
    success: function(data, status, request) {
      var valid = data['valid'], infoClass, grade;
      var msg = (valid?'Valid':'Invalid') + ' password.';
      if (valid) {
        var score = data['score']['total'];
        grade = (score<10?'Poor':(score<18?'Medium':'Strong'));
        infoClass = (score<10?'red':(score<18?'orange':'green'));
        msg += ' (Score: {score}, {grade})'
          .replace('{score}', score).replace('{grade}', grade);
      }
      $('#pwd-info').html(msg);
      $('#pwd-info').removeClass().addClass(infoClass);
    },
    error: function(data) { alert('Unexpected error'); }
  });
}

$(function() {
  $('#validate-btn').click(validatePassword);
});

The concept is the same as before, only this time it's for the validate-btn button. The body of the AJAX call is similar. We use a POST instead of a GET request, and we define the data as a JSON object, which is the equivalent of using json.dumps({'password': 'some_pwd'}) in Python.

The contentType line is a quick hack to avoid problems with the CORS preflight behavior of the browser. Cross-origin resource sharing (CORS) is a mechanism that allows restricted resources on a web page to be requested from another domain outside of the domain from which the request originated. In a nutshell, since the API is located at 127.0.0.1:5555 and the interface is running at 127.0.0.1:8000, without this hack, the browser wouldn't allow us to perform the calls. In a production environment, you may want to check the documentation for JSONP, which is a much better (albeit more complex) solution to this issue.

The body of the anonymous function which is run if the call succeeds is apparently only a bit complicated. All we need to do is understand if the password is valid (from data['valid']), and assign it a grade and a CSS class based on its score. Validity and score information come from the API response.

The only tricky bit in this code is the JavaScript ternary operator, so let's see a comparative example for it:

# Python
error = 'critical' if error_level > 50 else 'medium'
// JavaScript equivalent
error = (error_level > 50 ? 'critical' : 'medium');

With this example, you shouldn't have any issue reading the rest of the logic in the function. I know, I could have just used a regular if (...), but JavaScript coders use the ternary operator all the time, so you should get used to it. It's good training to scratch our heads a bit harder in order to understand code.

Lastly, I'd like you to take a look at the end of that function. We set the html of the pwd-info span element to the message we assembled (msg), and then we style it. In one line, we remove all the CSS classes from that element (removeClass() with no parameters does that), and we add the infoClass to it. infoClass is either 'red', 'orange', or 'green'. If you go back to the main.css file, you'll see them at the bottom.

Now that we've seen both the template code and the JavaScript to make the calls, let's see a screenshot of the page. We're going to edit the first record, the one about my sister's school.

Talking to the API

In the picture, you can see that I updated the password by clicking on the Generate Password button. Then, I saved the record (so you could see the nice message on top), and, finally, I clicked on the Validate Password button.

The result is shown in green on the right-hand side of the Password field. It's strong (23 is actually the maximum score we can get) so the message is displayed in a nice shade of green.

Deleting records

To delete a record, go to the list and click on the delete link. You'll be redirected to a page that asks you for confirmation; you can then choose to proceed and delete the poor record, or to cancel the request and go back to the list page. The template code is the following:

records/templates/records/record_confirm_delete.html

{% extends "records/base.html" %}
{% block title %}Delete record{% endblock title %}

{% block page-content %}
  <h1>Confirm Record Deletion</h1>
  <form action="." method="post">{% csrf_token %}
    <p>Are you sure you want to delete "{{ object }}"?</p>
    <input type="submit" value="Confirm" />&nbsp;
    <a href="{% url "records:list" %}#record-{{ object.pk }}">
       » cancel</a>
  </form>
{% endblock page-content %}

Since this is a template for a standard Django view, we need to use the naming conventions adopted by Django. Therefore, the record in question is called object in the template. The {{ object }} tag displays a string representation for the object, which is not exactly beautiful at the moment, since the whole line will read: Are you sure you want to delete "Record object"?.

This is because we haven't added a __str__ method to our Model class yet, which means that Python has no idea of what to show us when we ask for a string representation of an instance. Let's change this by completing our model, adding the __str__ method at the bottom of the class body:

records/models.py

class Record(models.Model):
    ...

    def __str__(self):
        return '{}'.format(self.title)

Restart the server and now the page will read: Are you sure you want to delete "Some Bank"? where Some Bank is the title of the record whose delete link I clicked on.

We could have just used {{ object.title }}, but I prefer to fix the root of the problem, not just the effect. Adding a __str__ method is in fact something that you ought to do for all of your models.

The interesting bit in this last template is actually the link for canceling the operation. We use the url tag to go back to the list view (records:list), but we add anchor information to it so that it will eventually read something like this (this is for pk=2):

http://127.0.0.1:8000/records/#record-2

This will go back to the list page and scroll down to the container div that has ID record 2, which is nice.

This concludes the interface. Even though this section was similar to what we saw in Chapter 10, Web Development Done Right, we've been able to concentrate more on the code in this chapter. We've seen how useful Django class-based views are, and we even touched on some cool JavaScript. Run $ python manage.py runserver and your interface should be up and running at http://127.0.0.1:8000.

Note

If you are wondering, 127.0.0.1 means the localhost—your computer—while 8000 is the port to which the server is bound, to listen for incoming requests.

Now it's time to spice things up a bit with the second part of this project.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset