****************** 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 :djangodocs:`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 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: .. code-block:: html+django :linenos: {% tagcloud %} {% tagcloud owner %} {% tagcloud owner=owner %} {% tagcloud owner=some_other_user %} 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 :file:`templatetags` directory inside the :file:`marcador` directory and inside of the new directory create the two Python files: :: mysite `-- marcador `-- templatetags |-- __init__.py `-- marcador_tags.py .. raw:: latex \newpage From now on we are able to load our tags in the template via ``{% load marcador_tags %}``. Let's add our first tag: .. literalinclude:: ../../src/mysite-tagcloud/marcador/templatetags/marcador_tags.py :linenos: 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 :djangodocs:`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 :djangodocs:`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 :pythondocs:`Format String Syntax ` to be able to add `query string `_, tag name and bookmarks count dynamically. Now we can use the :djangodocs:`format_html_join ` helper function to render the HTML for all tags, separated by a comma using the ``fmt`` string. 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: .. literalinclude:: ../../src/mysite-tagcloud/marcador/views.py :lines: 10-27 :emphasize-lines: 3-4, 15-16 :lineno-start: 10 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 :file:`templates/base.html`: .. literalinclude:: ../../src/mysite-tagcloud/templates/base.html :language: html+django :lines: 1-2 :emphasize-lines: 1 :linenos: and change the layout to include a sidebar with the tags: .. literalinclude:: ../../src/mysite-tagcloud/templates/base.html :language: html+django :lines: 50-77 :emphasize-lines: 2-3, 5, 7-8, 10-20 :lineno-start: 50 Finally we also modify the user bookmark template (:file:`marcador/templates/marcador/bookmark_user.html`) to only show the tags for the requested user: .. literalinclude:: ../../src/mysite-tagcloud/marcador/templates/marcador/bookmark_user.html :language: html+django :emphasize-lines: 2, 12-14 :linenos: .. raw:: latex \newpage 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: .. image:: /images/tagcloud/marcador-latest-bookmarks.* :alt: Frontend Bookmark List View with Tag Cloud :align: center