Let's take a look at some common design patterns seen in designing views.
Problem: Pages need to be conditionally accessible based on whether the user was logged in, is a member of staff, or any other condition.
Solution: Use mixins or decorators to control access to the view.
Most websites have pages that can be accessed only if you are logged in. Certain other pages are accessible to anonymous or public visitors. If an anonymous visitor tries to access a page, which needs a logged-in user, they could be routed to the login page. Ideally, after logging in, they should be routed back to the page they wished to see in the first place.
Similarly, there are pages that can only be seen by certain groups of users. For example, Django's admin interface is only accessible to the staff. If a non-staff user tries to access the admin pages, they would be routed to the login page.
Finally, there are pages that grant access only if certain conditions are met. For example, the ability to edit a post should be only accessible to the creator of the post. Anyone else accessing this page should see a Permission Denied error.
There are two ways to control access to a view:
@login_required(MyView.as_view())
dispatch
method of a class-based view through a mixin:from django.utils.decorators import method_decorator class LoginRequiredMixin: @method_decorator(login_required) def dispatch(self, request, *args, **kwargs): return super().dispatch(request, *args, **kwargs)
We really don't need the decorator here. The more explicit form recommended is as follows:
class LoginRequiredMixin: def dispatch(self, request, *args, **kwargs): if not request.user.is_authenticated(): raise PermissionDenied return super().dispatch(request, *args, **kwargs)
When the PermissionDenied
exception is raised, Django shows the 403.html
template in your root directory or, in its absence, a standard "403 Forbidden" page.
Of course, you would need a more robust and customizable set of mixins for real projects. The django-braces
package (https://github.com/brack3t/django-braces) has an excellent set of mixins, especially for controlling access to views.
Here are examples of using them to control access to the logged-in and anonymous views:
from braces.views import LoginRequiredMixin, AnonymousRequiredMixin class UserProfileView(LoginRequiredMixin, DetailView): # This view will be seen only if you are logged-in pass class LoginFormView(AnonymousRequiredMixin, FormView): # This view will NOT be seen if you are loggedin authenticated_redirect_url = "/feed"
Staff members in Django are users with the is_staff
flag set in the user model. Again, you can use a django-braces mixin called UserPassesTestMixin
, as follows:
from braces.views import UserPassesTestMixin class SomeStaffView(UserPassesTestMixin, TemplateView): def test_func(self, user): return user.is_staff
You can also create mixins to perform specific checks, such as if the object is being edited by its author or not (by comparing it with the logged-in user):
class CheckOwnerMixin: # To be used with classes derived from SingleObjectMixin def get_object(self, queryset=None): obj = super().get_object(queryset) if not obj.owner == self.request.user: raise PermissionDenied return obj
Problem: Several views based on generic views need the same context variable.
Solution: Create a mixin that sets the shared context variable.
Django templates can only show variables that are present in its context dictionary. However, sites need the same information in several pages. For instance, a sidebar showing the recent posts in your feed might be needed in several views.
However, if we use a generic class-based view, we would typically have a limited set of context variables related to a specific model. Setting the same context variable in each view is not DRY.
Most generic class-based views are derived from ContextMixin
. It provides the get_context_data
method, which most classes override, to add their own context variables. While overriding this method, as a best practice, you will need to call get_context_data
of the superclass first and then add or override your context variables.
We can abstract this in the form of a mixin, as we have seen before:
class FeedMixin(object): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context["feed"] = models.Post.objects.viewable_posts(self.request.user) return context
We can add this mixin to our views and use the added context variables in our templates. Notice that we are using the model manager defined in Chapter 3, Models, to filter the posts.
A more general solution is to use StaticContextMixin
from django-braces
for static-context variables. For example, we can add an additional context variable latest_profile
that contains the latest user to join the site:
class CtxView(StaticContextMixin, generic.TemplateView): template_name = "ctx.html" static_context = {"latest_profile": Profile.objects.latest('pk')}
Here, static context means anything that is unchanged from a request to request. In that sense, you can mention QuerySets
as well. However, our feed
context variable needs self.request.user
to retrieve the user's viewable posts. Hence, it cannot be included as a static context here.
Problem: Information from your website is often scraped and processed by other applications.
Solution: Create lightweight services that return data in machine-friendly formats, such as JSON or XML.
We often forget that websites are not just used by humans. A significant percentage of web traffic comes from other programs like crawlers, bots, or scrapers. Sometimes, you will need to write such programs yourself to extract information from another website.
Generally, pages designed for human consumption are cumbersome for mechanical extraction. HTML pages have information surrounded by markup, requiring extensive cleanup. Sometimes, information will be scattered, needing extensive data collation and transformation.
A machine interface would be ideal in such situations. You can not only reduce the hassle of extracting information but also enable the creation of mashups. The longevity of an application would be greatly increased if its functionality is exposed in a machine-friendly manner.
Service-oriented architecture (SOA) has popularized the concept of a service. A service is a distinct piece of functionality exposed to other applications as a service. For example, Twitter provides a service that returns the most recent public statuses.
A service has to follow certain basic principles:
In Django, you can create a basic service without any third-party packages. Instead of returning HTML, you can return the serialized data in the JSON format. This form of a service is usually called a web Application Programming Interface (API).
For example, we can create a simple service that returns five recent public posts from SuperBook as follows:
class PublicPostJSONView(generic.View): def get(self, request, *args, **kwargs): msgs = models.Post.objects.public_posts().values( "posted_by_id", "message")[:5] return HttpResponse(list(msgs), content_type="application/json")
For a more reusable implementation, you can use the JSONResponseMixin
class from django-braces
to return JSON using its render_json_response
method:
from braces.views import JSONResponseMixin class PublicPostJSONView(JSONResponseMixin, generic.View): def get(self, request, *args, **kwargs): msgs = models.Post.objects.public_posts().values( "posted_by_id", "message")[:5] return self.render_json_response(list(msgs))
If we try to retrieve this view, we will get a JSON string rather than an HTML response:
>>> from django.test import Client >>> Client().get("http://0.0.0.0:8000/public/").content b'[{"posted_by_id": 23, "message": "Hello!"}, {"posted_by_id": 13, "message": "Feeling happy"}, ...
Note that we cannot pass the QuerySet
method directly to render the JSON response. It has to be a list, dictionary, or any other basic Python built-in data type recognized by the JSON serializer.
Of course, you will need to use a package such as Django REST framework if you need to build anything more complex than this simple API. Django REST framework takes care of serializing (and deserializing) QuerySets
, authentication, generating a web-browsable API, and many other features essential to create a robust and full-fledged API.