Writing an app – most important features

To create a web application that stores e-mail addresses, we will need a table that stores the data and web pages that allow the end user to add, delete, and review the address book. Furthermore, we may want to transform the address book to read as a spreadsheet, or send the data to another app through the Internet. There are specific Django features to accomplish all these actions (models, views, admin, API REST-framework, and commands) and we will now discuss the way the data is stored.

Models

To create an e-mail address book, we need to store, in a table, the name of each contact with their e-mail address. A table in Django is called a model and it is defined in the models.py file:

from django.db import models
from django.utils.translation import ugettext_lazy as _

class Person(models.Model):
    name = models.CharField(_('Name'), max_length=255, unique=True)
    mail = models.EmailField(max_length=255, blank=True)
    #display name on admin panel
    def __unicode__(self):
            return self.name

In Django, the columns of a table are the fields of the model, and can be of different types: integer, char, and so on. Note that Django automatically adds an incremental ID field to any new object. The unique option means that duplicate names cannot exist in the model, and blank states whether the field can be empty or not. The __unicode__ function is optional, and it is used to render each person as a string (we set the name string in this case).

Now that the model has been created, we need to apply it to the SQLite database:

python manage.py makemigrations
python manage.py migrate

makemigrations will transform the model changes to migration files (for folder migrations inside addressesapp), while migrate applies the change to the database schema. Note that in case multiple applications are used by the same website, then the command to generate migrations is python manage.py makemigrations 'appname'.

URL and views behind HTML web pages

Now that we know how to store data, we need to record contacts through a web page and show the contacts in another page. In the following section, the pages are described giving a brief overview of the main properties of HTML pages.

HTML pages

All the code explained in this section is stored in the folder template under the test_server folder.

The main page of the application allows the user to record a new contact, and it looks like the following screenshot:

HTML pages

As you can see, the body of the page is specified by two boxes to be filled in with the person's name and their e-mail address, pressing Add to add them to the database. The HTML file, home.html, is as follows:

{% extends "addressesapp/base.html" %}

{% block content %}
        <form action="" method="POST">
            {% csrf_token %}
            <h2 align = Center>Add person to address book </h2>
            <p> <br><br></p>
            <p align = Center><input type="search" class="span3" placeholder="person" name="name" id="search" autofocus /> </p>
            <p align = Center><input type="search" class="span3" placeholder="email" name="email" id="search" autofocus /> </p>
            <p align = Center><button type="submit" class="btn btn-primary btn-large pull-center">Add &raquo;</button></p>
        </form>  
{% endblock content %}

We used the POST form to submit the data collected by the two paragraph fields (specified by <p>...</p>) and activated by the Add button tag (&raquo: is to render the small arrows after the text). The title of the page, Add person to address book, is rendered by a header of type 2 (<h2>...</h2>). Note the csrt_token tag, which enables the cross-site forgery protection request (see more at https://www.squarefree.com/securitytips/web-developers.html#CSRF).

The style of the page (CSS and JavaScript files), as well as the page footer and the header bar with the Home, Emails Book, and Find buttons, are defined in the base.html file (see the template folder). The Find button is implemented as a form:

<form class="navbar-search pull-left" action="{% url 'get_contacts' %}" method="GET">
          {% csrf_token %}
           <div style="overflow: hidden; padding-right: .5em;">
             <input type="text" name="term" style="width: 70%;" />
             <input type="submit" name="search" value="Find" size="30" style="float: right" />
            </div>
        </form>

The div tag has been used to define the text field and the Find button, which activates a GET call to the URL defined as get_contacts in the urls.py file (see the following section).

The other page to display is the address book:

HTML pages
{% extends "addressesapp/base.html" %}

{% block content %}
<h2 align = Center>Email address book</h2>
<P align=Center>[
{% for letter in alphabet %}
which is given by the book.html file:
{% extends "addressesapp/base.html" %}

{% block content %}
<h2 align = Center>Email address book</h2>
<P align=Center>[
{% for letter in alphabet %}
<a href="{% url 'addressesbook'  %}?letter={{letter}}" > {{letter}} </a>
{% endfor %}
|<a href="addressesapp/book.html"> Index </a> ] </P>
<section id="gridSystem">
{% for contact in contacts %}
<div class="row show-grid">
	<p align = Center><strong> name: </strong>{{ contact.name }} <strong>email:</strong> {{ contact.mail }}&nbsp&nbsp&nbsp&nbsp
        <a class="right" href="{% url 'delete_person' contact.name  %}" >   delete </a>
    </p>
</div>
{% endfor %}
</section>

{% endblock content %}

Again, base.html is called to render the main header buttons, the footer, and the style. After a header (of type 2) containing Email address book, a for loop on the alphabet letters, {% for letter in alphabet %}, is performed to show only the contacts starting with the corresponding letter. This is achieved by calling the addressesbook URL with the letter to query {{letter}}. The list of contacts shown is then rendered, looping over the contacts list {% for contact in contacts %}: a paragraph tag displays the name, email, and a button to use to delete the person from the database. We will now discuss the implementation of the page actions (add, find, or delete person, and show address book).

URL declarations and views

We will now discuss the way urls.py and views.py work together with the HTML code of each page to perform the desired actions.

As we have seen, the two main pages of the application, home and address book, are associated with a URL, which in Django is declared in the urls.py file:

from django.conf.urls import patterns, include, url
from django.contrib import admin
from addressesapp.api import AddressesList

urlpatterns = patterns('',
    url(r'^docs/', include('rest_framework_swagger.urls')),
    url(r'^$','addressesapp.views.main'),
    url(r'^book/','addressesapp.views.addressesbook',name='addressesbook'),
    url(r'^delete/(?P<name>.*)/','addressesapp.views.delete_person', name='delete_person'),
    url(r'^book-search/','addressesapp.views.get_contacts', name='get_contacts'),
    url(r'^addresses-list/', AddressesList.as_view(), name='addresses-list'),
    url(r'^notfound/','addressesapp.views.notfound',name='notfound'),url(r'^admin/', include(admin.site.urls)),)

Each URL is specified by a regex (an r in front of the URL string), so the main page is specified by http://127.0.0.1:8000/ (the ^ start symbol is followed by the $ end symbol) and its action (add record) is implemented in the main function of the views.py file:

def main(request):    
    context={}
    if request.method == 'POST':
        post_data = request.POST
        data = {}
        data['name'] = post_data.get('name', None)
        data['email'] = post_data.get('email', None)
        if data:
            return redirect('%s?%s' % (reverse('addressesapp.views.main'),
                                urllib.urlencode({'q': data})))
    elif request.method == 'GET':
        get_data = request.GET
        data= get_data.get('q',None)
        if not data:
            return render_to_response(
                'addressesapp/home.html', RequestContext(request, context))
        data = literal_eval(get_data.get('q',None))
        print data
        if not data['name'] and not data['email']:
            return render_to_response(
                'addressesapp/home.html', RequestContext(request, context))
                
        #add person to emails address book or update
        if Person.objects.filter(name=data['name']).exists():
            p = Person.objects.get(name=data['name'])
            p.mail=data['email']
            p.save()
        else:
            p = Person()
            p.name=data['name']
            p.mail=data['email']
            p.save()
            
        #restart page
        return render_to_response(
            'addressesapp/home.html', RequestContext(request, context)) 

Whenever the user posts a new contact to be store, the POST method redirects the call to a GET method. If the name and the email have been provided, a new object of the Person model will be added, or updated if it already exists. In this method, the same name but in capital letters will be considered a distinct name, so Andrea, ANDREA, and andrea will be three separate contacts. To change this, the reader can simply apply the lower function over the name field, so that the three andrea expressions will all refer to one andrea.

The find action in the base.html file is associated with the http://127.0.0.1:8000/book-search/ URL, and the action is defined in the get_contacts function in views.py:

def get_contacts(request):
    logging.debug('here')
    if request.method == 'GET':
        get_data = request.GET
        data= get_data.get('term','')
        if data == '':
            return render_to_response(
               'addressesapp/nopersonfound.html', RequestContext(request, {}))
        else:
            return redirect('%s?%s' % (reverse('addressesapp.views.addressesbook'),
    urllib.urlencode({'letter': data})))

If the user specifies a non-empty string on the text header field, the function will redirect to the addressesbook function with the name to search (otherwise a not found page is displayed).

The header button Emails book is linked to the http://127.0.0.1:8000/book/ URL, which shows the contacts according to the addressesbook function:

def addressesbook(request):
    context = {}
    logging.debug('address book')
    get_data = request.GET
    letter = get_data.get('letter',None)
    if letter:
        contacts = Person.objects.filter(name__iregex=r"(^|s)%s" % letter)
    else:
        contacts = Person.objects.all()
    #sorted alphabetically
    contacts = sort_lower(contacts,"name")#contacts.order_by("name")
    context['contacts']=contacts
    alphabetstring='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
    context['alphabet']=[l for l in alphabetstring]
    return render_to_response(
        'addressesapp/book.html', RequestContext(request, context)) 
def sort_lower(lst, key_name):
    return sorted(lst, key=lambda item: getattr(item, key_name).lower())

The letter field stores the name (in case of redirection from the Find header button) or the letter (in case of calling from the emails book page), and a lookup over the contacts in the Person model is performed. The retrieved contacts are then stored in the contacts context object, while the letters are stored in the alphabet context object. If no letter is specified, all the contacts in the database are returned. Note that the name can have both a capital and a lowercase first letter, so the usual order_by method will not sort the names in alphabetical order. Therefore, the function sort_lower will convert each name to lowercase and sort the contacts alphabetically.

The delete action is performed by the delete_person function and called by the http://127.0.0.1:8000/delete/(?P<name>.*)/ URL. The .* indicates that all the characters are valid for forming a name (note that if we wanted only character numbers and whitespace, we should have [a-zA-Z0-9 ]+):

def delete_person(request,name):
    if Person.objects.filter(name=name).exists():
       p =  Person.objects.get(name=name)
       p.delete()
       
    context = {}
    contacts = Person.objects.all()
    #sorted alphabetically
    contacts = sort_lower(contacts,"name")#contacts.order_by("name")
    context['contacts']=contacts   
    return render_to_response(
'addressesapp/book.html', RequestContext(request, context))

The name query variable is searched on the Person table in the database and deleted. The function returns the emails book page with the remaining contacts.

In the same way, the not found URL activates the not found function, and you should now be able to understand how it works.

The admin URL refers to the Django interface (see following section) while the docs is the REST framework swagger discussed in the RESTful application programming interfaces (APIs) section of this book.

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

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