10. Beyond simple output

Robert Crowther Feb 2022

PrevNext

Depends what you want to do.

Modified information

Sometimes you want to modify the information provided by a view. For example, you want to change the query or produce a list of items sorted in particular order.

Django view code can be a difficult subject. Django has some basic response methods builtin. After that Django moves on to class‐based view‐code with a very deep inheritance stack. The classes used contain tricky code. The result is usually clean and tidy, but between the brief documentation, the lack of third‐party documentation, and the bristling, tricky classes, it can be difficult to see what you need to do to achieve an effect. If you do not work with Django as a day‐job, you may need to do much work and meet much trouble. You may need to go to source code, and even that is not easy, due to the deep inheritance and tricks like meta‐programming and decorators.

An example. You have a list view, and you wish to order it. The listView class breaks out the ordering step and exposes it in the API. Once you have found that, your code can be clean as,

class ReviewListView(ListView):
    context_object_name = 'review'
    model = Review

    def get_ordering(self):
        return "upload_date"

That said, you can achieve the same effect, with potential for object selection also, by overriding the get_queryset() method,

class ReviewListView(ListView):
    context_object_name = 'review'
    model = Review


    def get_queryset(self):
        #NB complete override of View handling, no call to super()
        return Review.objects.order_by("price")

Reacting to URLs

So far, we have used Djano generic views to react to URLs. They are doing much work. They get given a model, they check if part of the URL refers to an instance, retrieve the instance then send it to the template. If nothing matches, they return an error page.

What if we need some kind of URL reaction—view code—that is more crude, or refined?

Accessing part of a path element

So you want the grab the last part of these paths in a view, then look at it,

/sitename/lights/red
/sitename/lights/blue

The angle bracketed elements in the following URL paths are called ‘path converters’. The format is ‘<type:name>’. I can’t be bothered listing them, if this interests you, go look at Django documentation.

Simple View

I’m adding defaults so you see how that is done. If you don’t need defaults, delete the appropriate code. urls.py looks like,

    path('lights/<str:color>/',  lights.views.detail),

    # default for no expressed color
    path('lights/', lights.views.detail),

A simple view passes the parameter through (in working code, you are more likely to use a TemplateResponse),

from django.http import HttpResponse

# default on color parameter is 'red'
def detail(request, color='red'):
    return HttpResponse("<p>"+ color + "</p>")

Generic View

urls.py looks like,

    path('lights/<str:color>/', LightsDetailView.as_view()),

    # default for no expressed color
    path('lights/', LightsDetailView.as_view()),

Bewilderingly, in a generic view, the parameter becomes loaded as an attribute named ‘kwargs’,

class LightsDetailView(DetailView):
    model = Lights

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        print(self.kwargs['color'])
        return context

Accessing the query of a path

So you want the grab the query parts of these paths in a view, then look at them? Note the wacko format here—for consistency, Django always assumes a trailing ‘/’. Except, no trailing ‘/’ is needed for the query elements. So,

/sitename/lights/?color=red
/sitename/lights/?color=blue

Simple View

Right, well, Django handles query parameters automatically, so you should never try to match them. So urls.py looks like,

    # default (no query) is also matched
    path('lights/',  lights.views.detail),

The query data is passed in the request,

from django.http import HttpResponse

# default on color parameter is 'red'
def detail(request):
    color = request.GET.get('color', 'red')
    return HttpResponse("<p>"+ color + "</p>")

Generic View

urls.py looks like,

    # default (no query) is also matched
    path('lights/', LightsDetailView.as_view()),

Again, the query data is passed in the request,

class LightsDetailView(DetailView):
    model = Lights

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        color = self.request.GET.get('color', 'red')
        print(color)
        return context

About request.GET

It’s a funny immutable object, like a dict. If you use a simple query with no value,

/sitename/lights/?blue

You’ll get a dict‐like object,

{'blue': ''}

You can test for key existence like this,

if ('type' in request.GET):
    ...

And, yes, there is a similar request.POST object.

Reacting to a query

I don’t think this guide is the place to dig in, but… starter notes. The methods on an ‘objects’ method on a model can accept a view’s keywords. Which is an ear‐bending sentence so, an example, you can write this in a view,

Reviews.objects.filter(**kwargs)

If you track back to what the kwargs are, they are matched in the URL, so they are a pre‐filtered, pre‐formed set of terms. You can, of course, do more with them than this.

You may need to do more. For example, let’s say you had a model field with a list of countries (for whatever reasons, product source origin etc.). For tidiness, you encoded the countries using an enumerated field of ISO codes e.g. ‘England’ is stored as ‘EN’,

    class Country(models.TextChoices):
        ...
        ENGLAND = 'EN', _('England')
        ...

But you want users to be able to search for an ‘england’ match? Can you recover those results from the human title ‘england’? As code, not easy. Best I have come up with—assume the enumeration keys are a precise match to the human codes. Normalise the input to upper‐case, then do a select on the enumeration class (will succeed or fail, depending if the key exists). Then use the resulting type as the filter match,

normalised_slug = kwargs['slug'].upper()
qs = Review.objects.filter(country=Review.Country[normalised_slug])

More normalisation code needed if keys have spaces, like ‘Sierra Leone’. Tangle‐code, used only to express “a caseless match on the country field”. Anyway, there’s an example of how enabling a search, even a simple match, can need thought.

Refs

Python documentation of RequestResponse,

https://docs.djangoproject.com/en/3.2/ref/request-response/

Python documentation of Querydict,

https://docs.djangoproject.com/en/3.2/ref/request-response/#querydict-objects

Extra information

Perhaps you need information from other parts of the database, such as categorisations (for breadcrumbs?)? In which case you need to boost the information going into the template from the view.

To do this, you need to override a view method and call super(). Then you can attach new data to the given context. Bear in mind you can tag in nearly any data. This can be data imported from anywhere, filtered by whatever method you choose. Filtering can be by data that comes specifically from the given URL, which passes through the kwargs. Like this,

class ReviewDetailView(DetailView):
    model = Review

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)

        # Global data
        context['product_type_titles'] = Review.product_type_titles

        # Data filtered to be specific to this URL
        ??? example of filtered data
        return context

Here’s typical kwargs data,

{'object': <Review: Peterson DustBuster>}

Refs

About halfway down, ‘Adding extra context’,

https://docs.djangoproject.com/en/3.2/topics/class-based-views/generic-display/

Output formatting and template tags

Sometimes you may want to do more with the output data, especially how it is rendered. For example, perhaps you would like a time to be rendered on an image of a clock? Or a country code to be rendered as a flag? Or delve some further information from the information provided, such as a wordcount on text content? A kind of internationalization can be done using tags, on the basis that depending on the user, a Spanish text has been requested, not Danish…

Refs

Builtin tags. The most evident of these are the structuring tags like ‘for’ and ’if’. But there are also tags to, for example, turn a boolean into the text ‘yes’/‘no’,

https://docs.djangoproject.com/en/3.2/ref/templates/builtins/#cycle

If you want to do something clever with images, or your own formatting, you can write your own tags,

https://docs.djangoproject.com/en/3.2/howto/custom-template-tags/

CSS and JavaScript

CSS is the classic way of modifying the look of a webpage. Mostly, unlike tags, CSS can’t modify the content of what is rendered. But it can transform the look, and is reusable and portable.

As for JS (JavaScript), I suppose most people would say it makes all things possible, Javascript is at it’s most intricate in sourcing new information from the web, or pushing given information to a server.

CDN or not

Or, Content Delivery Network. What I mean is, you can download large, generic lumps of code through the web, not by delivering them from a website server. This is not the place to fully explore CDN, but I’ll note that for stock CSS and JS components you may wish to use a CDN. A CDN is likely about twice the speed of your Django instance server, will not cost anything (not for stock CSS and JS), and is probably more reliably served across multiple points of delivery. That said, for CSS and JS, serving may only be a small part of serve times (images far outweigh minimal CSS serves) and CSS/JS files delivered through the server can be tuned to your own wishes. So there are arguments both ways.

Add local CSS/JS

To include the base code in a webpage (and ignoring the complexities of Javascript triggering and effect), you need the files of code. Place these files in two folders ‘sitename/static/css’ and/or ‘sitename/static/css’,

mkdir -p sitename/static/css
mkdir -p sitename/static/js

Add links to CSS/JS in templates like this. Note assuming ‘static’, see previous steps,

{% load static %}
    ...
    <link rel="stylesheet" type="text/css" href="{% static 'css/soundsense.css' %}">
    <script src="{% static 'js/jquery-3.3.1.min.js' %}"></script>