10. Complex View information

Robert Crowther Mar 2022
Last Modified: Feb 2023

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 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. 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")

For definitive answers, you’ll need to grit your teeth and dig into source.

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. You can tag in near any data. The data can be imported from anywhere, filtered by whatever method you choose, formatted however you wish. Filtering can be by other data that comes from the URL. URL data arrives in the kwargs. In a Generic View, the object also is available in 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/

Reacting to URLs

So far, we have used Django generic views to react to URLs. The views 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, here are some example paths, and you want the grab the last part of these paths, then look at it? You have URLs like,

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

How to get information information from the URL about color?

Simple View

This follows the two example URLs above exactly. In urls.py, use URL paths with ‘path converters’. The format is ‘<type:name>’. I can’t be bothered listing path converters—there’s a handful of them—if this interests you, go look at Django documentation. I added defaults to the paths 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 matched path section as a parameter (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

But the above isn’t the sophisticated way to do this. In truth, I find nothing wrong with a URL path match—it’s basic URL work. But assumes a defined enumeration of colors. It may be that the web‐user is submitting any of a wide range of color and, probably more important, the web‐app is intended to respond in some way to any color (even if it rejects it). The URL form for that is different,

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

The paths after the question marks are called a ‘query’. Note: no end slashes. Not needed.

Simple View

Right, well, every major web‐handling code has a way of dealing with queries. Java has great lumps of classes called ‘HTTPRequest’ and ‘HTTPResponse’, and query data is attached to them. So, I recall, though you never see them, has PHP. Django has too. This is kind of important—Django gathers query parameters automatically, so you should never try to match them. URL paths for a query look like,

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

The query data is passed in the request. It’s on an sub‐object of the request called GET (yes, there’s also a sub‐object called POST),

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

Let’s disregard questions, drive forwards. For a Generic View the URL path 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 anyhow,

{'blue': ''}

You can test for key existence,

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

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

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, to respond to a query URL 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