Sunteți pe pagina 1din 66

Django-marcador 2017

Django-marcador -Bookmark App


Contents
Django-marcador .............................................................................................................................. 1

Tutorial - Bookmark application ...................................................................................................... 1

1. Preparation ................................................................................................................................... 3

1.1. Python.................................................................................................................................... 3

1.1.1. Linux .............................................................................................................................. 4

1.1.2. Mac OS X ....................................................................................................................... 4

1.1.3. Windows......................................................................................................................... 4

1.2. Python Package Manager ...................................................................................................... 5

1.2.1. Python 3.4 and Python 2.7.9 .......................................................................................... 5

1.2.2. Python 3.3 and Python 2 ................................................................................................ 5

1.2.3. Test pip ........................................................................................................................... 6

2. Install Django ............................................................................................................................... 6

2.1. Install extra packages for this tutorial ................................................................................... 7

3. A new Django Project .................................................................................................................. 7

3.1. Start the Django Project ........................................................................................................ 7

3.2. Test the Development Server ................................................................................................ 8

3.3. Configuration ........................................................................................................................ 9

4. The django-marcador app........................................................................................................... 11

4.1. Create the app ...................................................................................................................... 11

5. Create the Models....................................................................................................................... 12

5.1. Fields ................................................................................................................................... 13


Django-marcador 2017

5.2. Metadata .............................................................................................................................. 14

5.3. Methods ............................................................................................................................... 15

5.4. Manager ............................................................................................................................... 16

5.5. The complete file ................................................................................................................. 17

6. Database Migration .................................................................................................................... 19

6.1. Create the migration ............................................................................................................ 19

6.2. Migrate the database............................................................................................................ 19

7. Admin Site.................................................................................................................................. 20

7.1. Configure the admin site ..................................................................................................... 20

7.2. Create a superuser ............................................................................................................... 21

7.3. Invoke the admin site .......................................................................................................... 21

8. URLs and Views ........................................................................................................................ 25

8.1. Configure the URLs ............................................................................................................ 25

8.1.1. Add URLs to the application ........................................................................................ 26

8.1.2. Add URLs to the project .............................................................................................. 27

8.1.3. Test the new URLs ....................................................................................................... 27

8.2. Add the views ...................................................................................................................... 27

8.2.1. A view to display all bookmarks .................................................................................. 28

8.2.2. A view for each user..................................................................................................... 28

8.2.3. Test the new views ....................................................................................................... 29

9. Templates ................................................................................................................................... 29

9.1. The Django template system ............................................................................................... 30

9.2. Create the base template ...................................................................................................... 30

9.3. Create the templates for the views ...................................................................................... 33

9.4. Test the new templates ........................................................................................................ 36


Django-marcador 2017

10. Setting Up Frontend Login ....................................................................................................... 38

10.1. URLconf ............................................................................................................................ 38

10.2. Configuration .................................................................................................................... 39

10.3. Templates .......................................................................................................................... 40

10.3.1. The main login template ............................................................................................. 40

10.3.2. The login template for the navigation ........................................................................ 41

10.4. Test the frontend login ...................................................................................................... 42

11. Forms ........................................................................................................................................ 44

11.1. Add URLs for the forms.................................................................................................... 44

11.2. Add the Form .................................................................................................................... 45

11.3. Add the Views ................................................................................................................... 46

11.4. Add the Templates ............................................................................................................ 48

11.5. Test the form ..................................................................................................................... 50

12. Advanced Exercises ................................................................................................................ 51

12.1. Adding a tag cloud ................................................................................................................ 51

12.1.1. Writing custom template tags ......................................................................................... 52

13. Further Information .............................................................................................................. 57

14. Changelog................................................................................................................................. 57

1.5.0 2015-05-31 ........................................................................................................................ 58

1.4.1 2014-03-17 ........................................................................................................................ 58

1.4.0 2014-03-16 ........................................................................................................................ 58

1.3.1 2013-10-11 ........................................................................................................................ 58

1.3.0 2013-10-09 ........................................................................................................................ 59

1.2.2 2013-06-15 ........................................................................................................................ 59

1.2.1 2013-05-19 ........................................................................................................................ 59


Django-marcador 2017

1.2.0 2013-04-20 ........................................................................................................................ 59

1.1.1 2013-03-17 ........................................................................................................................ 59

1.1.0 2013-03-16 ........................................................................................................................ 60

1.0.0 2012-10-31 ........................................................................................................................ 60

15. Authors ..................................................................................................................................... 60

15.1. Markus Zapke-Gründemann ............................................................................................. 60

15.2. Additional Authors ............................................................................................................ 61

16. License ..................................................................................................................................... 61


Django-marcador 2017

Tutorial - Bookmark application


django-marcador is a free Django tutorial based on Django 1.8.

Django is an open-source web framework written in Python that focuses on quick development
and clean, pragmatic design.

With every step of the tutorial you will learn Django’s basic principles by creating a bookmark
application similar to Delicious or Pinboard. This is why the tutorial is named django-marcador:
marcador means bookmark in Spanish.

This is how your bookmark application will look like:

1|Page
Django-marcador 2017

If you have basic knowledge in the following areas it will be a lot easier to learn Django:

• Object-oriented programming (using Python)


• HTTP Request/Response model
• HTML and CSS
• SQL and relational databases

If you have any suggestions for improvements, or find any errors in the tutorial, please let us
know in the Issue Tracker.

2|Page
Django-marcador 2017

If you want to discuss new ideas, problems or anything else regarding django- marcador use our
Librelist mailing list and simply write to djangomarcador@librelist.com.

• 1. Preparation
• 2. Install Django
• 3. A new Django Project
• 4. The django-marcador app
• 5. Create the Models
• 6. Database Migration
• 7. Admin Site
• 8. URLs and Views
• 9. Templates
• 10. Setting Up Frontend Login
• 11. Forms
• 12. Advanced Exercises
• 13. Further Information
• 14. Changelog
• 15. Authors
• 16. License

There is also an index which refers to specific topics.

1. Preparation
We’ll use Django Version 1.8. To get started we need to do a little preparation.

1.1. Python

Django is written completely in Python. Therefore Python needs to be installed first.

Note

Django 1.8 supports Python from version 2.7. If you have an older version of Python, you should
update it. Django supports Python 3 since version 1.5.

You can find out which version of Python you’re running by using the command line option --
version:

$ python --version
Python 2.7.6

Note

3|Page
Django-marcador 2017

If you are using Python 3 please make sure you have Python 3.3.2 or greater installed. Otherwise
there will be problems.

Also consider adding the following future-import on top of every Python file you are going to
edit to ensure Python 2 and 3 compatibility:

from __future__ import unicode_literals

This way all regular strings will be unicode string literals.

If you want to learn more read the Python 3 part of the Django documentation.

If you’ve already got the right version of Python installed, you can skip ahead to Python Package
Managers.

1.1.1. Linux

Many Linux distributions come with Python already installed. If you haven’t got a version of
Python installed, you can normally use your package manager to download and install it.

Alternatively, you can get the Python Sources from the website and compile it yourself.

1.1.2. Mac OS X

Python comes pre-installed on Mac OS X. You can however use Homebrew to install your own
copy of Python.

1.1.3. Windows

Download the Installer from the Python Website and install it.

So that Python works under Windows as expected, you need to change the environment variable
%PATH%. In the examples, we’ll assume that your Python is installed in C:\Python27\.

1.1.3.1. Windows 7 

1. Start, then right click on Computer


2. Now click the context menu option Properties
3. Next, in the window that just opened, click on the Advanced System Settings
4. A further window will open, click the Environment Variables
5. Under System Variables, select the PATH
6. Now click on Edit and add the required directory:
;C:\Python27\;C:\Python27\Scripts;. (The semi-colon at the beginning is required!)
7. Now close the windows Environment Variables and System Properties by clicking on OK.
4|Page
Django-marcador 2017

1.1.3.2. Windows XP 

1. Start ‣ Control Panel ‣ System ‣ Advanced


2. Click on the Environment Variables, then a new window will open. Under “System
Variables” select Path
3. Now click on Edit and add the required directory:
;C:\Python27\;C:\Python27\Scripts;. (The semi-colon at the beginning is required!)
4. Now close the windows Environment Variables and System Properties by clicking on OK.

1.2. Python Package Manager

Python has its own package system to manage distribution and installation of Python packages.
Because we will need to install several packages, we must first make sure the package manager
pip is installed. pip was originally written as an improvement of easy_install.

To decide which installation procedure is right for you check again your Python version:

$ python --version
Python 2.7.6

1.2.1. Python 3.4 and Python 2.7.9

If you have Python 3.4 or Python 2.7.9 (or newer) installed you can use ensurepip to install or
upgrade pip:

$ python -m ensurepip --upgrade

If your Python version is too old you will simply see an error message like that:

$ python -m ensurepip --upgrade


/usr/bin/python: No module named ensurepip

1.2.2. Python 3.3 and Python 2

If you have Python 3.3 or Python 2 with a version lower than 2.7.9 installed, your Python
installation does not contain the module ensurepip. In this case pip can be installed with the help
from a bootstrap script. If curl is installed, you can use it to download pip at the command line.
Otherwise just use the browser.

$ curl -O https://bootstrap.pypa.io/get-pip.py

When the bootstrap script has been downloaded execute it to install pip:

$ python get-pip.py

5|Page
Django-marcador 2017

Note

Under Linux and Mac OS X root privileges may be required. In this case use:

$ sudo python get-pip.py

Note

If you have problems running get-pip.py because of the configuration of your internet
connection please have a look the pip installation documentation.

You can delete the bootstrap script when the installation has been finished.

1.2.3. Test pip

After a successful upgrade or installation, you can test pip as follows:

$ pip --version
pip 7.0.1 from /Users/keimlink/.virtualenvs/django-
marcador/lib/python2.7/site-packages (python 2.7)

2. Install Django
Let’s install Django! We’ll use version 1.8 in this tutorial. Newer releases should work as well,
but if you are in doubt, use 1.8:

$ pip install --upgrade Django==1.8.2

Note

Under Linux and Mac OS X root privileges may be required. In this case use:

$ sudo pip install --upgrade Django==1.8.2

Note

If you want to isolate the Python packages from the system site directories you have two options:

1. Install the package(s) into a directory in your home directory using:


2. $ pip install --user <packagename>
3. Use a virtual environment for your project. One way to create a virtual environment is to
install virtualenv. If you are using Python 3 you can also use the new venv module.

If you go for one of the options you can use it any time this tutorial asks you to run pip install.

6|Page
Django-marcador 2017

After a successful installation, you can check the Django version number with the following
command:

$ django-admin.py --version
1.8.2

Note

It could be that the file django_admin.py is actually called django-admin. That’s not a problem,
just leave off the extension .py.

On Windows you may get an ImportError when you try to run django-admin.py. Prefix all
commands that use .py files with python and use the full path to the file, like so:

> python C:\Python27\Scripts\django-admin.py

2.1. Install extra packages for this tutorial

We will use Bootstrap 3 to get the frontend done quickly. Since Bootstrap expects a certain
markup structure, we will use the excellent django-crispy-forms package to get our forms
rendered nicely.

Starting with version 1.4, Django supports Timezones. This is activated by default and it is highly
recommended to install the pytz package.

To install both packages execute:

$ pip install pytz django-crispy-forms

Note

Under Linux and Mac OS X root privileges may be required.

3. A new Django Project


3.1. Start the Django Project

You can create a new Django project with the following command:

$ django-admin.py startproject mysite

After you’ve run the command you’ll find the following structure:

mysite
|-- manage.py
7|Page
Django-marcador 2017

`-- mysite
|-- __init__.py
|-- settings.py
|-- urls.py
`-- wsgi.py

3.2. Test the Development Server

After you’ve created the project, you can change to the directory mysite:

$ cd mysite

And try out the development server with the following command:

$ python manage.py runserver


Performing system checks...

System check identified no issues (0 silenced).


May 30, 2015 - 17:19:37
Django version 1.8.2, using settings 'mysite.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

Note

If you are on Windows and the command fails with an UnicodeDecodeError, use this command
instead:

$ python manage.py runserver 0.0.0.0:8000

Now you can open the “Welcome to Django” site from http://127.0.0.1:8000/. After you’ve
opened the site, you can kill the development server with CTRL + C.

8|Page
Django-marcador 2017

3.3. Configuration

In order to work with the project, you need to configure it. To do that, open the file settings.py
in a text editor.

So that you don’t need to enter the project directory several times in the configuration, it’s saved
in a “constant”. This constant can then be used everywhere where the project directory is
required. You can find it right at the top of the settings.py file:

13 # Build paths inside the project like this: os.path.join(BASE_DIR, ...)


14 import os
15
16 BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

The first thing that needs to be configured is the path where the templates will be located. The
settings.py already contains a TEMPLATES constant which is preconfigured to use Django’s
template engine. Add the path to the templates directory to the DIRS list:

57 TEMPLATES = [
58 {
59 'BACKEND': 'django.template.backends.django.DjangoTemplates',

9|Page
Django-marcador 2017

60 'DIRS': [os.path.join(BASE_DIR, 'templates')],


61 'APP_DIRS': True,
62 'OPTIONS': {
63 'context_processors': [
64 'django.template.context_processors.debug',
65 'django.template.context_processors.request',
66 'django.contrib.auth.context_processors.auth',
67 'django.contrib.messages.context_processors.messages',
68 ],
69 },
70 },
71 ]

The the existing database connection default is already configured to use SQLite, because it’s
built into Python:

76 # Database
77 # https://docs.djangoproject.com/en/1.8/ref/settings/#databases
78
79 DATABASES = {
80 'default': {
81 'ENGINE': 'django.db.backends.sqlite3',
82 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
83 }
84 }

Next change the timezone and language to suit:

87 # Internationalization
88 # https://docs.djangoproject.com/en/1.8/topics/i18n/
89
90 LANGUAGE_CODE = 'en-us'
91
92 TIME_ZONE = 'Europe/Berlin'

The constant LANGUAGE_CODE configures the language of the Admin inferface which we will use
later to English. You can change it to a different language, e.g. use de as LANGUAGE_CODE if you
want to use German.

Lastly, the path to static files must be defined at the end of settings.py by adding the
STATICFILES_DIRS setting:

106 # Static files (CSS, JavaScript, Images)


107 # https://docs.djangoproject.com/en/1.8/howto/static-files/
108
109 STATIC_URL = '/static/'
110
111 STATICFILES_DIRS = (
112 os.path.join(BASE_DIR, 'static'),
113 )

10 | P a g e
Django-marcador 2017

Now create the directory for static files and templates under directory mysite:

$ mkdir static templates

Afterwards the directory structure should look as follows:

mysite
|-- manage.py
|-- mysite
| |-- __init__.py
| |-- settings.py
| |-- urls.py
| `-- wsgi.py
|-- static
`-- templates

4. The django-marcador app


The aim of the tutorial is to create a Django app to manage bookmarks. Now that the project is
created and configured, you can begin to work on the app itself.

4.1. Create the app

You can create the app with the following command:

$ python manage.py startapp marcador

A new directory marcador will be created with the following comments:

marcador
|-- __init__.py
|-- admin.py
|-- migrations
| `-- __init__.py
|-- models.py
|-- tests.py
`-- views.py

The directory is found under the project directory mysite:

mysite
|-- manage.py
|-- marcador
| |-- __init__.py
| |-- admin.py
| |-- migrations
| | `-- __init__.py
| |-- models.py
| |-- tests.py
11 | P a g e
Django-marcador 2017

| `-- views.py
|-- mysite
| |-- __init__.py
| |-- settings.py
| |-- urls.py
| `-- wsgi.py
|-- static
`-- templates

Finally, add your new app marcador to INSTALLED_APPS in the file settings.py:

33 INSTALLED_APPS = (
34 'django.contrib.admin',
35 'django.contrib.auth',
36 'django.contrib.contenttypes',
37 'django.contrib.sessions',
38 'django.contrib.messages',
39 'django.contrib.staticfiles',
40 'marcador',
41 )

5. Create the Models


“A model is the single, definitive source of information about your data. It contains the essential
fields and behaviors of the data you’re storing. Generally, each model maps to a single database
table.”

—Models | Django documentation

The next step is to create the data structure with help from models.

Models define how data is saved. Usually a model represents a table in the database, and has
fields, metadata and methods. With this information Django can automatically generate an
interface to the database that allows object oriented access to it. This is called Object-Relation-
Mapping (ORM).

12 | P a g e
Django-marcador 2017

In django-marcador, two models are required. You can see them in the entity–relationship model
above. Bookmark represents the actual bookmarks and has fields for URL, title and description.
Data about who created the bookmark and when, as well as whether the bookmark is public or
not is also stored here. This data will be used later to filter the bookmarks. A second model Tag
represents keywords that can be attached to bookmarks to make them easier to find. Bookmark
and Tag have a many-to-many relationship, so Django will create the intermediate table
automatically.

In order that Django recognises your class as a model, it must inherit from
django.db.models.Model and be placed in models.py in your app directory (in this example
mysite/marcador/).

5.1. Fields

Fields are defined as class attributes, and map to the columns in the table. Django has several
different types of fields, in order to allow access to the data in the most sensible way. For
example CharField means a VARCHAR column in an SQL database. You can find a list of field
types in the Django documentation.

1 # encoding: utf-8
2 from django.contrib.auth.models import User
3 from django.db import models

13 | P a g e
Django-marcador 2017

4
5
6 class Tag(models.Model):
7 name = models.CharField(max_length=50, unique=True)
8
9
10 class Bookmark(models.Model):
11 url = models.URLField()
12 title = models.CharField('title', max_length=255)
13 description = models.TextField('description', blank=True)
14 is_public = models.BooleanField('public', default=True)
15 date_created = models.DateTimeField('date created')
16 date_updated = models.DateTimeField('date updated')
17 owner = models.ForeignKey(User, verbose_name='owner',
18 related_name='bookmarks')
19 tags = models.ManyToManyField(Tag, blank=True)

5.2. Metadata

Models can contain Metadata, that can influence how they are displayed or how they behave.
They are defined in the internal class Meta. In the example, the display name for presentation is
set (singular and plural), as well as the default sort order.

1 # encoding: utf-8
2 from django.contrib.auth.models import User
3 from django.db import models
4
5
6 class Tag(models.Model):
7 name = models.CharField(max_length=50, unique=True)
8
9 class Meta:
10 verbose_name = 'tag'
11 verbose_name_plural = 'tags'
12 ordering = ['name']
13
14
15 class Bookmark(models.Model):
16 url = models.URLField()
17 title = models.CharField('title', max_length=255)
18 description = models.TextField('description', blank=True)
19 is_public = models.BooleanField('public', default=True)
20 date_created = models.DateTimeField('date created')
21 date_updated = models.DateTimeField('date updated')
22 owner = models.ForeignKey(User, verbose_name='owner',
23 related_name='bookmarks')
24 tags = models.ManyToManyField(Tag, blank=True)
25
26 class Meta:
27 verbose_name = 'bookmark'
28 verbose_name_plural = 'bookmarks'
29 ordering = ['-date_created']

14 | P a g e
Django-marcador 2017

5.3. Methods

You can now add Model functionality Methods for actions that apply to a single record. For
instance, it is usual to create a human readable form of the record with the method __str__.

1 # encoding: utf-8
2 from django.contrib.auth.models import User
3 from django.db import models
4 from django.utils.encoding import python_2_unicode_compatible
5 from django.utils.timezone import now
6
7
8 @python_2_unicode_compatible
9 class Tag(models.Model):
10 name = models.CharField(max_length=50, unique=True)
11
12 class Meta:
13 verbose_name = 'tag'
14 verbose_name_plural = 'tags'
15 ordering = ['name']
16
17 def __str__(self):
18 return self.name
19
20
21 @python_2_unicode_compatible
22 class Bookmark(models.Model):
23 url = models.URLField()
24 title = models.CharField('title', max_length=255)
25 description = models.TextField('description', blank=True)
26 is_public = models.BooleanField('public', default=True)
27 date_created = models.DateTimeField('date created')
28 date_updated = models.DateTimeField('date updated')
29 owner = models.ForeignKey(User, verbose_name='owner',
30 related_name='bookmarks')
31 tags = models.ManyToManyField(Tag, blank=True)
32
33 class Meta:
34 verbose_name = 'bookmark'
35 verbose_name_plural = 'bookmarks'
36 ordering = ['-date_created']
37
38 def __str__(self):
39 return '%s (%s)' % (self.title, self.url)
40
41 def save(self, *args, **kwargs):
42 if not self.id:
43 self.date_created = now()
44 self.date_updated = now()
45 super(Bookmark, self).save(*args, **kwargs)

In the bookmark model, we’ll also override the method save() to set the right creation or last-
changed date. The state of the id field will be used, in order to decide if the model has already
15 | P a g e
Django-marcador 2017

been saved or not. The field id exists in every model - if it’s not explicitely declared, Django will
create it automatically. It is used as the primary key of the model, to uniquely identify the record.
If this field doesn’t exist, the model hasn’t been saved yet. As a last step, the function super() is
used to call the method save() from the base class (i.e. the class that we’ve inherited from).

Note

Marcador supports Python 2 and Python 3. This is achieved by the the decorator
@python_2_unicode_compatible and by using __str__ instead of __unicode__ as you may
find it in older versions of the documentation.

5.4. Manager

In order to run the database queries, there’s a manager for every model. Unless it’s otherwise
defined, the attribute objects holds a reference to the default manager. The queries can be
altered by overriding the default manager or adding another one.

The Django documentation explains the usage of a manager as follows:

“To retrieve objects from your database, construct a QuerySet via a Manager on your model
class.

A QuerySet represents a collection of objects from your database. It can have zero, one or many
filters – criteria that narrow down the collection based on given parameters. In SQL terms, a
QuerySet equates to a SELECT statement, and a filter is a limiting clause such as WHERE or
LIMIT.”

—Making queries | Django documentation

Most of the QuerySet methods return a new QuerySet so you can combine multiple of them. To
access the items in a QuerySet you can loop over it or simply use the item’s index. For example
the following Python code returns the first item of a QuerySet which contains all public
bookmarks ordered by their title:

>>> Bookmark.objects.filter(is_public=True).order_by('title')[0]

The SQL query executed against the database for the Python code above looks like this:

SELECT "marcador_bookmark"."id",
"marcador_bookmark"."url",
"marcador_bookmark"."title",
"marcador_bookmark"."description",
"marcador_bookmark"."is_public",
"marcador_bookmark"."date_created",
"marcador_bookmark"."date_updated",
"marcador_bookmark"."owner_id"

16 | P a g e
Django-marcador 2017

FROM "marcador_bookmark"
WHERE "marcador_bookmark"."is_public" = True
ORDER BY "marcador_bookmark"."title" ASC LIMIT 1

You can find a list of all QuerySet methods in the documentation.

In our example we’ll often show only public bookmarks so we add a second manager to the
Bookmark model that will only return the public bookmarks. We’ll assign it to the attribute
public. In order to keep a reference to the default manager, we have to explicitely assign it to the
objects attribute of the Bookmark class.

1 class PublicBookmarkManager(models.Manager):
2 def get_queryset(self):
3 qs = super(PublicBookmarkManager, self).get_queryset()
4 return qs.filter(is_public=True)
5
6
7 @python_2_unicode_compatible
8 class Bookmark(models.Model):
9 url = models.URLField()
10 title = models.CharField('title', max_length=255)
11 description = models.TextField('description', blank=True)
12 is_public = models.BooleanField('public', default=True)
13 date_created = models.DateTimeField('date created')
14 date_updated = models.DateTimeField('date updated')
15 owner = models.ForeignKey(User, verbose_name='owner',
16 related_name='bookmarks')
17 tags = models.ManyToManyField(Tag, blank=True)
18
19 objects = models.Manager()
20 public = PublicBookmarkManager()
21
22 class Meta:
23 verbose_name = 'bookmark'
24 verbose_name_plural = 'bookmarks'
25 ordering = ['-date_created']
26
27 def __str__(self):
28 return '%s (%s)' % (self.title, self.url)
29
30 def save(self, *args, **kwargs):
31 if not self.id:
32 self.date_created = now()
33 self.date_updated = now()
34 super(Bookmark, self).save(*args, **kwargs)

5.5. The complete file

When everything is complete, the file models.py should look as follows:

1 # encoding: utf-8

17 | P a g e
Django-marcador 2017

2 from django.contrib.auth.models import User


3 from django.db import models
4 from django.utils.encoding import python_2_unicode_compatible
5 from django.utils.timezone import now
6
7
8 @python_2_unicode_compatible
9 class Tag(models.Model):
10 name = models.CharField(max_length=50, unique=True)
11
12 class Meta:
13 verbose_name = 'tag'
14 verbose_name_plural = 'tags'
15 ordering = ['name']
16
17 def __str__(self):
18 return self.name
19
20
21 class PublicBookmarkManager(models.Manager):
22 def get_queryset(self):
23 qs = super(PublicBookmarkManager, self).get_queryset()
24 return qs.filter(is_public=True)
25
26
27 @python_2_unicode_compatible
28 class Bookmark(models.Model):
29 url = models.URLField()
30 title = models.CharField('title', max_length=255)
31 description = models.TextField('description', blank=True)
32 is_public = models.BooleanField('public', default=True)
33 date_created = models.DateTimeField('date created')
34 date_updated = models.DateTimeField('date updated')
35 owner = models.ForeignKey(User, verbose_name='owner',
36 related_name='bookmarks')
37 tags = models.ManyToManyField(Tag, blank=True)
38
39 objects = models.Manager()
40 public = PublicBookmarkManager()
41
42 class Meta:
43 verbose_name = 'bookmark'
44 verbose_name_plural = 'bookmarks'
45 ordering = ['-date_created']
46
47 def __str__(self):
48 return '%s (%s)' % (self.title, self.url)
49
50 def save(self, *args, **kwargs):
51 if not self.id:
52 self.date_created = now()
53 self.date_updated = now()
54 super(Bookmark, self).save(*args, **kwargs)

18 | P a g e
Django-marcador 2017

6. Database Migration
“Migrations are Django’s way of propagating changes you make to your models (adding a field,
deleting a model, etc.) into your database schema.”

—Migrations | Django documentation

Now that you have created the models, the next step is to create the tables in the database.

6.1. Create the migration

The first step is to create a migration so that Django’s migration framework knows about the new
models you created. You do that by running the makemigrations command:

$ python manage.py makemigrations marcador


Migrations for 'marcador':
0001_initial.py:
- Create model Bookmark
- Create model Tag
- Add field tags to bookmark

This will create the new file 0001_initial.py in the directory migrations of the app
marcador:

marcador
|-- __init__.py
|-- admin.py
|-- migrations
| |-- 0001_initial.py
| `-- __init__.py
|-- models.py
|-- tests.py
`-- views.py

6.2. Migrate the database

After that you have to apply the migration you just created as well as the already existing
migrations to create the database schema by using the migrate command:

$ python manage.py migrate


Operations to perform:
Synchronize unmigrated apps: staticfiles, messages
Apply all migrations: admin, contenttypes, marcador, auth, sessions
Synchronizing apps without migrations:
Creating tables...
Running deferred SQL...
Installing custom SQL...
Running migrations:
19 | P a g e
Django-marcador 2017

Rendering model states... DONE


Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
Applying admin.0001_initial... OK
Applying contenttypes.0002_remove_content_type_name... OK
Applying auth.0002_alter_permission_name_max_length... OK
Applying auth.0003_alter_user_email_max_length... OK
Applying auth.0004_alter_user_username_opts... OK
Applying auth.0005_alter_user_last_login_null... OK
Applying auth.0006_require_contenttypes_0002... OK
Applying marcador.0001_initial... OK
Applying sessions.0001_initial... OK

Now all tables defined in the migrations have been created in the database.

7. Admin Site
“One of the most powerful parts of Django is the automatic admin interface. It reads metadata in
your model to provide a powerful and production-ready interface that content producers can
immediately use to start adding content to the site.”

—The Django admin site | Django documentation

After you’ve created the models it’s time to configure the admin site.

7.1. Configure the admin site

Open file admin.py in the directory marcador and add the following contents:

1 from django.contrib import admin


2
3 from .models import Bookmark, Tag
4
5
6 class BookmarkAdmin(admin.ModelAdmin):
7 list_display = ('url', 'title', 'owner', 'is_public', 'date_updated')
8 list_editable = ('is_public',)
9 list_filter = ('is_public', 'owner__username')
10 search_fields = ['url', 'title', 'description']
11 readonly_fields = ('date_created', 'date_updated')
12
13
14 admin.site.register(Bookmark, BookmarkAdmin)
15 admin.site.register(Tag)

The attributes in BookmarkAdmin have the following functions:

• list_display: These fields will be shown in the list view.

20 | P a g e
Django-marcador 2017

• list_editable: These fields are editable in the list view .


• list_filter: These fields can be filtered in the list view based on their values. The filter
items will be shown in a column on the right side.
• search_fields: These fields can be searched for in the list view based on their values. A
search field will be shown above the list.
• readonly_fields: These fields are not editable in the detail view.

You can find a list of all the configuration possibilities in the documentation of the Admin-App.

7.2. Create a superuser

To create a new superuser run the command createsuperuser and fill out the fields that follow:

$ python manage.py createsuperuser


Username (leave blank to use 'keimlink'): admin
Email address:
Password:
Password (again):
Superuser created successfully.

In the next step, you can login with these login details.

7.3. Invoke the admin site

Start the development server:

$ python manage.py runserver


Performing system checks...

System check identified no issues (0 silenced).


May 30, 2015 - 17:19:37
Django version 1.8.2, using settings 'mysite.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

Now you can visit the admin site at http://127.0.0.1:8000/admin/ and login with the user you
created earlier, in order to save some bookmarks.

This is how the login form looks like:

21 | P a g e
Django-marcador 2017

After logging in you will see the site administration page:

22 | P a g e
Django-marcador 2017

If you click on the “Add” button in the “Bookmarks” row the form below will appear, which you
can use to create new bookmarks. Create new tags by clicking on the green plus right beside the
tags list. Note that the date created and date updated fields are read-only, as we configured it:

23 | P a g e
Django-marcador 2017

After clicking the “Save” button you will see this list view - note the list, search and filter options
you configured at the beginning of this chapter:

24 | P a g e
Django-marcador 2017

Create a few more bookmarks and tags using the “Add bookmark” button at the right top of the
list, so that there is enough content for the next chapter in which you will build the frontend.

Data from other apps can also be managed in the admin site. You can use the admin to manage
users and their groups.

8. URLs and Views


Now you can start to build the frontend for the marcador app. In this chapter two URLs and their
corresponding views will be created. The first is a simple list view of all public bookmarks, the
second is a list of all bookmarks from a particular person.

8.1. Configure the URLs

“A clean, elegant URL scheme is an important detail in a high-quality Web application. Django
lets you design URLs however you want, with no framework limitations.”

25 | P a g e
Django-marcador 2017

—URL dispatcher | Django documentation

So that the views can be used, paths must be associated with them. In Django, this is done
explicitly with a URL configration (URLconf). We’ll create a new URLconf just for the
marcador app, so that later on the project will have a cleaner structure.

8.1.1. Add URLs to the application

Create a new file named urls.py in the directory mysite/marcador and paste in the following
lines:

from django.conf.urls import url


1
2
3
urlpatterns = [
4
url(r'^user/(?P<username>[-\w]+)/$', 'marcador.views.bookmark_user',
5
name='marcador_bookmark_user'),
6
url(r'^$', 'marcador.views.bookmark_list',
7
name='marcador_bookmark_list'),
8
]

Every URLconf must define a variable urlpatterns which is a list of URLs. Each URL will be
created with the url() function. In our example url() is given three parameters: the path, the
names of the views, and an identifier (name), with which you can refer to this URL later. In the
documentation you’ll find a list of further parameters.

With Django the paths are defined as Regular Expressions (regexes). Regexes are a powerful tool
with which to describe strings of characters by means of syntactic rules. You can find a detailed
introduction to this theme at regular-expressions.info. The Online regex tester and debugger can
help you to craft regular expressions for new URLs.

The regex for the list view, r'^$', consists of the control characters ^ (the beginning of the
string) and $ (the end of the string). When nothing is in-between these two characters, they
describe an empty string. That means that the view is reachable from the path /, the home page.

The regex for the view with the bookmarks of a particular user is a bit more complex:
r'^user/(?P<username>[-\w]+)/$'. It contains a variable part that enables the user names to
be filtered out. So with /user/alice/ the bookmarks from Alice can be requested, and with
/user/bob/, the bookmarks from Bob.

The regexes are composed as follows: after the control character for the beginning ^ comes
user/, a static part that must match exactly. Then follows a grouping (?P<username>[-\w]+).
This means that all the following letters, numbers, underscores and dashes (defined by [-\w]+)
can be accessed using the variable username. Django makes sure that all groupings are passed to
the view as arguments. At the end is the static part / and the control character for the end of the
string $. With Django it’s conventional for all paths to end with a /.
26 | P a g e
Django-marcador 2017

8.1.2. Add URLs to the project

So that the URLconf for our app can be found, it must be referenced in the root URLconf. So
paste the emphasized line into the file mysite/mysite/urls.py:

"""mysite URL Configuration


1
2
The `urlpatterns` list routes URLs to views. For more information please
3
see:
4
https://docs.djangoproject.com/en/1.8/topics/http/urls/
5
Examples:
6
Function views
7
1. Add an import: from my_app import views
8
2. Add a URL to urlpatterns: url(r'^$', views.home, name='home')
9
Class-based views
10
1. Add an import: from other_app.views import Home
11
2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home')
12
Including another URLconf
13
1. Add an import: from blog import urls as blog_urls
14
2. Add a URL to urlpatterns: url(r'^blog/', include(blog_urls))
15
"""
16
from django.conf.urls import include, url
17
from django.contrib import admin
18
19
urlpatterns = [
20
url(r'^admin/', include(admin.site.urls)),
21
url(r'^', include('marcador.urls')),
22
]

8.1.3. Test the new URLs

Now you can visit both URLs in your browser:

• http://127.0.0.1:8000/
• http://127.0.0.1:8000/user/admin/

For both URLs a ViewDoesNotExist exception will be raised. This means that the URLs were
correctly resolved, but the associated views do not exist.

8.2. Add the views

“A view function, or view for short, is simply a Python function that takes a Web request and
returns a Web response.”

—Writing views | Django documentation

27 | P a g e
Django-marcador 2017

The next step is to add the views. In Django a view is a function that receives a request object and
returns a response object. The function contains the logic that determines the content of the
response.

8.2.1. A view to display all bookmarks

The first view gives a list of the public bookmarks. Open the file views.py in the directory
mysite/marcador/ and paste the following code in:

1 from django.shortcuts import get_object_or_404, redirect, render


2
3 from .models import Bookmark
4
5
6 def bookmark_list(request):
7 bookmarks = Bookmark.public.all()
8 context = {'bookmarks': bookmarks}
9 return render(request, 'marcador/bookmark_list.html', context)

First of all the database query for all public bookmarks is be generated by means of the ORM,
and assigned to the variable bookmarks. This is then in turn stored in the dictionary context. All
keys in this dictionary will later be available to the templates as variables. Finally the function
render() generates the response object from the request, the path to the template and the
dictionary. render() is a shortcut, that carries out several steps in one. You’ll need the shortcuts
get_object_or_404() and redirect() for the following views. You can find out what exactly
the shortcuts do in the documentation.

8.2.2. A view for each user

The second view lists all public bookmarks from a particular user. The new rows that you must
paste in are highlighted:

1 from django.contrib.auth.models import User


2 from django.shortcuts import get_object_or_404, redirect, render
3
4 from .models import Bookmark
5
6
7 def bookmark_list(request):
8 bookmarks = Bookmark.public.all()
9 context = {'bookmarks': bookmarks}
10 return render(request, 'marcador/bookmark_list.html', context)
11
12
13 def bookmark_user(request, username):
14 user = get_object_or_404(User, username=username)
15 if request.user == user:
16 bookmarks = user.bookmarks.all()
17 else:
28 | P a g e
Django-marcador 2017

18 bookmarks = Bookmark.public.filter(owner__username=username)
19 context = {'bookmarks': bookmarks, 'owner': user}
20 return render(request, 'marcador/bookmark_user.html', context)

It has an additional argument username, with which the requested user in the database is sought.
This contains the same variable as in the regex of the URLconf. If the user is not found, the
HTTP status code 404 will be returned automatically. Whether the user exists and whether he or
she is the logged-in user is checked with the help of the relationship, defined by the field
Bookmark.owner with the model User, and the bookmarks loaded. If the view for another user is
invoked, the public bookmarks are filtered with the help of the argument username. Bookmarks
and user are then added to the context dictionary, so that they can be accessed in the template
later.

8.2.3. Test the new views

You can now test both URLs:

• http://127.0.0.1:8000/
• http://127.0.0.1:8000/user/admin/

Of course both currently produce a TemplateDoesNotExist exception, because the templates do


not yet exist. Notice that the second URL will only work if you have an user named “admin”.
Otherwise use the username of the superuser you created when you ran the createsuperuser
command.

9. Templates
“Django’s template language is designed to strike a balance between power and ease. It’s
designed to feel comfortable to those used to working with HTML. If you have any exposure to
other text-based template languages, such as Smarty or Jinja2, you should feel right at home with
Django’s templates.”

—The Django template language | Django documentation

To view the contents, which are generated by the views we just created, we now need to prepare
the templates.

29 | P a g e
Django-marcador 2017

9.1. The Django template system

Django has it’s own template system to create HTML which doesn’t simply mix up Python code
and HTML. This is done for a reason: The template system should be used to display the contents
and not for programming logic.

The Django template system provides various tags to perform simple operations. For example
can the if tag be used to execute various tests or the for tag to iterate over a list. However the
templates are not executed like regular Python code and the choice of functions is limited to the
tags and filters provided by the Django template system. But it is possible to extend the template
system with your own tags and filters.

Aside from that the Django template system has a powerful inheritance feature. With this feature
it’s possible to use a base template that contains all basic elements of the website and defines
different blocks. Some or all of these blocks can be overwritten or extended by child templates
which extend the base template. Those who are familiar with object orientated programming
know this pattern.

9.2. Create the base template

At first create the base template with the help of the HTML5 Boilerplate. It comes together with
Bootstrap 3, a popular HTML and CSS framework for developing responsive, mobile first
websites and jQuery, a fast, small, and feature-rich JavaScript library. Perform the following
steps to download and copy the files:

1. Click at the Initializr Website on “Bootstrap” (www.initializr.com)


2. In the section “H5BP Optional” check the “404 Page” option
3. Finally click on the button “Download it!”
4. Extract the ZIP archive
5. Copy the file index.html into the directory mysite/templates and rename it to
base.html
6. Copy the file 404.html into the same directory
7. Copy the directories css, img and js and the file apple-touch-icon.png into the
directory mysite/static

Note

To make the download of the HTML5 Boilerplate ZIP archive work your browser needs to accept
the cookies set by the website.

Now you need to adjust the base template base.html so that Django is able to able to use the
right path for the static files. Therefor you will use the static template tag from the staticfiles app.
Template tags are written this way: {% tag %}. Because the tag static is provided by an app
it’s not part of the default template tags and needs to be loaded first. This is done with the help of

30 | P a g e
Django-marcador 2017

the load tag at the top of the template. Customize all highlighted parts in the head of base.html
as shown:

{% load staticfiles %}
<!doctype html>
1 <!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7" lang="">
2 <![endif]-->
3 <!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8" lang="">
4 <![endif]-->
5 <!--[if IE 8]> <html class="no-js lt-ie9" lang=""> <![endif]-->
6 <!--[if gt IE 8]><!--> <html class="no-js" lang=""> <!--<![endif]-->
7 <head>
8 <meta charset="utf-8">
9 <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
10 <title></title>
11 <meta name="description" content="">
12 <meta name="viewport" content="width=device-width, initial-scale=1">
13 <link rel="apple-touch-icon" href="{% static "apple-touch-icon.png" %}">
14
15 <link rel="stylesheet" href="{% static "css/bootstrap.min.css" %}">
16 <style>
17 body {
18 padding-top: 50px;
19 padding-bottom: 20px;
20 }
21 </style>
22 <link rel="stylesheet" href="{% static "css/bootstrap-
23 theme.min.css" %}">
24 <link rel="stylesheet" href="{% static "css/main.css" %}">
25
26 <script src="{% static "js/vendor/modernizr-2.8.3-respond-
27 1.4.2.min.js" %}"></script>
</head>
<body>

Now follow the locations where the static tag needs to be placed at the end of the template
base.html:

87 <footer>
88 <p>&copy; Company 2015</p>
89 </footer>
90
91 </div> <!-- /container -->
92
93 <script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js">
94 </script>
95 <script>window.jQuery || document.write('<script src="{% static
96 "js/vendor/jquery-1.11.2.min.js" %}"><\/script>')
97 </script>
98
99 <script src="{% static "js/vendor/bootstrap.min.js" %}"></script>
100
101 <script src="{% static "js/main.js" %}"></script>

31 | P a g e
Django-marcador 2017

102
103 <!-- Google Analytics: change UA-XXXXX-X to be your site's ID. -->
104 <script>
105 (function(b,o,i,l,e,r){b.GoogleAnalyticsObject=l;b[l]||(b[l]=
106 function(){(b[l].q=b[l].q||[]).push(arguments)});b[l].l=+new Date;
107 e=o.createElement(i);r=o.getElementsByTagName(i)[0];
e.src='//www.google-analytics.com/analytics.js';

r.parentNode.insertBefore(e,r)}(window,document,'script','ga'));
ga('create','UA-XXXXX-X','auto');ga('send','pageview');
</script>
</body>
</html>

Now replace the title HTML tag with the highlighted line:

7 <head>
8 <meta charset="utf-8">
9 <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
10 <title>Marcador - {% block title %}{% endblock %}</title>
11 <meta name="description" content="">
12 <meta name="viewport" content="width=device-width, initial-scale=1">

Here, the first (empty) block will be defined with the help of the block tag.

Delete the <div> HTML tag overwritten with <!-- Main jumbotron for a primary
marketing message or call to action --> including it’s contents. Here can see the lines
you have to delete:

<!-- Main jumbotron for a primary marketing message or call to action -->
<div class="jumbotron">
56
<div class="container">
57
<h1>Hello, world!</h1>
58
<p>This is a template for a simple marketing or informational website.
59
It includes a large callout called a jumbotron and three supporting pieces
60
of content. Use it as a starting point to create something more unique.</p>
61
<p><a class="btn btn-primary btn-lg" href="#" role="button">Learn more
62
&raquo;</a></p>
63
</div>
</div>

Below you will see another <div> HTML tag follow by <!-- Example row of columns -->.
Delete the HTML comment and the following HTML tag <div class="row"> including it’s
contents. Here can see the lines you have to delete:

66 <!-- Example row of columns -->


67 <div class="row">
68 <div class="col-md-4">
69 <h2>Heading</h2>
70 <p>Donec id elit non mi porta gravida at eget metus. Fusce dapibus,

32 | P a g e
Django-marcador 2017

71 tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum


72 massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod.
73 Donec sed odio dui. </p>
74 <p><a class="btn btn-default" href="#" role="button">View details
75 &raquo;</a></p>
76 </div>
77 <div class="col-md-4">
78 <h2>Heading</h2>
79 <p>Donec id elit non mi porta gravida at eget metus. Fusce dapibus,
80 tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum
81 massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod.
82 Donec sed odio dui. </p>
83 <p><a class="btn btn-default" href="#" role="button">View details
&raquo;</a></p>
</div>
<div class="col-md-4">
<h2>Heading</h2>
<p>Donec sed odio dui. Cras justo odio, dapibus ac facilisis in,
egestas eget quam. Vestibulum id ligula porta felis euismod semper. Fusce
dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut
fermentum massa justo sit amet risus.</p>
<p><a class="btn btn-default" href="#" role="button">View details
&raquo;</a></p>
</div>
</div>

Now add two additional blocks to the remaining <div> HTML tag; one for the headline and the
other for the contents of the current page:

<div class="container">
50 <div>
51 {% block heading %}{% endblock %}
52 </div>
53 <div>
54 {% block content %}{% endblock %}
55 </div>
56
57 <hr>
58
59 <footer>
60 <p>&copy; Company 2015</p>
61 </footer>
62 </div> <!-- /container -->
63 <script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js">
</script>

That’s it! The base template is done!

9.3. Create the templates for the views

Now the base template is finished. The next step is to create the missing templates for the views.
First create the appropriate directory structure:
33 | P a g e
Django-marcador 2017

$ mkdir -p marcador/templates/marcador

This structure is necessary so that the template loader can load the templates. After that the app
marcador has this structure:

marcador
|-- __init__.py
|-- admin.py
|-- migrations
| |-- 0001_initial.py
| `-- __init__.py
|-- models.py
|-- templates
| `-- marcador
|-- tests.py
|-- urls.py
`-- views.py

Note

If the development server was running while creating the new directories you have to restart it.
Otherwise the new directories wouldn’t be recognized.

Now create the templates inside the directory marcador/templates/marcador.

At first the template for the list view bookmark_list.html:

1 {% extends "base.html" %}
2
3 {% block title %}Latest bookmarks{% endblock %}
4
5 {% block heading %}
6 <h2>Latest bookmarks</h2>
7 {% endblock %}
8
9 {% block content %}
10 <ul class="list-unstyled">
11 {% for bookmark in bookmarks %}
12 <li class="well well-sm">{% include "marcador/bookmark.html" %}</li>
13 {% empty %}
14 <li>No bookmarks. :(</li>
15 {% endfor %}
16 </ul>
17 {% endblock %}

This template extends the base template base.html with the help of the extends tag. Therefor it’s
possible to extend the blocks defined inside the base template. Besides the already mentioned for
tag is used to iterate over the list of bookmarks (bookmarks) and create a list entry for each. The
template for the bookmark is included using the include tag.

34 | P a g e
Django-marcador 2017

Now you create the template bookmark.html which has been included in the previous template
using the include tag:

<a class="lead" href="{{ bookmark.url }}">{{ bookmark.title }}</a>


1
{% if bookmark.description %}
2
<br>{{ bookmark.description|linebreaksbr }}
3
{% endif %}
4
{% if not bookmark.is_public %}
5
<br><span class="label label-warning">private</span>
6
{% else %}
7
<br>
8
{% endif %}
9
{% if bookmark.tags.count %}
10
{% for tag in bookmark.tags.all %}
11
<span class="label label-primary">{{ tag|lower }}</span>&nbsp;
12
{% endfor %}
13
{% endif %}
14
<br>by <a href="{% url "marcador_bookmark_user" bookmark.owner.username
15
%}">
16
{{ bookmark.owner.username }}</a>
17
{{ bookmark.date_created|timesince }} ago

In this template a Bookmark object is available which is bound to the bookmark variable. In a
template it’s possible to access the attributes of an object, for example the title by using
bookmark.title. The attributes can be modified by filters, for example by using linebreaksbr
which converts all <br /> HTML tags to real line breaks.

It is also possible to access related objects, for example the tags. Because bookmark.tags is a list
of objects represented by a manager object you can access the Tag objects only with the help of
methods defined by the manager. bookmark.tags.count returns the number of tags.
bookmark.tags.all creates an iterator which can be used in a loop. To call these methods in
your Python code you have to put a pair of brackets at the end of the method
(bookmark.tags.count()). If the methods are called in a template no brackets are allowed
because you can’t pass arguments to a method in a template.

With the help of the url tag a link to the bookmark owners other bookmarks is created at the end
of the template. Because the URL marcador_bookmark_user needs a user name we need to pass
it as an argument to the template tag. That implies arguments can be passed to template tags.

At last the template for the user’s bookmarks must be created, bookmark_user.html:

1 {% extends "marcador/bookmark_list.html" %}
2
3 {% block title %}{{ owner.username }}'s bookmarks{% endblock %}
4
5 {% block heading %}
6 <h2>{{ owner.username }}'s bookmarks<br>
7 <small>{{ bookmarks.count }} bookmarks in total</small>
8 </h2>

35 | P a g e
Django-marcador 2017

9 {% endblock %}

This template extends the template bookmark_list.html because there are only very small
differences between them. We need only to overwrite the blocks title and heading and can
reuse the rest. Very convenient!

9.4. Test the new templates

After all templates have been created you can start the development server and have a look at the
bookmarks, which you have created using the admin, at the front end under
http://127.0.0.1:8000/.

The list of bookmarks should look like this:

36 | P a g e
Django-marcador 2017

The login form at the top of the page does not work yet. But you will finish it right in the next
chapter!

37 | P a g e
Django-marcador 2017

10. Setting Up Frontend Login


“Django comes with a user authentication system. It handles user accounts, groups, permissions
and cookie-based user sessions.”

—User authentication in Django | Django documentation

So far you can only create and edit bookmarks with the admin site. The next steps will show you
how to set up a login form in the frontend.

Django comes with an app named django.contrib.auth which includes everything necessary
to authenticate a user. The core element is the model User that you already used to store the
owner in the Bookmark model. It contains fields to store username, password and email. You can
find a full list of its fields in the documentation.

The authentication works this way:

If a user logs in, a cookie with a unique session id is set. The browser will send this cookie with
every request. With the session id the according user is mapped to the request and you can access
the User object with request.user like already done in the bookmark_user view.

We’ll create two views to allow users to login without using the admin site.

10.1. URLconf

First, you have to add the import of the reverse_lazy() function at the top and two additional
URLs to the end of URLconf mysite/urls.py.

1 """mysite URL Configuration


2
3 The `urlpatterns` list routes URLs to views. For more information please
4 see:
5 https://docs.djangoproject.com/en/1.8/topics/http/urls/
6 Examples:
7 Function views
8 1. Add an import: from my_app import views
9 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home')
10 Class-based views
11 1. Add an import: from other_app.views import Home
12 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home')
13 Including another URLconf
14 1. Add an import: from blog import urls as blog_urls
15 2. Add a URL to urlpatterns: url(r'^blog/', include(blog_urls))

38 | P a g e
Django-marcador 2017

16 """
17 from django.conf.urls import include, url
18 from django.contrib import admin
19 from django.core.urlresolvers import reverse_lazy
20
21 urlpatterns = [
22 url(r'^admin/', include(admin.site.urls)),
23 url(r'^', include('marcador.urls')),
24 url(r'^login/$', 'django.contrib.auth.views.login', {'template_name':
25 'login.html'},
26 name='mysite_login'),
27 url(r'^logout/$', 'django.contrib.auth.views.logout',
{'next_page': reverse_lazy('marcador_bookmark_list')},
name='mysite_logout'),
]

The function reverse_lazy() is similar to the url template tag you have used in the previous
chapter. It returns a link to the URL passed to it as the first argument, but can even be executed
before your project’s URLConf is loaded.

The URLs /login/ and /logout/ are linked here with the corresponding views. These views are
included in Django and don’t have to be created. You just have to configure the template for the
login view and the redirect of the logout view.

10.2. Configuration

Next, you create three new constants at the end of file settings.py:

110 LOGIN_URL = 'mysite_login'


111 LOGOUT_URL = 'mysite_logout'
112 LOGIN_REDIRECT_URL = 'marcador_bookmark_list'

These constants configure the automatic redirect after the login and the logout. For example if a
user who is not authenticated wants to access a protected page, she’ll be automatically redirected
to /login/ where she can log in because this is the URL where mysite_login refers to. The
constants should either be simple strings which match a named URL pattern from the URLconf
or URLs relative to the current domain.

Since this template uses some functionality provided by crispy_forms we will add it to
INSTALLED_APPS in the settings file:

31 # Application definition
32
33 INSTALLED_APPS = (
34 'django.contrib.admin',
35 'django.contrib.auth',
36 'django.contrib.contenttypes',
37 'django.contrib.sessions',

39 | P a g e
Django-marcador 2017

38 'django.contrib.messages',
39 'django.contrib.staticfiles',
40 'marcador',
41 'crispy_forms',
42 )

crispy_forms comes with a template pack for Bootstrap 3 (http://django-


marcador.keimlink.de/en/templates.html#bootstrap-3). As this is not the default template pack we
have to configure crispy_forms to use it by adding the following line to the end of the file
settings.py:

114 CRISPY_TEMPLATE_PACK = 'bootstrap3'

10.3. Templates

10.3.1. The main login template

Now you can create the template for the login form mysite/templates/login.html.

{% extends "base.html" %}
1
{% load crispy_forms_tags %}
2
3
{% block title %}Login{% endblock %}
4
5
{% block toggle_login %}{% endblock %}
6
7
{% block heading %}
8
<h2>Login</h2>
9
{% endblock %}
10
11
{% block content %}
12
<form action="{% url "mysite_login" %}" method="post" accept-
13
charset="utf-8">
14
{{ form|crispy }}
15
{% csrf_token %}
16
<input type="hidden" name="next" value="{{ next }}">
17
<input class="btn btn-default" type="submit" value="Login">
18
</form>
19
{% endblock %}

First we create a form in the block content. The <form> tag gets attributes to make it send a
POST request to the login view.

The template gets a variable form from the login view which contains the login form. With {{
form|crispy }} Django creates all necessary input fields. Of course you can also render all
fields separately. How to do that is described in the documentation.

40 | P a g e
Django-marcador 2017

The next row contains the template tag {% csrf_token %}. It is used to activate Django’s
protection against manipulated form requests (Cross Site Request Forgery).

After that comes a hidden input field which gets the value from the variable next. next contains
the path to which the user should be redirected after a successful login. Usually this is the value
of the constant LOGIN_REDIRECT_URL which you defined in settings.py. The default value is
replaced if you request the page with an additional query parameter. For example
/login/?next=/user/bob/ will redirect the user to the page with Bob’s bookmarks after the
login.

Finally we create a button to submit the form.

10.3.2. The login template for the navigation

Now comes the template for login through the navigation bar
mysite/templates/toggle_login.html:

The goal of this template is to show different content in the navigation bar, depending on the
users login state. If the user is logged in, she should see a link to the page with her bookmarks
and a button to log out. If she is not logged in, there should be input fields for username and
password as well as a login button.

{% if user.is_authenticated %}
<ul class="nav navbar-nav navbar-right">
1
<li class="dropdown">
2
<a href="#" class="dropdown-toggle" data-toggle="dropdown"
3
role="button"
4
aria-expanded="false">{{ user.username }} <span
5
class="caret"></span></a>
6
<ul class="dropdown-menu" role="menu">
7
<li><a href="{% url "marcador_bookmark_user" user.username %}">
8
My bookmarks</a></li>
9
<li><a href="{% url "mysite_logout" %}">Logout</a></li>
10
</ul>
11
</li>
12
</ul>
13
{% else %}
14
<form class="navbar-form navbar-right" role="form" action="{% url
15
"mysite_login" %}"
16
method="post" accept-charset="utf-8">
17
<div class="form-group">
18
<input type="text" placeholder="Username" class="form-control"
19
name="username">
20
</div>
21
<div class="form-group">
22
<input type="password" placeholder="Password" class="form-control"
23
name="password">
24
</div>
25
{% csrf_token %}
<button type="submit" class="btn btn-success">Sign in</button>
41 | P a g e
Django-marcador 2017

</form>
{% endif %}

The difference between these two states is done with help of the if tag and the condition
user.is_authenticated. The variable user refers to the already mentioned user object which is
available in all templates. The method is_authenticated returns True if the user is
authenticated or False if not.

Because this template gets included in base.html to display it on every page, the form can’t be
rendered automatically like in the last template. This is because only the login view provides the
variable form. Instead we set the input fields manually. Please note that the attribute name has to
be equal to the field name in the User model.

The template mysite/templates/base.html originally contains the following login form:

<form class="navbar-form navbar-right" role="form">


<div class="form-group">
<input type="text" placeholder="Email" class="form-control">
</div>
<div class="form-group">
<input type="password" placeholder="Password" class="form-control">
</div>
<button type="submit" class="btn btn-success">Sign in</button>
</form>

This gets replaced with a block in which you use the include tag to embed the template
mysite/templates/toggle_login.html.

{% block toggle_login %}
{% include "toggle_login.html" %}
{% endblock %}

10.4. Test the frontend login

Now users can log in and out in the frontend using the form and the dropdown menu at the top,
which are rendered using the mysite/templates/toggle_login.html template:

42 | P a g e
Django-marcador 2017

Call http://127.0.0.1:8000/user/admin/ to see the login form which you build at first in
mysite/templates/login.html (replace admin with the name of the user you created when
executing createsuperuser):

43 | P a g e
Django-marcador 2017

The main login form is also used if the authentication via the form at the top fails.

11. Forms
“Django provides a range of tools and libraries to help you build forms to accept input from site
visitors, and then process and respond to the input.”

—Working with forms | Django documentation

Now that the frontend login is working, we can build the form to create bookmarks.

11.1. Add URLs for the forms

First you have to extend the marcador app’s URLconf in mysite/marcador/urls.py by two
new URLs:

44 | P a g e
Django-marcador 2017

from django.conf.urls import url


1
2
3
urlpatterns = [
4
url(r'^user/(?P<username>[-\w]+)/$', 'marcador.views.bookmark_user',
5
name='marcador_bookmark_user'),
6
url(r'^create/$', 'marcador.views.bookmark_create',
7
name='marcador_bookmark_create'),
8
url(r'^edit/(?P<pk>\d+)/$', 'marcador.views.bookmark_edit',
9
name='marcador_bookmark_edit'),
10
url(r'^$', 'marcador.views.bookmark_list',
11
name='marcador_bookmark_list'),
12
]

The first one ^create/$ is completely static and binds the corresponding view to the path
/create/.

The pattern ^edit/(?<pk>\d+)/$ has a variable part which catches the primary key (PK) of a
bookmark. The PK is added to every model by Django, so every entry gets a unique identifier.
The path /edit/1/ for example leads to the bookmark with the PK 1.

Between the static parts edit/ at the beginning and / at the end stands the group (?P<pk>\d+).
With \d+ it catches any number of numerical characters and stores them in the variable pk which
can be used in the view.

11.2. Add the Form

The next step is the form. To create it, add the file mysite/marcador/forms.py:

1 from django.forms import ModelForm


2
3 from .models import Bookmark
4
5
6 class BookmarkForm(ModelForm):
7 class Meta:
8 model = Bookmark
9 exclude = ('date_created', 'date_updated', 'owner')

The form class BookmarkForm is also called a ModelForm because it inherits from
django.forms.ModelForm. ModelForms automatically create form fields for every field in the
model to which they belong. The linking of the models is done in the inner class Meta which
must be present in every ModelForm.

For security reasons the value owner mustn’t be editable through the form because otherwise it
would be possible to foist a bookmark on another user. Therefore owner is added to the list of
excluded fields. The same is done with the fields date_created and date_updated because they
are set automatically.
45 | P a g e
Django-marcador 2017

11.3. Add the Views

Now you can create two new views which make use of the BookmarkForm. The first one is used
to create new bookmarks:

1 from django.contrib.auth.decorators import login_required


2 from django.contrib.auth.models import User
3 from django.shortcuts import get_object_or_404, redirect, render
4
5 from .forms import BookmarkForm
6 from .models import Bookmark
7
8
9 def bookmark_list(request):
10 bookmarks = Bookmark.public.all()
11 context = {'bookmarks': bookmarks}
12 return render(request, 'marcador/bookmark_list.html', context)
13
14
15 def bookmark_user(request, username):
16 user = get_object_or_404(User, username=username)
17 if request.user == user:
18 bookmarks = user.bookmarks.all()
19 else:
20 bookmarks = Bookmark.public.filter(owner__username=username)
21 context = {'bookmarks': bookmarks, 'owner': user}
22 return render(request, 'marcador/bookmark_user.html', context)
23
24
25 @login_required
26 def bookmark_create(request):
27 if request.method == 'POST':
28 form = BookmarkForm(data=request.POST)
29 if form.is_valid():
30 bookmark = form.save(commit=False)
31 bookmark.owner = request.user
32 bookmark.save()
33 form.save_m2m()
34 return redirect('marcador_bookmark_user',
35 username=request.user.username)
36 else:
37 form = BookmarkForm()
38 context = {'form': form, 'create': True}
39 return render(request, 'marcador/form.html', context)

The decorator @login_required makes sure that the view is only accessible by authenticated
users. Other visitors are redirected to the login page.

The first step is to check if the request was done via POST. If yes, the form is initialized with the
POST data.

46 | P a g e
Django-marcador 2017

The form gets validated by using form.is_valid(). If the validation was successful, the new
bookmark can be saved with form.save(commit=False). The argument commit=False prevents
the ModelForm from saving the bookmark, it just returns the model instance prepared with the
validated data. Now the current user is added as the owner and the bookmark is saved. After that
form.save_m2m() is called to create the relationships to the Tag models. Finally the user gets
redirected to his or her bookmark list.

If the validation wasn’t successful or if the request was done via GET, the form is passed to the
template where it can be rendered including eventual error messages.

Two values are passed to the template as context variables. The dictionary key form contains the
instance of BookmarkForm. create is a boolean value and is used to determine if the template is
used to create or edit a bookmark because it is used in both views.

The second view allows editing of bookmarks:

1 from django.contrib.auth.decorators import login_required


2 from django.contrib.auth.models import User
3 from django.core.exceptions import PermissionDenied
4 from django.shortcuts import get_object_or_404, redirect, render
5
6 from .forms import BookmarkForm
7 from .models import Bookmark
8
9
10 def bookmark_list(request):
11 bookmarks = Bookmark.public.all()
12 context = {'bookmarks': bookmarks}
13 return render(request, 'marcador/bookmark_list.html', context)
14
15
16 def bookmark_user(request, username):
17 user = get_object_or_404(User, username=username)
18 if request.user == user:
19 bookmarks = user.bookmarks.all()
20 else:
21 bookmarks = Bookmark.public.filter(owner__username=username)
22 context = {'bookmarks': bookmarks, 'owner': user}
23 return render(request, 'marcador/bookmark_user.html', context)
24
25
26 @login_required
27 def bookmark_create(request):
28 if request.method == 'POST':
29 form = BookmarkForm(data=request.POST)
30 if form.is_valid():
31 bookmark = form.save(commit=False)
32 bookmark.owner = request.user
33 bookmark.save()
34 form.save_m2m()
35 return redirect('marcador_bookmark_user',

47 | P a g e
Django-marcador 2017

36 username=request.user.username)
37 else:
38 form = BookmarkForm()
39 context = {'form': form, 'create': True}
40 return render(request, 'marcador/form.html', context)
41
42
43 @login_required
44 def bookmark_edit(request, pk):
45 bookmark = get_object_or_404(Bookmark, pk=pk)
46 if bookmark.owner != request.user and not request.user.is_superuser:
47 raise PermissionDenied
48 if request.method == 'POST':
49 form = BookmarkForm(instance=bookmark, data=request.POST)
50 if form.is_valid():
51 form.save()
52 return redirect('marcador_bookmark_user',
53 username=request.user.username)
54 else:
55 form = BookmarkForm(instance=bookmark)
56 context = {'form': form, 'create': False}
57 return render(request, 'marcador/form.html', context)

Here the primary key pk is used to fetch the bookmark from the database. If no bookmark with
this PK exists, the HTTP status code 404 is returned. If the current user isn’t the owner
(bookmark.owner != request.user) and isn’t a superuser (not
request.user.is_superuser), access is denied and the HTTP status code 403 is returned.

Apart from that, the process is similar to bookmark_create. But the form is initialized with a
bookmark instance (instance=bookmark). This way all form fields are pre-filled with the old
values. If POST data is present, it overrides the values from the bookmark instance.

In addition the key create in the context dictionary is set to False because this view is only used
to edit existing bookmarks.

11.4. Add the Templates

Now you can add the template for the form


mysite/marcador/templates/marcador/form.html:

1 {% extends "base.html" %}
2 {% load crispy_forms_tags %}
3
4 {% block title %}
5 {% if create %}Create{% else %}Edit{% endif %} bookmark
6 {% endblock %}
7
8 {% block heading %}
9 <h2>
10 {% if create %}

48 | P a g e
Django-marcador 2017

11 Create bookmark
12 {% else %}
13 Edit bookmark
14 {% endif %}
15 </h2>
16 {% endblock %}
17
18 {% block content %}
19 {% if create %}
20 {% url "marcador_bookmark_create" as action_url %}
21 {% else %}
22 {% url "marcador_bookmark_edit" pk=form.instance.pk as action_url %}
23 {% endif %}
24 <form action="{{ action_url }}" method="post" accept-charset="utf-8">
25 {{ form|crispy }}
26 {% csrf_token %}
27 <p><input type="submit" class="btn btn-default" value="Save"></p>
28 </form>
29 {% endblock %}

The variable create is used in the title and heading blocks to display different text depending
on whether the template is used to create or edit a bookmark. The URL for the form’s action
attribute is also set according to the value of create and assigned to action_url. Note that the
current bookmark fetched from the database has been assigned to form.instance and can be
used to create the edit URL for this bookmark.

Now you can add a link to create new bookmarks to the file
mysite/templates/toggle_login.html. The corresponding line is highlighted:

{% if user.is_authenticated %}
<ul class="nav navbar-nav navbar-right">
1
<li><a href="{% url "marcador_bookmark_create" %}">Create
2
bookmark</a></li>
3
<li class="dropdown">
4
<a href="#" class="dropdown-toggle" data-toggle="dropdown"
5
role="button"
6
aria-expanded="false">{{ user.username }} <span
7
class="caret"></span></a>
8
<ul class="dropdown-menu" role="menu">
9
<li><a href="{% url "marcador_bookmark_user" user.username %}">
10
My bookmarks</a></li>
11
<li><a href="{% url "mysite_logout" %}">Logout</a></li>
12
</ul>
13
</li>
14
</ul>
{% else %}

The last step is to extend the template


mysite/marcador/templates/marcador/bookmark.html at the end with the following lines:

15 <br>by <a href="{% url "marcador_bookmark_user" bookmark.owner.username

49 | P a g e
Django-marcador 2017

16 %}">
17 {{ bookmark.owner.username }}</a>
18 {{ bookmark.date_created|timesince }} ago
19 {% if bookmark.owner == user or user.is_superuser %}
20 <br><a class="btn btn-default btn-xs" role="button"
21 href="{% url "marcador_bookmark_edit" bookmark.pk %}">Edit
bookmark</a>
{% endif %}

This way authenticated users see an edit button bellow their own bookmarks.

11.5. Test the form

Now load the main page and click on the link to create a new bookmark. You should see this
form:

It displays all fields we have configured in the form. Tags can’t be added, but you can build a
second form to do this.
50 | P a g e
Django-marcador 2017

If you click on one the links to edit a bookmark you will see this edit form:

12. Advanced Exercises


Here are some advanced exercises for everyone who still hasn’t enough of Django and wants to
see what else we can do with our bookmarking app.

• 12.1. Adding a tag cloud

12.1. Adding a tag cloud


Django does not only ship some built-in tags for the template engine but also allows you to
extend it with custom tags. In this exercise we will write our own template tag to display a tag
cloud on every page we want. The tag will be able to either display a tag cloud for all tags or just

51 | P a g e
Django-marcador 2017

for one user depending on whether we provide an extra argument or not. This short example
shows the different ways the new tag can be used:

1 <!-- Tag cloud for all tags -->


2 {% tagcloud %}
3
4 <!-- Tag cloud for a single user (several variants) -->
5 {% tagcloud owner %}
6 {% tagcloud owner=owner %}
7 {% tagcloud owner=some_other_user %}

12.1.1. Writing custom template tags

Before we can write our template tag we have to prepare a special file structure so Django can
find and load our template tag. So create the templatetags directory inside the marcador
directory and inside of the new directory create the two Python files:

mysite
`-- marcador
`-- templatetags
|-- __init__.py
`-- marcador_tags.py

From now on we are able to load our tags in the template via {% load marcador_tags %}. Let’s
add our first tag:

1 from django import template


2 from django.core.urlresolvers import reverse
3 from django.db import models
4 from django.utils.html import format_html_join
5
6 from ..models import Tag
7
8
9 register = template.Library()
10
11
12 @register.simple_tag(takes_context=True)
13 def tagcloud(context, owner=None):
14 url = reverse('marcador_bookmark_list')
15 filters = {'bookmark__is_public': True}
16
17 if owner is not None:
18 url = reverse('marcador_bookmark_user',
19 kwargs={'username': owner.username})
20 filters['bookmark__owner'] = owner
21 if context['user'] == owner:
22 del filters['bookmark__is_public']
23
52 | P a g e
Django-marcador 2017

24 tags = Tag.objects.filter(**filters)
25 tags = tags.annotate(count=models.Count('bookmark'))
26 tags = tags.order_by('name').values_list('name', 'count')
27 fmt = '<a href="%s?tag={0}">{0} ({1})</a>' % url
28 return format_html_join(', ', fmt, tags)

We start by initializing a Library class, where we can register our tags. Django provides a few
shortcuts for writing simple tags without having to dig to deep into the template engine.
simple_tag is such a shortcut which allows us to write a simple function which the template
system will call and then put the output into the template.

When we register our function via the simple_tag-decorator, Django will inspect the function
definition and mark tag arguments as required or optional depending on whether they have a
default value or not. In our case this means that the tag will support a single argument (ignore
context for now) named owner which is optional.

Since we want to be able to show tags of private bookmarks to the owner, we would have to add
another argument to the tag. Luckily Django provides an easier way to access all variables in the
template; this is the special context argument in our function, which only exists if we register
the tag with takes_context=True. By default, Django has a few special variables in every
template, like user for the currently authenticated user. Those variables never change and make a
perfect candidate to be accessed via the global context instead of a custom argument to our tag.

So what does the simple tag tagcloud actually do? At first the URL to the main page is
generated. We will use this URL to show just a single tag instead of the full list. A dictionary
filters is defined which will later be used to query the database and select only public
bookmarks. If the argument owner has been set, the content of url is replaced with an URL
pointing to the bookmarks of the user represented by owner. Also a new key is added to filters
to select only the bookmarks of the user owner. If owner is the currently logged in user we
remove the filter to show only private bookmarks because the user is looking at her own
bookmarks.

Now the database query has to be build: The first step is to use the dictionary filters to filter all
bookmarks so that the resulting QuerySet only contains the ones we need. Then we use the
annotate method to add the number of bookmarks to each tag. Finally we order the tags by name
and convert the QuerySet into a list of tuples using the values_list method.

The last step is to render the actual HTML that the template tag will insert into the page.
Therefore we define a string fmt which uses the url we set above. This string uses Python`s
Format String Syntax to be able to add query string, tag name and bookmarks count dynamically.
Now we can use the format_html_join helper function to render the HTML for all tags, separated
by a comma using the fmt string.

53 | P a g e
Django-marcador 2017

Currently the views used to render the list of bookmarks and the bookmarks of a user are not
capable of using the query string to filter the results. But it’s easy to change that! Add the
following two lines to each view:

10 def bookmark_list(request):
11 bookmarks = Bookmark.public.all()
12 if request.GET.get('tag'):
13 bookmarks = bookmarks.filter(tags__name=request.GET['tag'])
14 context = {'bookmarks': bookmarks}
15 return render(request, 'marcador/bookmark_list.html', context)
16
17
18 def bookmark_user(request, username):
19 user = get_object_or_404(User, username=username)
20 if request.user == user:
21 bookmarks = user.bookmarks.all()
22 else:
23 bookmarks = Bookmark.public.filter(owner__username=username)
24 if request.GET.get('tag'):
25 bookmarks = bookmarks.filter(tags__name=request.GET['tag'])
26 context = {'bookmarks': bookmarks, 'owner': user}
27 return render(request, 'marcador/bookmark_user.html', context)

The two new lines add a new filter to the bookmarks QuerySet if tag is present in the query
string. Using the get method on the dictionary-like request.GET object prevents a KeyError to
be raised if tag is not present in the query string.

To show the tag cloud in our template we first load the tags in templates/base.html:

1 {% load staticfiles marcador_tags %}


2 <!doctype html>

and change the layout to include a sidebar with the tags:

50 <div class="container">
51 <div class="row">
52 <div class="col-md-12">
53 {% block heading %}{% endblock %}
54 </div>
55 </div>
56 <div class="row">
57 <div class="col-md-8">
58 {% block content %}{% endblock %}
59 </div>
60 <div class="col-md-4">
61 <div class="panel panel-primary">
62 <div class="panel-heading">Tag cloud</div>
63 <div class="panel-body">
64 {% block sidebar %}
65 {% tagcloud %}
66 {% endblock %}

54 | P a g e
Django-marcador 2017

67 </div>
68 </div>
69 </div>
70 </div>
71
72 <hr>
73
74 <footer>
75 <p>&copy; Company 2015</p>
76 </footer>
77 </div> <!-- /container --> <script
src="//ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>

Finally we also modify the user bookmark template


(marcador/templates/marcador/bookmark_user.html) to only show the tags for the
requested user:

1 {% extends "marcador/bookmark_list.html" %}
2 {% load marcador_tags %}
3
4 {% block title %}{{ owner.username }}'s bookmarks{% endblock %}
5
6 {% block heading %}
7 <h2>{{ owner.username }}'s bookmarks<br>
8 <small>{{ bookmarks.count }} bookmarks in total</small>
9 </h2>
10 {% endblock %}
11
12 {% block sidebar %}
13 {% tagcloud owner %}
14 {% endblock %}

That’s it! Try out the new tag cloud in your frontend - it should looks like this. If you click on
the tags the list should be filtered to display only bookmarks with the selected tag:

55 | P a g e
Django-marcador 2017

56 | P a g e
Django-marcador 2017

13. Further Information


This is the end of this tutorial. After catching a brief glimpse of Django you certainly want to
know where you can find more information.

Your first stop should always be the excellent Django Online Documentation as well as the
Django Offline PDF Documentation (http://media.readthedocs.org/pdf/django/1.11.x/django.pdf)
which has currently more than 1,500 pages. Also check out the Django FAQ.

The Django cheat sheet (https://www.revsys.com/django/cheatsheet/) by Revolution Systems


makes your daily work as Django developer much more easier.

To get help you can choose between several options:

• django-users - the main mailing list for help and announcements


• #django IRC channel on freenode
• ‘django’ questions on Stack Overflow (consider reading How do I ask a good question?
before asking your first question)

Other useful resources are:

• Django Source Code (https://github.com/django/django)


• Django Issue Tracker (https://code.djangoproject.com/query)
• Django Packages - (https://djangopackages.org/) to find reusable Django apps
• Django snippets - (https://djangosnippets.org/) a huge collection of useful Django code
snippets
• Django Class-Based-View Inspector - (http://ccbv.co.uk/) helpful to understand Django’s
Class-based views
• Django subreddit - (https://www.reddit.com/r/django/) reddit is a community news
website
• List of Python User Groups - (https://wiki.python.org/moin/LocalUserGroups) meet other
Python users and share knowledge in one of the more than 400 Python user groups
worldwide

Please respect the Django Code of Conduct when using spaces managed by the Django project or
Django Software Foundation like IRC, the mailing lists or the issue tracker.

14. Changelog

57 | P a g e
Django-marcador 2017

1.5.0 2015‐05‐31

• [Support] #95: Switch to new Read the Docs Sphinx Theme


• [Feature] #94: Add socialshareprivacy for social share buttons
• [Support] #92: Use new pip bootstrap URL
• [Support] #84: Use static template tag from staticfiles
• [Support] #86: overriding __init__() and save() on BookmarkForm seems unnecessary
• [Support] #102: Upgrade tutorial to Django 1.8
• [Support] #45: Create screenshots using CasperJS
• [Support] #8: Allow Disqus comments (Thanks Max Brauer)
• [Support] #30: Add screenshots of application and admin

1.4.1 2014‐03‐17

• [Bug] #89: Install instructions show still Django 1.5.4

1.4.0 2014‐03‐16

• [Support] #63: Switch to setuptools (Thanks Florian Apolloner)


• [Bug] #61: Update initializr.com template (Thanks Florian Apolloner)
• [Support] #55: Add fixtures for testing and demonstration (Thanks Max Brauer)
• [Support] #47: Explain what QuerySets are (Thanks Andreas Hug)
• [Support] #46: Explain what Unicode strings are
• [Bug] #49: Prepare for Python 3
• [Support] #15: Add a note on virtualenv and –user to the installation section
• [Bug] #79: Use full path under Windows for calling django-admin.py
• [Support] #77: Describe what the body of the tagcloud does
• [Support] #78: Describe what the request.GET attribute is used for
• [Feature] #80: Add a Makefile target to run the Django test suite
• [Feature] #82: Setup Tox (Thanks Andreas Hug)
• [Feature] #81: Make the tutorial work with Python 3 (Thanks Andreas Hug)
• [Support] #83: Upgraded to Creative Commons 4.0
• [Support] #85: Removed BookmarkForm.__init__() to simplify code
• [Support] #87: pip will install setuptools, if setuptools are not already installed (Thanks
Andreas Hug)
• [Support] #88: Pin Django version (Thanks Andreas Hug)

1.3.1 2013‐10‐11

• [Bug] #72: German article to “View” should be feminine (Thanks Markus Holtermann)
• [Bug] #73: Untranslated sentence in “Install Django” chapter
• [Bug] #74: Missing German translation in “Weiterführende Informationen” (Thanks
Markus Holtermann)
• [Bug] #75: Several parts of the Spanish tutorial are not translated
58 | P a g e
Django-marcador 2017

1.3.0 2013‐10‐09

• [Support]: Thanks to Ernesto Rico-Schmidt for taking the time to do the Spanish
translation!
• [Support]: Updated introduction
• [Feature] #59: Using named urls for LOGIN_URL and LOGOUT_URL
• [Support]: Explained translation workflow
• [Feature]: Added Spanish translation
• [Feature]: Added Librelist mailing list
• [Support]: Redesigned landing page
• [Feature] #56: Added an ERM for the models
• [Feature] #51: Added languages to sidebar
• [Support] #41: Explained the name “marcador”
• [Support] #58: Added note explaining BASE_DIR should be at the top of settings.py
• [Support] #35: Switched from wget to curl and explained manual download of bootstrap
scripts
• [Bug] #42: Revised instructions to edit settings.py
• [Bug]: Translated all index words to English (until the index can be translated)
• [Feature] #31: Added a “Useful Links” sidebar section
• [Bug] #43: Explained which username is used with marcador_bookmark_user url
• [Feature] #53: Capturing as much output as possible using sphinxcontrib-programoutput
• [Feature] #39: Added a page with further information at the end
• [Bug] #68: Typo on “URLs and Views”
• [Bug] #70: Double “the” on “URLs and Views”

1.2.2 2013‐06‐15

• [Bug] #50: Removed fuzzy markers in translation to enable already translated strings

1.2.1 2013‐05‐19

• [Bug] #44: Fixed broken links in English version

1.2.0 2013‐04‐20

• [Support]: Thanks to Andreas Hug, Dave Brotherstone, Dinu Gherman and Sean
Hammond for making the English translation possible!
• [Support]: Finished English translation

1.1.1 2013‐03‐17

• [Bug]: Removed obsolete include tag arguments

59 | P a g e
Django-marcador 2017

1.1.0 2013‐03‐16

• [Support]: Thanks to Andreas Hug for contributing to this release


• [Bug]: Removed obsolete line from URLconf in admin site chapter
• [Support]: Added link to issue tracker on front page
• [Support]: Removed Class Based View
• [Support] #1: Better explanation of Models, Admin, database, URLconf and Views
• [Support]: Added bookmark_ prefix to view names
• [Support] #9: Made Bookmark.tags optional
• [Feature]: Improved CSS, show edit links only for owner, added link to create bookmarks
• [Support]: Added note regarding HTML5 Boilerplate download
• [Feature]: Added new block heading to simplify template inheritance
• [Support]: Improved users bookmark list
• [Support]: Block title is now just a suffix of the page title
• [Bug]: Create/edit form uses appropriate title
• [Support]: Using redirect() shortcut instead of HttpResponseRedirect
• [Support]: Converted tags from labels to badges
• [Feature]: Added badge for private bookmarks
• [Support]: Improved frontend drop down menu
• [Support] #6: Added explanation of ModelForm usage
• [Support] #3: Added explanation of the template system
• [Support]: Upgraded to Django 1.5
• [Feature]: Added a “How to contribute” section
• [Feature]: Added tasks to prepare, update, build and deploy translations
• [Support]: Replaced STATIC_URL with static template tag
• [Support]: Massive improvements of the URLs, Views, Templates and Forms pages
• [Bug]: Install pip only using the bootstrap script as recommended
• [Bug]: Explained how to gain root permissions for installation

1.0.0 2012‐10‐31

• [Support]: Initial Release

15. Authors
15.1. Markus Zapke‐Gründemann

Markus Zapke-Gründemann (http://www.keimlink.de/) looks back on over twelve years of


experience in software development and works for more than five years as self-employed
software developer, consultant and trainer. His focus is on developing web applications with
Django and integration with Mercurial. He is the owner of transcode (http://www.transcode.de/),
a company offering Python and Django software development and training.

60 | P a g e
Django-marcador 2017

15.2. Additional Authors

Many thanks for supporting this project go to:

• Andreas Hug
• Dave Brotherstone
• Dinu Gherman
• Ernesto Rico-Schmidt
• Florian Apolloner
• Markus Holtermann
• Max Brauer
• Raphael Ackermann
• Sean Hammond

16. License
This work is licensed under the Creative Commons Attribution-ShareAlike 4.0 International
License. To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/4.0/ or
send a letter to Creative Commons, 444 Castro Street, Suite 900, Mountain View, California,
94041, USA.

The authors page has a list of all authors and contributors.

http://www.keimlink.de/

http://www.transcode.de/

http://django-marcador.keimlink.de/en/index.html

---------------------------------------------------------------------------------------------------------------

Heroku is free. It uses deployment via git, so you will also learn that in the process.

I suggest setting up an account at Github - also free for public repositories - and following the
tutorial to setup the repository for your app. Then learn how to deploy that repo to Heroku.

The documentation of both sites is fantastic.

----------------------------------------------------------------------------------------------------------------

https://www.pythonanywhere.com/

61 | P a g e
Django-marcador 2017

https://www.heroku.com/pricing

62 | P a g e

S-ar putea să vă placă și