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.
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'
.
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.
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:
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 »</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 (»
: 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:
{% 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 }}     <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).
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.
18.227.190.211