Two Scoops of Django 3.x by Daniel Audrey Feldroy (251-350)
Two Scoops of Django 3.x by Daniel Audrey Feldroy (251-350)
Two Scoops of Django 3.x by Daniel Audrey Feldroy (251-350)
# flavors/urls.py
from django.urls import path
urlpatterns = [
# /flavors/api/
path(
route='api/',
view=views.FlavorListCreateAPIView.as_view(),
name='flavor_rest_api'
),
# /flavors/api/:uuid/
path(
route='api/<uuid:uuid>/',
view=views.FlavorRetrieveUpdateDestroyAPIView.as_view(),
name='flavor_rest_api'
)
]
What we are doing is reusing the URLConf name, making it easier to manage when you
have a need for a JavaScript-heavy front-end. All you need to do is access the Flavor resource
via the {% url %} template tag.
In case it’s not clear exactly what our URLConf is doing, let’s review it with a table:
flavors/api/
flavors/api/:uuid/
We’ve shown you (if you didn’t know already) how it’s very easy to build REST APIs in
Django. Now let’s go over some advice on maintaining and extending them.
flavors/
├── api/
ä We like to place all our API components into a package within an app called api/ .
That allows us to isolate our API components in a consistent location. If we were to
put it in the root of our app, then we would end up with a huge list of API-specific
modules in the general area of the app.
ä Viewsets belong in their own module.
ä We always place routers in urls.py. Either at the app or project level, routers belong
in urls.py.
Therefore, the name of the app should reflect its API version (see Section 17.3.7: Version
Your API).
For example, we might place all our views, serializers, and other API components in an app
titled apiv4.
The downside is the possibility for the API app to become too large and disconnected from
the apps that power it. Hence we consider an alternative in the next subsection.
endorse when it comes to any other view. The same goes for app- or model-specific serializers
and renderers. If we do have app-specific serializers or renderers, the same applies.
For apps with so many REST API view classes that it makes it hard to navigate a single
api/views.py or api/viewsets.py module, we can break them up. Specifically, we move our
view (or viewset) classes into a api/views/ (or api/viewsets/ ) package containing Python
modules typically named after our models. So you might see:
flavors/
├── api/
│ ├── __init__.py
│ ├── ... other modules here
│ ├── views
│ │ ├── __init__.py
│ │ ├── flavor.py
│ │ ├── ingredient.py
The downside with this approach is that if there are too many small, interconnecting apps,
it can be hard to keep track of the myriad of places API components are placed. Hence we
considered another approach in the previous subsection.
Regardless of which architectural approach you take, it’s a good idea to try to keep as much
logic as possible out of API views. If this sounds familiar, it should. We covered this in
Section 8.5: Try to Keep Business Logic Out of Views. Remember, at the end of the day,
API views are just another type of view.
In the past, we placed all API view code into a dedicated Django app called api or apiv1,
with custom logic in some of the REST views, serializers, and more. In theory it’s a pretty
good approach, but in practice it means we have logic for a particular app in more than just
one location.
Our current approach is to lean on URL configuration. When building a project-wide API
we write the REST views in the api/views.py or api/viewsets.py modules, wire them into
# core/api_urls.py
"""Called from the project root's urls.py URLConf thus:
path('api/', include('core.api_urls', namespace='api')),
"""
from django.urls import path
urlpatterns = [
# {% url 'api:flavors' %}
path(
route='flavors/',
view=flavor_views.FlavorCreateReadView.as_view(),
name='flavors'
),
# {% url 'api:flavors' flavor.uuid %}
path(
route='flavors/<uuid:uuid>/',
view=flavor_views.FlavorReadUpdateDeleteView.as_view(),
name='flavors'
),
# {% url 'api:users' %}
path(
route='users/',
view=user_views.UserCreateReadView.as_view(),
name='users'
),
# {% url 'api:users' user.uuid %}
path(
route='users/<uuid:uuid>/',
view=user_views.UserReadUpdateDeleteView.as_view(),
name='users'
),
]
Also, in order to avoid angering API consumers, it’s critical to maintain both the existing
API and the predecessor API during and after upgrades. It’s not uncommon for the depre-
cated API to remain in use for several months.
When we implement a new version of the API, we provide customers/users with a dep-
recation warning well in advance of breaking API changes so they can perform necessary
upgrades and not break their own applications. From personal experience, the ability to send
a deprecation warning to end users is an excellent reason to request email addresses from
users of even free and open source API services.
Django REST Framework has built-in mechanisms for supporting the preferred
scheme we list above as well as other approaches. Documentation for this exists at
django-rest-framework.org/api-guide/versioning/
ä If we’re creating a new authentication scheme, we keep it simple and well tested.
ä Outside of the code, we document why existing standard authentication schemes are
insufficient. See the tipbox below.
ä Also outside of the code, we document in depth how our authentication scheme is
designed to work. See the tipbox below.
References:
ä en.wikipedia.org/wiki/Remote_Procedure_Call
ä en.wikipedia.org/wiki/Resource-oriented_architecture
Fortunately, RPC calls are easy to implement with Django Rest Framework. All we have to
do is ignore the abstraction tools of DRF and rely instead on its base APIView:
# sundaes/api/views.py
from django.shortcuts import get_object_or_404
class PourSyrupOnSundaeView(APIView):
"""View dedicated to adding syrup to sundaes"""
While there are nicely complex solutions for nested data, we’ve found a better solution. And
that is to simplify things just a bit. Example:
ä Keep the GET representation of the Cone that includes its Scoops
ä Remove any capability of the POST or PUT for the Cone model to modify Scoops
for that cone.
ä Create GET/POST/PUT API views for Scoops that belong to a Cone.
Yes, this approach does add extra views and additional API calls. On the other hand, this
kind of data modeling can result in simplification of your API. That simplification will result
in easier testing, hence a more robust API.
For what it’s worth, if you take a close look at the Stripe API reference (stripe.com/
docs/api) you’ll see they follow our pattern. You can view complex data, but you have to
create it bit-by-bit.
ä Can we simplify our views? Does switching to APIView resolve the problem?
ä Can we simplify our REST data model as described by views? Does adding more
views (of a straightforward nature) resolve the problem?
ä If a serializer is troublesome and is outrageously complex, why not break it up into
two different serializers for the same model?
As you can see, to overcome problems with DRF, we break our API down into smaller,
more atomic components. We’ve found that it’s better to have more views designed to be
as atomic as possible than a few views with many options. As any experienced programmer
knows, more options means more edge cases.
Below is a sample shutdown view that works against any HTTP method:
# core/apiv1_shutdown.py
from django.http import HttpResponseGone
"""
def apiv1_gone(request):
return HttpResponseGone(apiv1_gone_msg)
Fortunately at DjangoCon 2010 we had the opportunity to ask one of the founders of
GitHub if we could have unlimited access to their API. He graciously said ‘yes’ and within
a day we could get data from GitHub as much as we wanted.
We were delighted. Our users were delighted. Usage of the site increased, people were thirsty
for data as to what were the most active projects. So desirous of data were we that every hour
we requested the latest data from GitHub. And that caused a problem for GitHub.
You see, this was 2010 and GitHub was not the giant, powerful company it is today. At 17
minutes past each hour, Django Packages would send thousands of requests to the GitHub
API in a very short period. With unfettered access we were causing them problems.
Eventually, GitHub contacted us and requested that we scale back how much we were using
their API. We would still have unlimited access, just needed to give them breathing room.
We complied, checking data once per day instead of by the hour, and at a more reasonable
rate. We continue to do so to this day.
While modern GitHub can certainly handle much, much larger volumes of API access then
it could in late 2010, we like to think we learned a shared lesson about unfettered access to
an API: Grant such access cautiously.
Developer tier is free, but only allows 10 API requests per hour.
One Scoop is $24/month, allows 25 requests per minute.
Two Scoops is $79/month, allows 50 requests per minute.
Corporate is $5000/month, allows for 200 requests per minute.
This method is called Bucket-Based Pricing. Now all we have to do is get people to use
our API.
fit into buckets or tiers. The downside is it will require more work to set up this
billing plan in your project. Third-party services like Stripe and Paddle can add fees
or additional amounts to subscriptions, so it’s possible to do in common payment
systems.
Reference: rdegges.com/2020/the-only-type-of-api-services-ill-use
17.7.1 Documentation
The most important thing to do is to provide comprehensive documentation. The eas-
ier to read and understand the better. Providing easy-to-use code examples is a must.
You can write it from scratch or use auto-documentation tools provided by django-
rest-framework itself and various third-party packages (django-rest-framework.org/
topics/documenting-your-api/#third-party-packages. You can even embrace
commercial documentation generation services like readthedocs.com and swagger.io.
Some of the material in Chapter 25: Documentation: Be Obsessed might prove useful for
forward-facing REST API documentation.
In our experience, it’s a good idea to write at least one of these libraries ourselves and create
a demo project. The reason is that it not only advertises our API, it forces us to experience
our API from the same vantage point as our consumers.
Fortunately for us, thanks to the underlying OpenAPI document object model of
(openapis.org/), DRF provides JSON Hyperschema-compatible functionality. When
this format is followed for both the client- and server-side, OpenAPI allows for writing
dynamically driven client libraries that can interact with any API that exposes a supported
schema or hypermedia format.
For building Python-powered client SDKs, reading Section 23.9: Releasing Your Own
Django Packages might prove useful.
ä en.wikipedia.org/wiki/REST
ä en.wikipedia.org/wiki/List_of_HTTP_status_codes
ä github.com/OAI/OpenAPI-Specification
ä jacobian.org/writing/rest-worst-practices/
class FlavorApiView(LoginRequiredMixin,View):
def post(self, request, *args, **kwargs):
# logic goes here
return JsonResponse({})
17.9.3 django-tastypie
django-tastypie is a mature API framework that implements its own class-based view sys-
tem. Predating Django REST Framework by 3 years, it’s a feature-rich, mature, powerful,
stable tool for creating APIs from Django models.
django-tastypie.readthedocs.io/
17.10 Summary
In this chapter we covered:
Coming up next, we’ll go over the other side of REST APIs in Chapter 19: JavaScript and
Django.
Started in 2012, GraphQL is a newcomer to the world of API formats. Unlike REST,
GraphQL comes with schemas, types, and one of our favorite features is a a built-in method
to handle real-time update (Subscriptions). Clients of an API specify the data they want,
and can easily figure out queries because the schemas and types makes it very easy to build
instrospection tools. GraphQL responses are easily serialized to JSON or YAML. For these
reasons and more, GraphQL has seen rapid growth in the past few years.
Because GraphQL comes with a query language and types named in clear English terms
we’ve found it easier to teach beginners how to use GraphQL than REST.
1 By specifying only the data they need rather than accepting large REST-style re-
sponses, clients consume significantly less data. This can have a dramatic impact on
both server and database overhead.
2 Much like with a REST API, common acces patterns can still be identified and han-
dled better through caching, indexing, or code optimization.
Do keep in mind we’re not saying that GraphQL APIs are immune to bottlenecks, we’re
just saying that they are at least as manageable as REST APIs, if not more so due to the
lower bandwidth.
Our preferred library for building GraphQL APIs with Django is Ariadne. Here’s why:
ä Fully asynchronous when used with ASGI and Channels, allowed real-time updates
from Django projects via GraphQL Subscriptions.
ä If you aren’t ready for async yet, you can still run under WSGI.
ä Schemas comes first, meaning you are forced to spec out your design before starting to
code. Since GraphQL schemas carry so much weight, this is a very powerful feature.
ä Creating queries and mutations requires following a simple and lightweight API.
ä Supports Apollo Federation, a specification for composing multiple GraphQL ser-
vices into one GraphQL service.
Specifically:
ä Graphene-Django doesn’t support GraphQL Subscriptions, meaning to get
real-time updates one is forced to either poll the API from the client or use a
separate websocket process. Either is an unnacceptable burden and one that
we hope Django’s new Async Views will empower the library maintainers to
correct.
ä On other platforms, such as Apollo server, GraphQL APIs require little code
to implement. In comparison, to us Graphene feels heavyweight.
Reference: docs.graphene-python.org/projects/django/
In our example, we’re going to use the model’s UUID rather than the model’s primary key
to look up our records. We always try to avoid using sequential numbers for lookups.
The first is a simple GraphQL architecture suitable for most small-to-mid sized applications.
Because Ariadne’s API is lightweight, we’ve found it’s workable to write all queries and
mutations in one schemas.py module.
config/
├── schema.py # imports forms & models from flavors app
├── settings/
├── urls.py
flavors/
├── __init__.py
├── app.py
├── forms.py
├── models.py
Sometimes a single schemas.py module gets too big for comfort. In which case breaking it
up across apps has worked well. Queries and mutations are moved to individual apps, then
imported into the core schemas.py module where make_executable_schema() can be
called on it.
config/
├── schema.py # imports queries/mutations from flavors app
├── settings/
├── urls.py
flavors/
├── __init__.py
├── app.py
├── api/
│ ├── queries.py # imports models
│ ├── mutations.py # imports forms and models
├── forms.py
├── models.py
It’s easy with Ariadne to start dumping business logic into function or class definitions.
Avoid this anti-pattern, it makes it harder to test, upgrade, or reuse logic when it comes
time to release APIv2.
This chapter is about JavaScript in the context of Django. Here are the most common ways
JavaScript is used in tandem with Django:
React.js facebook.github.io/react/
A JavaScript framework and ecosystem created and maintained by Facebook. De-
signed for creation of HTML, iOS, and Android applications. React is typically used
for Single Page Apps, but can be used to enhance existing pages.
Vue.js vuejs.org
Vue is designed to be incrementally adoptable. While its ecosystem isn’t as large as
React.js, the simpler data model is considered easier to learn. In the Django world,
Vue is frequently used to enhance existing pages or for building of Single Page Apps
frontends.
Angular angular.io
Angular is a typescript-only framework open sourced by Google. Within the Django
community it is not as popular as React or Vue.
Reactisso2019.HTML+minimumJSis2020
These five options can really improve what we like to call the ‘immediate user experience’.
However, with every good thing there are always things to consider and things to do. Here
are a number of anti-patterns that we’ve discovered when it comes to frontend projects
consuming APIs for content.
For example, our health provider in 2017 had a SPA-style site. It was lovely. The way every-
thing moved together was a marvel. And it was completely useless when we had to do any
kind of comparative research.
The worst example of the site was when the search system returned a list of doctors we
couldn’t easily compare them. When we clicked on one for more information, their data was
in a sliding modal. We couldn’t right-click and open several on independant tabs as doing
so just took us to the root search page. We could print or email yourself the information on
individual doctors, but PDFs and email are awful comparison tools compared to hopping
between tabs.
What the site should have provided is individual domain references (i.e. URLs) for each
doctor. Either parsed by the server on the back end or even by JavaScript URL management
on the front end. This isn’t hard to do, yet it remained a painfully common issue until we
went to another healthcare provider.
When working with legacy projects, it’s often easier to add new features as single-page
apps. This allows for the maintainers of the project to deliver improved experiences with
new features, while preserving the stability of the existing code base. A good example of
this might be adding a calendar application to an existing project. This is easily possible
with Vue and to a lesser extent with React.
We recommend looking up the data management methods for your chosen JavaScript frame-
work and embracing them as deeply as possible.
Reference material:
ä developers.google.com/web/tools/chrome-devtools
ä developer.mozilla.org/en-US/docs/Mozilla/Debugging/Debugging_
JavaScript
As we write this, the most commonly used tool for this kind of work is webpack. Used by
both React, Vue, and every major framework, webpack bundles browser-based scripts to be
served as assets. While we prefer to use the stock webpack setup provided by Vue or React
CLI tools, understanding webpack is a very powerful skill to have.
References:
ä webpack.js.org
ä github.com/owais/django-webpack-loader - Owais Lone’s Django package
for transparently using webpack with Django
Then the complaints from the other side of the planet start coming in about the slow speed
of the application. Our effort isn’t ‘real-time’ to any of a potentially large block of users and
our client/boss is really unhappy.
This isn’t a joke, it’s a very real problem. Here, Django isn’t the problem. Instead, it’s physics.
The time it takes for HTTP requests to transmit back and forth across half the circumference
of the planet is noticeable to human beings. Add in server-side and client-side processing,
and we risk alienating potential or existing users.
Also, keep in mind that even the fastest local connections have hiccups and slow-downs. So
it’s not uncommon for ‘real-time’ applications to have ways to handle this sort of behavior.
If you’ve ever suddenly discovered that your cloud-based spreadsheet hadn’t saved the data
entered for the past 30 seconds, you’ve uncovered this kind of JavaScript powered trickery
in action. As this can be very frustrating, some online tools upon detecting a connection
failure, disallow further data entry.
significant volume of skills and expertise that’s outside the scope of this book.
If you have the time and budget, this can be an exciting avenue to explore and we encour-
age it. However, unless you’ve done this before there is a good chance you are going to
underestimate the effort involved.
Our answer to overcoming this hurdle to read and understand the AJAX portion of the
CSRF documentation of Django, listed in the resources below. It comes with sample
JavaScript code that you can put into your projects.
References:
ä docs.djangoproject.com/en/3.2/ref/csrf/
ä github.com/jazzband/dj-rest-auth This is a proven authentication library for
DRF that depending on circumstances can be used without CSRF.
ä ??: ??
{{ page_data|json_script:"page-data" }}
<script>
var data = JSON.parse(document.getElementById('page').textContent);
injectNameIntoDiv('scoopName', data.scoop.name);
</script>
Reference: docs.djangoproject.com/en/3.2/ref/templates/builtins/
#json-script
TODO
19.7 Summary
TODO
Some people advocate swapping out core parts of Django’s stack for other pieces. Should it
be done?
Short Answer: Don’t do it. Even one of the founders of Instagram (Kevin Systrom) said
to Forbes.com that it’s completely unnecessary (bit.ly/2pZxOBO).
Long Answer: It’s certainly possible, since Django modules are simply just Python mod-
ules. Is it worth it? Well, it’s worth it only if:
ä You are okay with sacrificing some or all of your ability to use third-party Django
packages.
ä You have no problem giving up the powerful Django admin.
ä You have already made a determined effort to build your project with core
Django components, but you are running into walls that are major blockers.
ä You have already analyzed your own code to find and fix the root causes of your
problems. For example, you’ve done all the work you can to reduce the numbers
of queries made in your templates.
ä You’ve explored all other options including caching, denormalization, etc.
ä Your project is a real, live production site with tons of users. In other words,
you’re certain that you’re not just optimizing prematurely.
ä You’ve looked at and rejected adopting a Service Oriented Approach (SOA) for
those cases Django has problems dealing with.
ä You’re willing to accept the fact that upgrading Django will be extremely painful
or impossible going forward.
Figure 20.1: Replacing more core components of cake with ice cream seems like a good idea.
Which cake would win? The one on the right!
The problem occurs when NoSQL solutions are used to completely replace Django’s rela-
tional database functionality without considering in-depth the long-term implications.
Atomicity means that all parts of a transaction work or it all fails. Without this, you risk
data corruption.
Consistency means that any transaction will keep data in a valid state. Strings remain
strings and integers remain integers. Without this, you risk data corruption.
Isolation means that concurrent execution of data within a transaction will not collide or
leak into another transaction. Without this, you risk data corruption.
Durability means that once a transaction is committed, it will remain so even if the
database server is shut down. Without this, you risk data corruption.
Fad Reasons
For performance reasons, replacing the Not okay: “I have an idea for a social net-
database/ORM with a NoSQL database work for ice cream haters. I just started
and corresponding ORM replacement. building it last month. It must scale to bil-
lions!
Did you notice how each of those descriptions ended with ‘Without this, you risk data corrup-
tion.’? This is because for many NoSQL engines, there is little-to-no mechanism for ACID
compliance. It’s much easier to corrupt the data, which is mostly a non-issue for things like
caching but another thing altogether for projects handling processing of persistent medical
or e-commerce data.
Perhaps...
We would need to track the relationship between properties, property owners, and laws of
50 states. Our Python code would have to maintain the referential integrity between all the
components. We would also need to ensure that the right data goes into the right place.
Instead, do as we do: search for benchmarks, read case studies describing when things went
right or wrong, and form opinions as independently as possible.
Also, experiment with unfamiliar NoSQL databases on small hobby side projects before
you make major changes to your main project infrastructure. Your main codebase is not a
playground.
ä Pinterest: medium.com/@Pinterest_Engineering/
stop-using-shiny-3e1613c2ce14
ä Dan McKinley while at Etsy: mcfunley.com/
why-mongodb-never-worked-out-at-etsy
ä When to use MongoDB with Django daniel.feldroy.com/
when-to-use-mongodb-with-django.html
ä If we use a non-relational data store, limit usage to short-term things like caches,
queues, and sometimes denormalized data. But avoid it if possible, to reduce the
number of moving parts.
ä Use relational data stores for long-term, relational data and sometimes denormalized
data (PostgreSQL’s array and JSON fields work great for this task).
For us, this is the sweet spot that makes our Django projects shine.
20.4 Summary
Always use the right tool for the right job. We prefer to go with stock Django components,
just like we prefer using a scoop when serving ice cream. However, there are times when
other tools make sense.
Just don’t follow the fad of mixing vegetables into your ice cream. You simply can’t replace
the classic strawberry, chocolate, and vanilla with supposedly “high-performance” flavors
such as broccoli, corn, and spinach. That’s taking it too far.
Please submit
Prepared exclusively issues to github.com/feldroy/two-
for LAKLAK scoops- of- django- 3.x/issues
LAKLAK ([email protected]) Transaction: 11322 251
Chapter 20: Tradeoffs of Replacing Core Components
When people ask, “What are the benefits of Django over other web frameworks?” the admin is
what usually comes to mind.
Imagine if every gallon of ice cream came with an admin interface. You’d be able to not
just see the list of ingredients, but also add/edit/delete ingredients. If someone was messing
around with your ice cream in a way that you didn’t like, you could limit or revoke their
access.
Pretty surreal, isn’t it? Well, that’s what web developers coming from another background
feel like when they first use the Django admin interface. It gives you so much power over
your web application automatically, with little work required.
Please submit
Prepared exclusively issues to github.com/feldroy/two-
for LAKLAK scoops- of- django- 3.x/issues
LAKLAK ([email protected]) Transaction: 11322 253
Chapter 21: Working With the Django Admin
Although it’s possible to stretch it into something that your end users could use, you really
shouldn’t. It’s just not designed for use by every site visitor.
class IceCreamBar(models.Model):
name = models.CharField(max_length=100)
shell = models.CharField(max_length=100)
filling = models.CharField(max_length=100)
has_stick = models.BooleanField(default=True)
def __str__(self):
return self.name
Figure 21.4: Improved admin list page with better string representation of our objects.
It’s more than that. When you’re in the shell, you see the better string representation:
>>> IceCreamBar.objects.all()
[<IceCreamBar: Vanilla Crisp>, <IceCreamBar: Mint Cookie Crunch>,
<IceCreamBar: Strawberry Pie>]
Please submit
Prepared exclusively issues to github.com/feldroy/two-
for LAKLAK scoops- of- django- 3.x/issues
LAKLAK ([email protected]) Transaction: 11322 255
Chapter 21: Working With the Django Admin
The __str__() method is called whenever you call str() on an object. This occurs in the
Django shell, templates, and by extension the Django admin. Therefore, try to make the
results of __str__() nice, readable representation of Django model instances.
@admin.register(IceCreamBar)
class IceCreamBarModelAdmin(admin.ModelAdmin):
list_display = ('name', 'shell', 'filling')
For example, it’s not uncommon to want to see the exact URL of a model instance in
the Django admin. If you define a get_absolute_url() method for your model, what
Django provides in the admin is a link to a redirect view whose URL is very different from
the actual object URL. Also, there are cases where the get_absolute_url() method is
meaningless (REST APIs come to mind).
In the example below, we demonstrate how to use a simple callable to provide a link to our
target URL:
# icecreambars/admin.py
from django.contrib import admin
from django.urls import reverse, NoReverseMatch
from django.utils.html import format_html
@admin.register(IceCreamBar)
class IceCreamBarModelAdmin(admin.ModelAdmin):
list_display = ('name', 'shell', 'filling')
readonly_fields = ('show_url',)
Since a picture is worth a thousand words, here is what our callable does for us:
Please submit
Prepared exclusively issues to github.com/feldroy/two-
for LAKLAK scoops- of- django- 3.x/issues
LAKLAK ([email protected]) Transaction: 11322 257
Chapter 21: Working With the Django Admin
1 Via the Django admin, Daniel edits the record for “Peppermint Sundae” ice cream
bar. He starts to make changes. He gets a phone call from the marketing officer of
Icecreamlandia and leaves his screen open.
2 In the meantime, Audrey decides to modify “Peppermint Sundae” ice cream bar. She
spends ten minutes making her changes, then saves the her data.
3 Daniel gets off the phone and finally saves his changes. He overwrites Audrey’s
changes.
If you have multiple users with access to the Django admin, you need to be aware of this
possibility.
It’s useful because it introspects the Django framework to display docstrings for project
components like models, views, custom template tags, and custom filters. Even if a project’s
components don’t contain any docstrings, simply seeing a list of harder-to-introspect items
like oddly named custom template tags and custom filters can be really useful in exploring
the architecture of a complicated, existing application.
Once you have this in place, go to /admin/doc/ and explore. You may notice a lot of
your project’s code lacks any sort of documentation. This is addressed in the formal doc-
umentation on django.contrib.admindocs: docs.djangoproject.com/en/3.2/
ref/contrib/admin/admindocs/ and our own chapter on Chapter 25: Documenta-
tion: Be Obsessed.
Please submit
Prepared exclusively issues to github.com/feldroy/two-
for LAKLAK scoops- of- django- 3.x/issues
LAKLAK ([email protected]) Transaction: 11322 259
Chapter 21: Working With the Django Admin
It turns out that besides the most basic CSS-based modifications, creating custom Django
themes is very challenging. For anyone who has delved into the source code for these
projects, it’s clear that custom admin skins require arcane code to account for some of the
idiosyncrasies of django.contrib.admin.
Patrick Kranzlmueller, maintainer of django-grappelli, goes into great detail in his article
on the subject, ‘A Frontend Framework for the Django Admin Interface’, which you can
read at the link below:
ä sehmaschine.net/blog/django-admin-frontend-framework.
Here are some tips when working with custom django.contrib.admin skins:
Therefore, when evaluating one of these projects for use on a project, check to see how far
the documentation goes beyond installation instructions.
Therefore, if you use a custom skin, the best practice is to write tests of the admin, especially
for any customization. Yes, it is a bit of work up front, but it means catching these bugs much,
much earlier.
For more on testing, see our writings on testing in Chapter 24: Testing Stinks and Is a
Waste of Money!.
It also prevents attackers from easily profiling your site. For example, attackers can
tell which version of Django you’re using, sometimes down to the point-release level,
by examining the HTML content of the login screen for admin/ .
If you’re particularly concerned about people trying to break into your Django site, django-
admin-honeypot is a package that puts a fake Django admin login screen at admin/ and
logs information about anyone who attempts to log in.
Please submit
Prepared exclusively issues to github.com/feldroy/two-
for LAKLAK scoops- of- django- 3.x/issues
LAKLAK ([email protected]) Transaction: 11322 261
Chapter 21: Working With the Django Admin
Without TLS, if you log into your Django admin on an open WiFi network, it’s trivial for
someone to sniff your admin username/password.
An acceptable alternative is to put this logic into middleware. It’s better to do it at the
web server level because every middleware component adds an extra layer of logic wrapping
your views, but in some cases this can be your only option. For example, your platform-as-
a-service might not give you fine-grain control over web server configuration.
21.10 Summary
In this chapter we covered the following:
Django comes with a built-in support for user records. It’s a useful feature, doubly so once
you learn how to extend and expand on the basic functionality. So let’s go over the best
practices:
It is possible to get two different User model definitions depending on the project configu-
ration. This doesn’t mean that a project can have two different User models; it means that
every project can customize its own User model.
Please submit
Prepared exclusively issues to github.com/feldroy/two-
for LAKLAK scoops- of- django- 3.x/issues
LAKLAK ([email protected]) Transaction: 11322 263
Chapter 22: Dealing With the User Model
class IceCreamStore(models.Model):
owner = models.OneToOneField(settings.AUTH_USER_MODEL)
title = models.CharField(max_length=255)
Yes, it looks a bit strange, but that’s what the official Django docs advise.
# DON'T DO THIS!
from django.contrib.auth import get_user_model
from django.db import models
class IceCreamStore(models.Model):
# profiles/models.py
from django.contrib.auth.models import AbstractUser
from django.db import models
class KarmaUser(AbstractUser):
karma = models.PositiveIntegerField(verbose_name='karma',
default=0,
blank=True)
Please submit
Prepared exclusively issues to github.com/feldroy/two-
for LAKLAK scoops- of- django- 3.x/issues
LAKLAK ([email protected]) Transaction: 11322 265
Chapter 22: Dealing With the User Model
AUTH_USER_MODEL = 'profiles.KarmaUser'
ä You’re unhappy with the fields that the User model provides by default, such as
first_name and last_name.
ä You prefer to subclass from an extremely bare-bones clean slate but want to take
advantage of the AbstractBaseUser sane default approach to storing passwords.
Either of these use cases provide motive for the continued use of this technique.
# profiles/models.py
class EaterProfile(models.Model):
class ScooperProfile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL)
scoops_scooped = models.IntegerField(default=0)
class InventorProfile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL)
flavors_invented = models.ManyToManyField(Flavor, null=True,
,→ blank=True)
Using this approach, we can query for any user’s favorite ice cream trivially with the ORM:
user.eaterprofile.favorite_ice_cream. In addition, Scooper and Inventor
Please submit
Prepared exclusively issues to github.com/feldroy/two-
for LAKLAK scoops- of- django- 3.x/issues
LAKLAK ([email protected]) Transaction: 11322 267
Chapter 22: Dealing With the User Model
profiles provide individual data that only applies to those users. Since that data is isolated
into dedicated models, it’s much harder for accidents between user types to occur.
The only downside to this approach is that it’s possible to take it too far in complexity of
profiles or in the supporting code. As always, keep your code as simple and clean as possible.
Instead, just use one model and mark it appropriately. There’s two primary ways to do this:
class User(AbstractUser):
class Types(models.TextChoices):
EATER = "EATER", "Eater"
SCOOPER = "SCOOPER", "Scooper"
INVENTOR = "INVENTOR", "Inventor"
Please note that we don’t rely on the multiple use of BooleanField to control roles. That
approach works only in limited circumstances, and becomes confusing as a site grows in size
and complexity over time. Better to use a single field to control the role of a User.
Furthermore, the approach we’re using relies on a CharField. In practice, we tend to use
ForeignKey relations to a Role model. If users need multiple roles, then we will either make
that a ManyToManyField or lean on Django’s built-in Group system. The disadvantage of
using Django’s built-in Group system is the minimal documentation for it.
Let’s also make querying for types of user as obvious and explicit as possible. It is easy to
forget to include a filter on a type of user, granting them access to things they should not
have.
Proxy models makes implementing this easy. First, in our model, add a base_type property
and extend the built-in save() method:
class User(AbstractUser):
class Types(models.TextChoices):
EATER = "EATER", "Eater"
SCOOPER = "SCOOPER", "Scooper"
INVENTOR = "INVENTOR", "Inventor"
# ...
Please submit
Prepared exclusively issues to github.com/feldroy/two-
for LAKLAK scoops- of- django- 3.x/issues
LAKLAK ([email protected]) Transaction: 11322 269
Chapter 22: Dealing With the User Model
class InventorManager(BaseUserManager):
def get_queryset(self, *args, **kwargs):
results = super().get_queryset(*args, **kwargs)
return results.filter(type=User.Types.INVENTOR)
class Inventor(User):
# This sets the user type to INVENTOR during record creation
base_type = User.Types.INVENTOR
As mentioned in the comments, proxy models don’t add fields. What they do create is refer-
ences to a model object on which we can hang custom managers, methods, and properties.
The example queries below will show the power of this approach:
The proxy approach gives us multiple types of users without creating a new User table or
being forced to dramatically extend django.contrib.auth in such a way that we can’t
use third-party libraries.
That proxy models can have their own model managers means that we can have more ex-
plicit queries. This helps prevent mistakes in granting permission. Compare these two code
examples for clarity:
>>> User.objects.filter(type=User.Types.INVENTOR)
>>> Inventor.objects.filter() # Our preference
Please submit
Prepared exclusively issues to github.com/feldroy/two-
for LAKLAK scoops- of- django- 3.x/issues
LAKLAK ([email protected]) Transaction: 11322 271
Chapter 22: Dealing With the User Model
mance considerations in, there is significant risk of innappropiate data being held by
the wrong user type.
Our preference is the first option, to link back from a related model and combine with proxy
models. Here’s how we do it using all the ”Profile” models from Section 22.2.3: Option 3:
Linking Back From a Related Model.
class Inventor(User):
# ...
objects = InventorManager()
class Meta:
proxy = True
@property
def extra(self):
return self.inventorprofile
class Scooper(User):
# ...
objects = ScooperManager()
class Meta:
proxy = True
@property
def extra(self):
return self.scooperprofile
class Eater(User):
# ...
objects = EaterManager()
class Meta:
proxy = True
@property
def extra(self):
return self.eaterprofile
Now, no matter the user type, the Profile from the One-to-One relationship can be accessed
thus:
ä eater.extra.favorite_ice_cream
ä scooper.extra.scoops_scooped
ä invented.extra.flavors_invented
We really like this approach, it is easy to remember that the relation to the profile table is
accessible via the extra property. It even works with architectures that allow users to have
more than one role.
Proxy models are something typically used sparingly as complex implementations can be
confusing. However, under the right circumstances such as multiple user types, they are
incredibly useful.
22.4 Summary
In this chapter we covered the new method to find the User model and define our own
custom ones. Depending on the needs of a project, they can either continue with the current
way of doing things or customize the actual user model. We also covered the use case of
multiple user model types through proxy models.
Please submit
Prepared exclusively issues to github.com/feldroy/two-
for LAKLAK scoops- of- django- 3.x/issues
LAKLAK ([email protected]) Transaction: 11322 273
Chapter 22: Dealing With the User Model
The real power of Django is more than just the framework and documentation available at
djangoproject.com. It’s the vast selection of third-party Django and Python packages
provided by the open source community. There are many, many third-party packages avail-
able for your Django projects which can do an incredible amount of work for you. These
packages have been written by people from all walks of life, and they power much of the
world today.
Figure 23.1: A jar of Django’s mysterious secret sauce. Most don’t have a clue what this is.
Much of professional Django and Python development is about the incorporation of third-
party packages into Django projects. If you try to write every single tool that you need from
scratch, you’ll have a hard time getting things done.
This is especially true for us in the consulting world, where client projects consist of many
of the same or similar building blocks.
Please submit
Prepared exclusively issues to github.com/feldroy/two-
for LAKLAK scoops- of- django- 3.x/issues
LAKLAK ([email protected]) Transaction: 11322 275
Chapter 23: Django’s Secret Sauce: Third-Party Packages
Note that not all of those packages are Django-specific, that means that you can use some of
them in other Python projects. (Generally, Django-specific packages have names prefixed
with “django-” or “dj-”, but there are many exceptions.)
For the vast majority of Python community, no open source project release is considered
official until it occurs on the Python Package Index.
The Python Package Index is much more than just a directory. Think of it as the world’s
largest center for Python package information and files. Whenever you use pip to install a
particular release of Django, pip downloads the files from the Python Package Index. Most
Python and Django packages are downloadable from the Python Package Index in addition
to pip.
Django Packages is best known as a comparison site for evaluating package features. On
Django Packages, packages are organized into handy grids so they can be compared against
each other.
Django Packages also happens to have been created by the authors of this book, with con-
tributions from many, many people in the Python community. Thanks to many volunteers
it is continually maintained and improved as a helpful resource for Django users.
As a Django (and Python) developer, make it your mission to use third-party libraries in-
stead of reinventing the wheel whenever possible. The best libraries have been written, doc-
umented, and tested by amazingly competent developers working around the world. Stand-
ing on the shoulders of these giants is the difference between amazing success and tragic
downfall.
As you use various packages, study and learn from their code. You’ll learn patterns and tricks
that will make you a better developer.
On the other hand, it’s very important to be able to identify the good packages from the
bad. It’s well worth taking the time to evaluate packages written by others the same way we
evaluate our own work. We cover this later in this chapter in Section 23.10: What Makes a
Good Django Package?
Refer to Chapter 2: The Optimal Django Environment Setup for more details.
Please submit
Prepared exclusively issues to github.com/feldroy/two-
for LAKLAK scoops- of- django- 3.x/issues
LAKLAK ([email protected]) Transaction: 11322 277
Chapter 23: Django’s Secret Sauce: Third-Party Packages
Django==1.11
coverage==4.3.4
django-extensions==1.7.6
django-braces==1.11
Note that each package is pinned to a specific version number. Always pin your package
dependencies to version numbers.
What happens if you don’t pin your dependencies? You are almost guaranteed to run into
problems at some point when you try to reinstall or change your Django project. When new
versions of packages are released, you can’t expect them to be backwards-compatible.
Our sad example: Once we followed a software-as-a-service platform’s instructions for us-
ing their library. As they didn’t have their own Python client, but an early adopter had a
working implementation on GitHub, those instructions told us to put the following into
our requirements/base.txt:
-e git+https://github.com/erly-adptr/py-junk.git#egg=py-jnk
Our mistake. We should have known better and pinned it to a particular git revision number.
Not the early adopter’s fault at all, but they pushed up a broken commit to their repo. Once
we had to fix a problem on a site very quickly, so we wrote a bug fix and tested it locally in
development. It passed the tests. Then we deployed it to production in a process that grabs
all dependency changes; unfortunately the broken commit was interpreted as a valid change.
Which meant, while fixing one bug, we crashed the site.
The purpose of using pinned releases is to add a little formality and process to our published
work. Especially in Python, GitHub and other repos are a place for developers to pub-
lish their work-in-progress, not the final, stable work upon which our production-quality
projects depend.
One more thing, when pinning dependencies, try to pin the dependencies of dependencies.
It just makes deployment and testing that much more predictable.
If this is the first time you’ve done this for a particular virtualenv, it’s going to take a while
for it to grab all the dependencies and install them.
First, make a serious effort to determine and solve the problem yourself. Pour over the
documentation and make sure you didn’t miss a step. Search online to see if others have run
into the same issue. Be willing to roll up your sleeves and look at the package source code,
as you may have found a bug.
If it appears to be a bug, see if someone has already reported it in the package repository’s
issue tracker. Sometimes you’ll find workarounds and fixes there. If it’s a bug that no one
has reported, go ahead and file it.
Please submit
Prepared exclusively issues to github.com/feldroy/two-
for LAKLAK scoops- of- django- 3.x/issues
LAKLAK ([email protected]) Transaction: 11322 279
Chapter 23: Django’s Secret Sauce: Third-Party Packages
If you still get stuck, try asking for help in all the usual places: StackOverflow, IRC #django,
the project’s IRC channel if it has its own one, and your local Python user group. Be as
descriptive and provide as much context as possible about your issue.
The best way to get started is to follow Django’s Advanced Tutorial: How to Write Reusable
Apps, for the basics: docs.djangoproject.com/en/3.2/intro/reusable-apps/
ä Create a public repo containing the code. Most Django packages are hosted on
GitHub these days, so it’s easiest to attract contributors there, but various alterna-
tives exist (Gitlab, Bitbucket, Assembla, etc.).
ä Release the package on the Python Package Index (pypi.org). Follow
the submission instructions at packaging.python.org/distributing/
#uploading-your-project-to-pypi.
ä Add the package to Django Packages (djangopackages.org).
ä Use Read the Docs (readthedocs.io) or similar service to host your Sphinx docu-
mentation.
When choosing alternative hosted version control services, keep in mind that pip
only supports Git, Mercurial, Bazaar, and Subversion.
23.10.1 Purpose
Your package should do something useful and do it well. The name should be descriptive.
The package repo’s root folder should be prefixed with ‘django-’ or ‘dj-’ to help make it easier
to find.
If part of the package’s purpose can be accomplished with a related Python package that
doesn’t depend on Django, then create a separate Python package and use it as a dependency.
23.10.2 Scope
Your package’s scope should be tightly focused on one small task. This means that your
application logic will be tighter, and users will have an easier time patching or replacing the
package.
23.10.3 Documentation
A package without documentation is a pre-alpha package. Docstrings don’t suffice as docu-
mentation.
If your package has dependencies, they should be documented. Your package’s installation
instructions should also be documented. The installation steps should be bulletproof.
23.10.4 Tests
Your package should have tests. Tests improve reliability, make it easier to advance
Python/Django versions, and make it easier for others to contribute effectively. Write up
instructions on how to run your package’s test suite. If you or any contributor can run your
tests easily before submitting a pull request, then you’re more likely to get better quality
contributions.
23.10.5 Templates
In the past, some Django packages provided instructions for creating templates in their docs
in lieu of actual template files. However, nowadays it’s pretty standard for Django packages
to come with a set of barebones templates that demonstrate basic functionality. Typically
these templates contain only minimalist HTML, any needed JavaScript, and no CSS. The
exception is for packages containing widgets that require CSS styling.
23.10.6 Activity
Your package should receive regular updates from you or contributors if/when needed.
When you update the code in your repo, you should consider uploading a minor or ma-
Please submit
Prepared exclusively issues to github.com/feldroy/two-
for LAKLAK scoops- of- django- 3.x/issues
LAKLAK ([email protected]) Transaction: 11322 281
Chapter 23: Django’s Secret Sauce: Third-Party Packages
23.10.7 Community
Great open source packages, including those for Django, often end up receiving contribu-
tions from other developers in the open source community. All contributors should receive
attribution in a CONTRIBUTORS.rst or AUTHORS.rst file.
Be an active community leader if you have contributors or forks of your package. If your
package is forked by other developers, pay attention to their work. Consider if there are
ways that parts or all of their work can be merged into your fork. If the package’s function-
ality diverges a lot from your package’s purpose, be humble and consider asking the other
developer to give their fork a new name.
23.10.8 Modularity
Your package should be as easily pluggable into any Django project that doesn’t replace
core components (templates, ORM, etc) with alternatives. Installation should be minimally
invasive. Be careful not to confuse modularity with over-engineering, though.
# DON'T DO THIS!
# requirements for django-blarg
Django==3.0
requests==2.11.0
The reason is dependency graphs. Every so often something that you absolutely pin to a
specific version of Django or another library will break on someone else’s site project. For
example, what if icecreamratings.com was our site and this was its deployed project’s require-
ments.txt file, and we installed django-blarg?
What would happen if Bad Example 21.3 were installed to a project with Example 21.4
requirements is that the Django 3.0 requirement would overwrite the Django 3.1 specifica-
tion during installation of icecreamratings.com requirements. As there are several backwards
incompatibilities between Django 3.0 and 3.1, django-blarg could make icecreamratings.com
site simply throw HTTP 500 errors.
Your third-party package should specify what other libraries your package requires in the
broadest terms possible:
Django>=3.1,<3.0
requests>=2.13.0,<=3.0.0
Additional Reading:
ä pip.pypa.io/en/stable/reference/pip_install/
#requirement-specifiers
ä nvie.com/posts/pin-your-packages/
Please submit
Prepared exclusively issues to github.com/feldroy/two-
for LAKLAK scoops- of- django- 3.x/issues
LAKLAK ([email protected]) Transaction: 11322 283
Chapter 23: Django’s Secret Sauce: Third-Party Packages
‘A’ represents the major version number. Increments should only happen with large changes
that break backwards compatibility from the previous major version. It’s not uncom-
mon to see large API changes between versions.
‘B’ is the minor version number. Increments include less breaking changes, or deprecation
notices about forthcoming changes.
‘C’ represents bug fix releases, and purists call this the ‘micro’ release. It’s not uncommon
for developers to wait until a project has its first release at this level before trying the
latest major or minor release of an existing project.
For alpha, beta, or release-candidates for a project, the convention is to place this informa-
tion as a suffix to the upcoming version number. So you might have:
ä Django 3.2-alpha
ä django-crispy-forms 1.9.1-beta
Be nice to other developers and follow the convention of only placing proper
releases on PyPI.
Note: While modern versions of pip no longer install pre-releases by default, it’s
dangerous to expect users of code to have the latest pip version installed.
Additional Reading:
ä python.org/dev/peps/pep-0386
ä semver.org
23.10.12 Name
The name of the project is absolutely critical. A well-named project makes it easy to discover
and remember, a poor name hides it from potential users, can scare off its use from some
developer shops, and even block it from being listed on PyPI, Django Packages, and other
resources.
We did cover the basics in Section 4.2: What to Name Your Django Apps, but here are tips
that apply to open source Django packages:
ä Check to see that the name isn’t already registered on PyPI. Otherwise, it won’t be trivial
to install with pip.
ä Check to see that the name isn’t on Django Packages. This applies only to packages de-
signed for use with Django.
ä Don’t use names that include obscenity. While you might find them funny, it’s unfortu-
nate for others. For example a noted developer once created a library that couldn’t be
used at NASA until he agreed to change the name.
23.10.13 License
Your package needs a license. Preferably, for individuals it should be licensed under the MIT
licenses, which are generally accepted for being permissive enough for most commercial or
noncommercial uses. If you are worried about patents, then go with the Apache license.
Create a LICENSE.rst file in your repo root, mention the license name at the top, and paste
in the appropriate text from the (OSI) approved list at choosealicense.com.
Please submit
Prepared exclusively issues to github.com/feldroy/two-
for LAKLAK scoops- of- django- 3.x/issues
LAKLAK ([email protected]) Transaction: 11322 285
Chapter 23: Django’s Secret Sauce: Third-Party Packages
If there is concern about future collisions, settings-based URL namespace systems can be
implemented. This is where the project defines its URL namespace as a setting, then pro-
vides a Django context processor and detailed instructions on use. While it’s not hard to
implement, it does create a level of abstraction that can make a project a little bit harder to
maintain.
That said, putting all the pieces together in order to make a reusable Django package is a lot
of work, and it’s common to get things wrong. Fortunately, Cookiecutter makes this easy.
In the Cookiecutter templates referenced below, we have vetted them by aggressively asking
for them to be reviewed by leaders in both the Django and Python communities. Just use
the following bash example at the command-line:
The open source packages that you create have a life of their own. They mature over time,
changing as their needs and the development standards grow over time. Here are some
things we should do when maintaining open source projects:
Here are problematic pull requests that should be considered for rejection:
ä Any pull request that fails the tests. Ask for fixes. See Chapter 24: Testing Stinks and
Is a Waste of Money!.
ä Any added code that reduces test coverage. Again, see chapter 24.
ä Pull requests should change/fix as little as possible. Large, wide-sweeping changes in a
pull request should be rejected, with comments to isolate changes in smaller, atomic
pull requests.
ä Overly complex code submissions should be carefully considered. There is nothing wrong
with asking for simplification, better comments, or even rejecting an overly complex
pull request.
ä Code that breaks PEP-8 needs to be resubmitted. The Django world follows PEP-8 very
closely, and so should your project. Submissions that violate PEP 8 can be requested
to be improved.
ä Code changes combined with major whitespace cleanup. If someone submits a change of
two lines of code and corrects 200 lines of whitespace issues, the diff on that pull
Please submit
Prepared exclusively issues to github.com/feldroy/two-
for LAKLAK scoops- of- django- 3.x/issues
LAKLAK ([email protected]) Transaction: 11322 287
Chapter 23: Django’s Secret Sauce: Third-Party Packages
In the Python world, the accepted best practice is to release when significant (or even minor)
changes or bug fixes happen on trunk or master. In fact, minor bug fix releases are a part of
every ongoing software project and no one faults anyone for these kinds of things (except
in US government IT contracts, but that’s outside the scope of this book).
If you aren’t sure how this works, please look at python-request’s change history, it be-
ing one of Python’s most popular projects: github.com/psf/requests/blob/master/
HISTORY.md
That’s not all! Twine works better at uploading Wheels (see the next subsection),
doesn’t require executing the setup.py, and even pre-signs your releases. If you are
seriously security minded, it’s the tool of choice.
Then, after you’ve deployed your package to PyPI, run the following commands:
Twine makes universal wheels when the optional setup.cfg file is at the same level as setup.py
and includes this snippet:
# setup.cfg
[wheel]
universal = 1
Wheel Resources:
Please submit
Prepared exclusively issues to github.com/feldroy/two-
for LAKLAK scoops- of- django- 3.x/issues
LAKLAK ([email protected]) Transaction: 11322 289
Chapter 23: Django’s Secret Sauce: Third-Party Packages
Reference: git-scm.com/book/en/v2/Git-Basics-Tagging
If for no other reason, this is an excellent reason to include tests in your project.
using it to solve their problems. Invariably they’ll modify the templates inside their own
templates/ directory, but this just makes everything so much easier.
bit.ly/2onSzCV
However, by giving a project away to active maintainers, it can be reborn and prove more
useful. It also earns the respect of the developer community at large.
23.14 Summary
Django’s real power is in the vast selection of third-party packages available to you for use
in your Django projects.
Make sure that you have pip and virtualenv (or Poetry as an alternative) installed and know
how to use them, since they’re your best tools for installing packages on your system in a
manageable way.
Please submit
Prepared exclusively issues to github.com/feldroy/two-
for LAKLAK scoops- of- django- 3.x/issues
LAKLAK ([email protected]) Transaction: 11322 291
Chapter 23: Django’s Secret Sauce: Third-Party Packages
Get to know the packages that exist. The Python Package Index and Django Packages are
a great starting point for finding information about packages.
Package maturity, documentation, tests, and code quality are good starting criteria when
evaluating a Django package.
Installation of stable packages is the foundation of Django projects big and small. Being
able to use packages means sticking to specific releases, not just the trunk or master of a
project. Barring a specific release, you can rely on a particular commit. Fixing problems that
a package has with your project takes diligence and time, but remember to ask for help if
you get stuck.
We also covered how to create your own third-party package, and provided basic instruction
on how to use cookiecutter to jump-start you on your way to releasing something on the
Python Package Index. We also included instructions on using the new Wheel format.
Gretchen Davidian, a Management and Program Analyst at NASA, told me that when she
was still an engineer, her job as a tester was to put equipment intended to get into space
through such rigorous conditions that they would begin emitting smoke and eventually
catch on fire.
That sounds exciting! Employment, money, and lives were on the line, and knowing
Gretchen’s attention to detail, I’m sure she set a lot of hardware on fire.
Keep in mind that for a lot of us, as software engineers, the same risks are on the line
as NASA. I recall in 2004 while working for a private company how a single miles-vs-
kilometers mistake cost a company hundreds of thousands of dollars in a matter of hours.
Quality Assurance (QA) staff lost their jobs, which meant that money and health benefits
were gone. In other words, employment, money, and possibly lives can be lost without ad-
equate tests. While the QA staff were very dedicated, everything was done via manually
clicking through projects, and human error simply crept into the testing process.
Today, as Django moves into a wider and wider set of applications, the need for automated
testing is just as important as it was for Gretchen at NASA and for the poor QA staff in 2004.
Here are some cases where Django is used today that have similar quality requirements:
Please submit
Prepared exclusively issues to github.com/feldroy/two-
for LAKLAK scoops- of- django- 3.x/issues
LAKLAK ([email protected]) Transaction: 11322 293
Chapter 24: Testing Stinks and Is a Waste of Money!
This tool provides clear insight into what parts of your code base are covered by tests,
and what lines haven’t been touched by tests. You also get a handy percentage of how
much of your code is covered by tests. Even 100% test coverage doesn’t guarantee a
bug-free application, but it helps.
We want to thank Ned Batchelder for his incredible work in maintaining cover-
age.py. It’s a superb project and is useful for any Python-related project.
Inside that new directory, because most apps need them, we create test_forms.py,
test_models.py, test_views.py modules. Tests that apply to forms go into test_forms.py, model
tests go into test_models.py, and so on.
popsicles/
__init__.py
admin.py
forms.py
models.py
tests/
__init__.py
test_forms.py
test_models.py
test_views.py
views.py
Also, if we have other files besides forms.py, models.py and views.py that need testing, we
create corresponding test files.
We like this approach because while it does add an extra layer of nesting, the alternative is
to have an app structure with a painful number of modules to navigate.
Over the years, we’ve evolved a number of practices we like to follow when writing tests,
including unit tests. Our goal is always to write the most meaningful tests in the shortest
amount of time. Hence the following:
Therein lies a conundrum. How does one run a test for a view, when views often require the
use of models, forms, methods, and functions?
The trick is to be absolutely minimalistic when constructing the environment for a particular
test, as shown in the example below:
# flavors/tests/test_api.py
import json
Please submit
Prepared exclusively issues to github.com/feldroy/two-
for LAKLAK scoops- of- django- 3.x/issues
LAKLAK ([email protected]) Transaction: 11322 295
Chapter 24: Testing Stinks and Is a Waste of Money!
class FlavorAPITests(TestCase):
def setUp(self):
Flavor.objects.get_or_create(title='A Title',
,→ slug='a-slug')
def test_list(self):
url = reverse('flavors:flavor_object_api')
response = self.client.get(url)
self.assertEquals(response.status_code, 200)
data = json.loads(response.content)
self.assertEquals(len(data), 1)
In this test, taken from code testing the API we presented in Section 17.2: Illustrating
Design Concepts With a Simple API, we use the setUp() method to create the minimum
possible number of records needed to run the test.
Here’s a much larger example, one based on the REST API example that we provided in
Chapter 17: Building REST APIs With Django REST Framework.
# flavors/tests/test_api.py
import json
class DjangoRestFrameworkTests(TestCase):
def setUp(self):
Flavor.objects.get_or_create(title='title1', slug='slug1')
Flavor.objects.get_or_create(title='title2', slug='slug2')
self.create_read_url = reverse('flavors:flavor_rest_api')
self.read_update_delete_url = \
reverse('flavors:flavor_rest_api', kwargs={'slug':
,→ 'slug1'})
def test_list(self):
response = self.client.get(self.create_read_url)
def test_detail(self):
response = self.client.get(self.read_update_delete_url)
data = json.loads(response.content)
content = {'id': 1, 'title': 'title1', 'slug': 'slug1',
'scoops_remaining': 0}
self.assertEquals(data, content)
def test_create(self):
post = {'title': 'title3', 'slug': 'slug3'}
response = self.client.post(self.create_read_url, post)
data = json.loads(response.content)
self.assertEquals(response.status_code, 201)
content = {'id': 3, 'title': 'title3', 'slug': 'slug3',
'scoops_remaining': 0}
self.assertEquals(data, content)
self.assertEquals(Flavor.objects.count(), 3)
def test_delete(self):
response = self.client.delete(self.read_update_delete_url)
self.assertEquals(response.status_code, 204)
self.assertEquals(Flavor.objects.count(), 1)
Please submit
Prepared exclusively issues to github.com/feldroy/two-
for LAKLAK scoops- of- django- 3.x/issues
LAKLAK ([email protected]) Transaction: 11322 297
Chapter 24: Testing Stinks and Is a Waste of Money!
the part of the test writer. This is because the request factory doesn’t support middleware,
including session and authentication.
See docs.djangoproject.com/en/3.2/topics/testing/advanced/
Unfortunately the documentation doesn’t cover when you want to test a view wrapped with
a single middleware class. For example, if a view required sessions, this is how we would
do it:
class SavoryIceCreamTest(TestCase):
def setUp(self):
# Every test needs access to the request factory.
self.factory = RequestFactory()
def test_cheese_flavors(self):
request = self.factory.get('/cheesy/broccoli/')
request.user = AnonymousUser()
response = cheese_flavors(request)
self.assertContains(response, 'bleah!')
Our favorite method of handling these actions is to just dig in and write the same or sim-
ilar code multiple times. In fact, we’ll quietly admit to copy/pasting code between tests to
expedite our work.
Please submit
Prepared exclusively issues to github.com/feldroy/two-
for LAKLAK scoops- of- django- 3.x/issues
LAKLAK ([email protected]) Transaction: 11322 299
Chapter 24: Testing Stinks and Is a Waste of Money!
load process where your JSON file(s) is either broken or a subtly inaccurate representation
of the database.
Rather than wrestle with fixtures, we’ve found it’s easier to write code that relies on the
ORM. Other people like to use third-party packages.
Views: Viewing of data, changing of data, and custom class-based view methods.
Models: Creating/updating/deleting of models, model methods, model manager methods.
Forms: Form methods, clean() methods, and custom fields.
Validators: Really dig in and write multiple test methods against each custom validator
you write. Pretend you are a malignant intruder attempting to damage the data in the
site.
Signals: Since they act at a distance, signals can cause grief especially if you lack tests on
them.
Filters: Since filters are essentially just functions accepting one or two arguments, writing
tests for them should be easy.
Template Tags: Since template tags can do anything and can even accept template context,
writing tests often become much more challenging. This means you really need to test
them, since otherwise you may run into edge cases.
Miscellany: Context processors, middleware, email, and anything else not covered in this
list.
Failure What happens when any of the above fail? Testing for system error is as important
as testing for system success.
The only things that shouldn’t be tested are parts of your project that are already covered by
tests in core Django and third-party packages. For example, a model’s fields don’t have to be
tested if you’re using Django’s standard fields as-is. However, if you’re creating a new type
of field (e.g. by subclassing FileField), then you should write detailed tests for anything
that could go wrong with your new field type.
Figure 24.1: Test as much of your project as you can, as if it were free ice cream.
However, this only tests part of the scenario. What if the user isn’t logged in? What if
the user is trying to edit someone else’s review? Does the view produce an error and most
importantly: is the object left unchanged? It has been argued that this test is even more
important than the success scenario: a failure in the success scenario will cause inconvenience
for users but will be reported. A failure in the fail scenario will cause a silent security hole
that could go undetected until it’s too late.
This is only a sampling of the things that can go wrong when we don’t test for what happens
when our systems break down. It is up to us to learn how to test for the exceptions our code
Please submit
Prepared exclusively issues to github.com/feldroy/two-
for LAKLAK scoops- of- django- 3.x/issues
LAKLAK ([email protected]) Transaction: 11322 301
Chapter 24: Testing Stinks and Is a Waste of Money!
may throw:
ä docs.python.org/2/library/unittest.html#unittest.TestCase.
assertRaises
ä bit.ly/2pAxLtm PyTest assertion docs
24.3.8 Use Mock to Keep Unit Tests From Touching the World
Unit tests are not supposed to test things external to the function or method they are call-
ing. Which means that during tests we should not access external APIs, receive emails or
webhooks, or anything that is not part of the tested action. Alas, this causes a conundrum
when you are trying to write a unit test for a function that interacts with an external API.
The Mock library, created by Michael Foord, has as one of its features the capability to
briefly monkey-patch libraries in order to make them return the exact value that we want.
This way we are testing not the availability of the external API, but instead just the logic of
our code.
In the example displayed below, we are monkey-patching a function in a mythical Ice Cream
API library so our test code doesn’t access anything external to our application.
Example 24.5: Using Mock to Keep Unit Tests From Touching the World
import icecreamapi
class TestIceCreamSorting(TestCase):
self.assertEqual(
flavors,
['chocolate', 'strawberry', 'vanilla', ]
Now let’s demonstrate how to test the behavior of the list_flavors_sorted() function
when the Ice Cream API is inaccessible.
@mock.patch.object(icecreamapi, 'get_flavors')
def test_flavor_sort_failure(self, get_flavors):
# Instructs icecreamapi.get_flavors() to throw a FlavorError.
get_flavors.side_effect = icecreamapi.FlavorError()
As an added bonus for API authors, here’s how we test how code handles two different
python-requests connection problems:
@mock.patch.object(requests, 'get')
def test_request_failure(self, get):
"""Test if the target site is inaccessible."""
Please submit
Prepared exclusively issues to github.com/feldroy/two-
for LAKLAK scoops- of- django- 3.x/issues
LAKLAK ([email protected]) Transaction: 11322 303
Chapter 24: Testing Stinks and Is a Waste of Money!
get.side_effect = requests.exception.ConnectionError()
with self.assertRaises(CantListFlavors):
list_flavors_sorted()
@mock.patch.object(requests, 'get')
def test_request_failure_ssl(self, get):
"""Test if we can handle SSL problems elegantly."""
get.side_effect = requests.exception.SSLError()
with self.assertRaises(CantListFlavors):
list_flavors_sorted()
ä docs.python.org/3/library/unittest.html#assert-methods
ä docs.djangoproject.com/en/3.2/topics/testing/tools/#assertions
ä assertRaises
ä assertRaisesMessage()
ä assertCountEqual()
ä assertDictEqual()
ä assertFormError()
ä assertContains() Check status 200, checks in response.content.
ä assertHTMLEqual() Amongst many things, ignores whitespace differences.
ä assertInHTML() Great for confirming a tiny bit of HTML in a giant HTML page.
ä assertJSONEqual()
ä assertURLEqual()
If one thinks this is boring, well, we’ve found that a good way to deal with an impossible-
to-debug problem is to document the related tests. By the time the tests are documented,
we have either figured out the problem or we have documented tests. Either case is a win!
Resources:
ä hynek.me/articles/document-your-tests/
ä interrogate.readthedocs.io/ - A library that checks code bases for missing
docstrings
Integration tests are a great way to confirm that ‘all the pieces’ are working. We can confirm
that our users will see what they are supposed to see and our APIs are functioning correctly.
Please submit
Prepared exclusively issues to github.com/feldroy/two-
for LAKLAK scoops- of- django- 3.x/issues
LAKLAK ([email protected]) Transaction: 11322 305
Chapter 24: Testing Stinks and Is a Waste of Money!
ä Integration tests are fragile compared to unit tests. A small change in a component
or setting can break them. We’ve yet to work on a significant project where at least
one person wasn’t forever blocked from running them successfully.
Even with these problems, integration tests are useful and worth considering adding to our
testing stack.
–Kent Beck
Let’s say you are confident of your coding skill and decide to skip testing to increase your
speed of development. Or maybe you feel lazy. It’s easy to argue that even with test genera-
tors and using tests instead of the shell, they can increase the time to get stuff done.
Oh, really?
That’s when the small amount of work you did up front to add tests saves you a lot of work.
For example, in the summer of 2010, Django 1.2 was the standard when we started Django
Packages (djangopackages.org). Since then the project has stayed current with new
Django versions, which has been really useful. Because of its pretty good test coverage,
moving it up a version of Django (or the various dependencies) has been easy. Our path to
upgrade:
If Django Packages didn’t have tests, any time we upgraded anything we would have to click
through dozens and dozens of scenarios manually, which is error-prone. Having tests means
we can make changes and dependency upgrades with the confidence that our users (i.e. the
Django community) won’t have to deal with a buggy experience.
We advocate following these steps because most of the time we want to only test our own
project’s apps, not all of Django, and the myriad of third-party libraries that are the building
blocks of our project. Testing those ‘building blocks’ takes an enormous amount of time,
which is a waste because most are already tested or require additional setup of resources.
If we have nothing except for the default tests for two apps, we should get a response that
looks like:
OK
Please submit
Prepared exclusively issues to github.com/feldroy/two-
for LAKLAK scoops- of- django- 3.x/issues
LAKLAK ([email protected]) Transaction: 11322 307
Chapter 24: Testing Stinks and Is a Waste of Money!
This doesn’t look like much, but what it means is that we’ve constrained our application to
only run the tests that you want. Now it’s time to go and look at and analyze our embarrass-
ingly low test coverage numbers.
ä /Users/audreyr/code/twoscoops/twoscoops/
ä /Users/pydanny/projects/twoscoops/twoscoops/
ä c:\ twoscoops
After this runs, in the <project_root> directory there is a new directory called htmlcov/ . In
the htmlcov/ directory, open the index.html file using any browser.
What is seen in the browser is the test results for our test run. Unless we already wrote some
tests, the total on the front page will be in the single digits, if not at 0%. Click into the
various modules listed and we should see lots of code that’s red-colored. Red is bad.
Let’s go ahead and admit that our project has a low coverage total. If your project has a
low coverage total, you need to admit it as well. It’s okay just so long as we also resolve to
improve the coverage total.
In fact, there is nothing wrong in saying publicly that you are working to improve a project’s
test coverage. Then, other developers (including ourselves) will cheer you on!
If we add a feature or bug fix and coverage is 65% when we start, we can’t merge our code
in until coverage is at least 65% again. At the end of each day, if test coverage goes up by
any amount, it means we’re winning.
Keep in mind that the gradual increase of test coverage can be a very good thing over huge
jumps. Gradual increases can mean that we developers aren’t putting in bogus tests to bump
up coverage numbers; instead, we are improving the quality of the project.
Fortunately, there is the pytest alternatives that requires a lot less boilerplate: pypi.org/
project/pytest-django/
This library is a wrapper around the pytest library. In return for a little bit of extra setup,
this allows for not just the running of unittest-based tests, but also for running any function
(and class/directory/module) prefixed with “test_”. For example, you could write a simple
test that looks like this:
# test_models.py
from pytest import raises
def test_good_choice():
assert Cone.objects.filter(type='sugar').count() == 1
def test_bad_cone_choice():
with raises(Cone.DoesNotExist):
Cone.objects.get(type='spaghetti')
While this example is based on pytest, similar functionality can be used with nose and it’s
nose.tools.raises decorator.
A possible downside of the simplicity of these function-based tests is the lack of inheritance.
If a project needs to have similar behavior over a lot of different tests, then writing tests this
way may not make sense.
Please submit
Prepared exclusively issues to github.com/feldroy/two-
for LAKLAK scoops- of- django- 3.x/issues
LAKLAK ([email protected]) Transaction: 11322 309
Chapter 24: Testing Stinks and Is a Waste of Money!
24.11 Summary
All of this might seem silly, but testing can be very serious business. In a lot of developer
groups this subject, while gamified, is taken very seriously. Lack of stability in a project can
mean the loss of clients, contracts, and even employment.
Given a choice between ice cream and writing great documentation, most Python developers
would probably choose to write the documentation. That being said, writing documentation
while eating ice cream is even better.
When you have great documentation tools like Markdown , MkDocs, Sphinx, Myst-
Parser for Sphinx, and you actually can’t help but want to add docs to your projects.
Here is the formal GFM specification and a sample project which benefits from using it:
ä github.github.com/gfm/
ä django-rest-framework.org/
While it’s possible to study the formal documentation for GFM and learn the basics, here
is a quick primer of some very useful commands you should learn.
# H1 Header
**emphasis (bold/strong)**
*italics*
_underline_
Please submit
Prepared exclusively issues to github.com/feldroy/two-
for LAKLAK scoops- of- django- 3.x/issues
LAKLAK ([email protected]) Transaction: 11322 311
Chapter 25: Documentation: Be Obsessed
## Section Header
- First bullet
- Second bullet
- Indented Bullet
``` python
def like():
print("I like Ice Cream")
for i in range(10):
like()
```
``` js
console.log("Don't use alert()");
```