Django community: Community blog posts RSS
This page, updated regularly, aggregates Community blog posts from the Django community.
-
Basics of Webpack
Webpack can be complicated, but it doesn't have to be. Learn some of the basics of webpack, and how you can get started with it while actually understanding it. In this video we start our journey to working with react and django, but first we need to build our javascript and that is where webpack comes in.Watch Now... -
Sending HTML emails with embedded images from Django
Currently I'm working on an application which sends HTML emails with embedded or inline images and multiple CSV and PDF attachments. Let's assume that we will be using an object containing data for our email. I'm providing my models here just as a reference to get the idea: class RenderedReport(models.Model): report = models.ForeignKey('Report', related_name='rendered_reports') approved_by = models.ForeignKey('auth.User', blank=True, null=True) date_rendered = models.DateTimeField(auto_now_add=True) date_queried = models.DateTimeField(blank=True, null=True) date_approved = models.DateTimeField(blank=True, null=True) date_sent = models.DateTimeField(blank=True, null=True) class Meta: get_latest_by = 'date_rendered' ordering = ('-date_rendered',) def __unicode__(self): return str(self.report) class RenderedView(models.Model): rendered_report = models.ForeignKey('RenderedReport', related_name='rendered_views') view = models.ForeignKey('View', related_name='rendered_views') png = ImageField(upload_to='reports') pdf = models.FileField(upload_to='reports', blank=True) csv = models.FileField(upload_to='reports', blank=True) class Meta: ordering = ['view'] def __unicode__(self): return str(self.view) @property def image_filename(self): return os.path.basename(self.png.name) I don't like the idea of re-inventing the wheel, so I will be using a responsive email template from Zurb Studios. I'm skipping the entire HTML template code for brevity because it wasn't modified. We only need this part: <!-- BODY --> <table class="body-wrap"> <tr> <td></td> <td class="container" bgcolor="#FFFFFF"> <div class="content"> <table> <tr> <td> {% for view in views %} <h3>{{ view }}</h3> <p><img src="cid:{{ view.image_filename }}" /></p> {% if not forloop.last %}<p>&nbsp;</p>{% endif %} {% endfor %} </td> … -
Don't keep important data in your Celery queue
The Celery library (previous posts) makes it as easy to schedule a task to run later as calling a function. Just change: send_welcome_email('dan@example.com') to: send_welcome_email.apply_async('dan@example.com') and rely on Celery to run it later. But this introduces a new point of failure for your application -- if Celery loses track of that task and never runs it, then a user will not get an email that we wanted to send them. This could happen, for example, if the server hosting your Celery queue crashed. You could set up a hot backup server for your queue server, but it would be a lot of work. It's simpler if you treat your Celery queue like a cache -- it's helpful to keep around, but if you lose it, your work will still get done. We can do that by changing our pattern for doing things in the background. The key idea is to keep the information about the work that needs to be done in the database, with the rest of our crucial data. For example, instead of: send_welcome_email.apply_async('dan@example.com') we might add a needs_welcome_email Boolean field to our model, and write: user.needs_welcome_email = True user.save() Now we know from our database that this … -
Django Tips #18 Difference Between ugettext And ugettext_lazy
The Django translation API provides several utility functions to help you translate your application. They are all available in the django.utils.translation module. For the most cases you will be using ugettext() and ugettext_lazy(). The “u” prefix stands for “unicode”, and usually it is a better idea to use ugettext() instead of gettext(), and ugettext_lazy() instead of gettext_lazy(), since for the most part we will be working with international charsets. As the name suggests, the “lazy” version of the function holds a reference to the translation string instead of the actual translated text, so the translation occurs when the value is accessed rather than when they’re called. It’s very important to pay attention to this details, because in a Django project there are several cases where the code is only executed once (on Django startup). That happens with definition modules like models, forms and model forms. So, what would happen if you use ugettext() (instead of ugettext_lazy()) in a model definition (let’s say on field labels): Django starts up, the default language is english; Django picks the english version of the field labels; The user changes the website language to spanish; The labels are still displayed in english (because the field … -
PyData Carolinas 2016 Recap
We had a great time at the inaugural PyData Carolinas hosted nearby at IBM in the Research Triangle Park. People from Caktus presented a number of talks and the videos are now up online: Erin Mullaney & Rebecca Muraya-Reach More People: SMS Data Collection with RapidPro Colin Copeland-Identifying Racial Bias in Policing Practices: Open Data Policing Rebecca Conley & Mark Lavin-You Belong with Me: Scraping Taylor Swift Lyrics with Python and Celery Philip Semanchuk-Python, C, C++, and Fortran Relationship Status: It’s Not That Complicated There were also many more fascinating talks about how people in and around North and South Carolina are using Python to do data analysis with Pandas, Jupyter notebooks, and more. It was a great event that brought together the strong communities around data and Python locally to celebrate their overlapping interests. We had a great time meeting folks and reconnecting with old friends at the after hours events hosted by MaxPoint and the Durham Convention & Visitors Bureau. Many thanks to all of the local organizers and sponsors who worked together to put on a great program and event. We can’t wait until the next one! -
Handling statuses in Django #1
Whether you're building up a CMS or a bespoke application, chances are that you will have to handle some states / statuses. Let's discuss your options in Django. -
Handling statuses in Django
Whether you're building up a CMS or a bespoke application, chances are that you will have to handle some states / statuses. Let's discuss your options in Django. -
Django admin template structure
As you all (probably) know, Django is a magnificent Web framework to build the website of your dreams. There is a plethora of tutorials, how-to's, getting-started, django-for-dummies out there in the internet-wild. I am not going to show you the Django basics (models, views, templates or forms to name a few). I just want to share with you the template inheritance of the incredible Django admin contribution package. At this time of writing, I am dreaming of a much more appealing admin interface which is based on the super-fancy AdminLTE template (there is a free version you can download, under the MIT License -- more about licences here). But in order to adopt the new-dreamed template in my own project I must understand the template inheritance workflow of the built-in Django admin app. I know there is a package, already out there (django-adminlte-templates), that offer you the capability of using this admin template but I wanted to deeply understand how the Django admin template system is assembled and working so flawlessly. Lets get to work: The core ones In this section we will describe briefly, the core HTML templates where the rest inherit from. If you do not have Django … -
How to Deploy a Django Application to Digital Ocean
DigitalOcean is a Virtual Private Server (VPS) provider. In my opinion it’s a great service to get started. It’s cheap and very simple to setup. In this tutorial I will guide you through the steps I go to deploy a Django application using Ubuntu 16.04, Git, PostgreSQL, NGINX, Supervisor and Gunicorn. In this tutorial we will be deploying the following Django application: github.com/sibtc/urban-train It’s just an empty Django project I created to illustrate the deployment process. If you are wondering about the name, it is the repository name suggestion GitHub gave. Anyway! Let’s get started. Create a New Droplet Pick the Ubuntu 16.04.1 distribution: Select the size of the Droplet (cloud server): Select the region you want to deploy: Finally pick a name for the Droplet: And click on Create. After that you will see the recently created droplet in your profile: You will receive the root password via email. Now pick the IP Address and ssh into the server: ssh root@107.170.28.172 You will be asked to change the root password upon the first login. If you are using a Unix-like operating system you can use the terminal. If you are on Windows, you can perhaps download PuTTY. Also if … -
Common web site security vulnerabilities
I recently decided I wanted to understand better what Cross-Site Scripting and Cross-Site Request Forgery were, and how they compared to that classic vulnerability, SQL Injection. I also looked into some ways that sites protect against those attacks. Vulnerabilities SQL Injection SQL Injection is a classic vulnerability. It probably dates back almost to punch cards. Suppose a program uses data from a user in a database query. For example, the company web site lets users enter a name of an employee, free-form, and the site will search for that employee and display their contact information. A naive site might build a SQL query as a string using code like this, including whatever the user entered as NAME: "SELECT * FROM employees WHERE name LIKE '" + NAME + "'" If NAME is "John Doe", then we get: SELECT * FROM employees WHERE name LIKE 'John Doe' which is fine. But suppose someone types this into the NAME field: John Doe'; DROP TABLE EMPLOYEES; then the site will end up building this query: SELECT * FROM employees WHERE name LIKE 'John Doe'; DROP TABLE EMPLOYEES;' which might delete the whole employee directory. It could instead do something less obvious but even … -
Season of fixes
The last few months has been dominated by bug-fixing and testing in Evennia-land. A lot more new users appears to be starting to use Evennia, especially from the MUSH world where the Evennia-based Arx, After the Reckoning is, while still in alpha, currently leading the charge. With a new influx of users comes the application of all sorts of use- and edge-cases that stretch and exercise the framework in all the places where it matters. There is no better test of code than new users trying to use it unsupervised! Evennia is holding up well overall but there are always things that can be improved. I reworked the way on-object Attributes was cached (from a stupid but simple way to a lot more sophisticated but harder way) and achieved three times faster performance in certain special cases people had complained about. Other issues also came to view while diving into this, which could be fixed. I reworked the venerable batch command and batchcode processors (these allow to create a game world from a script file) and made their inputs make more sense to people. This was one of the older parts of Evennia and apart from the module needing a … -
How to Use Django's Generic Relations
You probably have already seen Django’s ContentTypes and wondered how to use it or what is it for anyway. Basically it’s a built in app that keeps track of models from the installed apps of your Django application. And one of the use cases of the ContentTypes is to create generic relationships between models. That’s what this post is about. Installation If you didn’t remove anything from settings generated by the python manage.py startproject command, it’s probably already installed. Also Django’s built in authentication system relies on it. Basically just make sure you have it in your INSTALLED_APPS: INSTALLED_APPS = [ ... 'django.contrib.contenttypes', ... ] Example Scenario Let’s say we have one social app where the users can ask and answer questions, up vote, down vote, favorite a question, like a post in the website, etc. To keep track of that we create a model named Activity. See below: class Activity(models.Model): FAVORITE = 'F' LIKE = 'L' UP_VOTE = 'U' DOWN_VOTE = 'D' ACTIVITY_TYPES = ( (FAVORITE, 'Favorite'), (LIKE, 'Like'), (UP_VOTE, 'Up Vote'), (DOWN_VOTE, 'Down Vote'), ) user = models.ForeignKey(User) activity_type = models.CharField(max_length=1, choices=ACTIVITY_TYPES) date = models.DateTimeField(auto_now_add=True) post = models.ForeignKey(Post, null=True) question = models.ForeignKey(Question, null=True) answer = models.ForeignKey(Answer, null=True) comment … -
How kubeadm Initializes Your Kubernetes Master
`kubeadm` is a new tool that is part of the Kubernetes distribution as of 1.4.0 which helps you to install and set up a Kubernetes cluster. One of the most frequent criticisms of Kubernetes is that it's hard to install. `kubeadm` really makes this easier so I suggest you give it a try. The documentation for kubeadm outlines how to set up a cluster but as I was doing that I found how `kubeadm` actually sets up the master to be really interesting so I wanted to share that here. ## The Kubernetes Control Plane The Kubernetes control plane consists of the Kubernetes API server (`kube- apis[...] -
Package of the Week: django-hosts
This is a very handy Django package that I’ve used in a couple of projects. Essentially django-hosts let you serve different parts of your application under different subdomains. For example, let’s say we have a Django application deployed on www.example.com. With this app you can serve an e-commerce under shop.example.com and the help center under help.example.com. And in the end, it’s just a single Django website. It can also be used to host user spaces, with a wildcard, where your users could get their own subdomain like vitor.example.com or erica.example.com. But that require a few tweaks in the DNS configuration. A small caveat of developing with django-hosts is that you will need to do some configurations in your local machine, which can differ if you are using Windows, Linux or Mac. Installation Install it with pip: pip install django-hosts Add the django_hosts to the INSTALLED_APPS: INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'django_hosts', 'core', ] Add the HostsRequestMiddleware in the beginning of the MIDDLEWARE and HostsResponseMiddleware in the end of the MIDDLEWARE: MIDDLEWARE = [ 'django_hosts.middleware.HostsRequestMiddleware', 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django_hosts.middleware.HostsResponseMiddleware', ] Still in the settings.py, add the following configuration variables: ROOT_HOSTCONF = 'mysite.hosts' # … -
List of Useful URL Patterns
The ability to create beautiful and meaningful urls is certainly something I love about the Django Framework. But truth is, it’s very hard to get it right. Honestly I always have to refer to the documentation or to past projects I’ve developed, just to grab the regex I need. So that’s why I thought about creating this post, to serve as a reference guide for common urls. Primary Key AutoField Regex: (?P<pk>\d+) url(r'^questions/(?P<pk>\d+)/$', views.question_details, name='question_details'), Match URL Captures /questions/0/ {'pk': '0'} /questions/1/ {'pk': '1'} /questions/934/ {'pk': '934'} Won't match URL /questions/-1/ /questions/test-1/ /questions/abcdef/ Slug Fields Regex: (?P<slug>[-\w]+) url(r'^posts/(?P<slug>[-\w]+)/$', views.post, name='post'), Match URL Captures /posts/0/ {'slug': '0'} /posts/hello-world/ {'slug': 'hello-world'} /posts/-hello-world_/ {'slug': '-hello-world_'} Won't match URL /posts/hello world/ /posts/hello%20world/ /posts/@hello-world*/ Slug with Primary Key Regex: (?P<slug>[-\w]+)-(?P<pk>\d+) url(r'^blog/(?P<slug>[-\w]+)-(?P<pk>\d+)/$', views.blog_post, name='blog_post'), Match URL Captures /blog/hello-world-159/ {'slug': 'hello-world', 'pk': '159'} /blog/a-0/ {'slug': 'a', 'pk': '0'} Won't match URL /blog/hello-world/ /blog/1/ /blog/helloworld1/ /hello-world-1-test/ Usernames Regex: (?P<username>[\w.@+-]+) url(r'^profile/(?P<username>[\w.@+-]+)/$', views.user_profile), Match URL Captures /profile/vitorfs/ {'username': 'vitorfs'} /profile/vitor.fs/ {'username': 'vitor.fs'} /profile/@vitorfs/ {'username': '@vitorfs'} Won't match URL /profile/*vitorfs/ /profile/$vitorfs/ /profile/vitor fs/ Dates Year Regex: (?P<year>[0-9]{4}) url(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive) Match URL Captures /articles/2016/ {'year': '2016'} /articles/9999/ {'year': '9999'} Won't match URL /articles/999/ Year / Month Regex: (?P<year>[0-9]{4})/(?P<month>[0-9]{2}) url(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive), Match URL … -
Package of the Week: isort
isort is a Python utility / library to sort imports alphabetically, and automatically separated into sections. It’s very useful in Django projects, specially in views where we usually deal with a great amount of imports. Organizing the imports in sections is easy, but to keep them in alphabetical order is very tedious. I don’t know about you, but sometimes I have to run all the letters in my head to make sure 😂. Installation Install it via pip or grab the code from GitHub: pip install isort Usage You can already start using it without any configuration: # sort multiple files isort views.py urls.py # show a diff before applying any change isort views.py --diff # just check for errors isort urls.py --check-only You can also apply the changes or check for errors recursively: # check which files will be sorted isort --recursive --check-only # sort the whole project isort --recursive . Django This is how I like to organize my imports in a Django project: Future imports Python Standard Libraries Django core Third party libraries (related or not to Django) First party libraries (that is, our project’s imports) Local imports You can achieve this by adding a configuration file … -
Django Tips #17 Using QuerySet Latest & Earliest Methods
Similar to the QuerySet methods first and last, the API also offer the earliest and latest methods. They are convenience methods which can be used to enhance the readability of the code. The best way to use it is to define get_latest_by in the model’s Meta class: class Post(models.Model): headline = models.CharField(max_length=150) author = models.ForeignKey(User, on_delete=models.CASCADE) publication_date = models.DateTimeField(blank=True, null=True) change_date = models.DateTimeField(blank=True, null=True) class Meta: get_latest_by = 'publication_date' Then the usage is very straightforward: latest_post = Post.objects.latest() earliest_post = Post.objects.earliest() If you didn’t specify the get_latest_by property, or if you want to use a different field, you can pass it as a parameter on the fly: latest_change = Post.objects.latest('change_date') The earliest and latest methods will raise a DoesNotExist exception if there is no object with the given parameters, that is, the table is empty or it was filtered. It is slightly different from first and last, because it returns None if there is no matching object. Another important thing to note is that the earliest and latest methods might return instances with null dates. But the thing is, the ordering behavior is not consistent between the different databases. So you might want to remove null dates, like so: Post.objects.filter(change_date__isnull=False).latest('change_date') … -
Django Tips #16 Simple Database Access Optimizations
So, I just wanted to share a few straightforward tips about database access optimization. For the most those tips are not something you should go to your code base and start refactoring. Actually for existing code it’s a better idea to profile first (with Django Debug Toolbar for example). But those tips are very easy to start applying, so keep them in mind when writing new code! Accessing Foreign Key Values If you only need the ID of the Foreign Key: Do post.author_id Don't post.author.id If you have a foreign key named author, Django will automatically store the primary key in the property author_id, while in the author property will be stored a lazy database reference. So if you access the id via the author instance, like this post.author.id, it will cause an additional query. Bulk Insert on Many to Many Fields Do user.groups.add(administrators, managers) Don't user.groups.add(administrators) user.groups.add(managers) Counting QuerySets If you only need the count: Do users = User.objects.all() users.count() # Or in template... {{ users.count }} Don't users = User.objects.all() len(users) # Or in template... {{ users|length }} Empty QuerySets If you only need to know if the queryset is empty: Do groups = Group.objects.all() if groups.exists(): # … -
How to Use Django's Flatpages App
Django ships with a Flatpages application that enables the user to create flat HTML pages and store it in the database. It can be very handy for pages like About, Privacy Policy, Cookies Policy and so on. Basically it works like this: You define a master page for the content to be displayed, the user creates a new flatpage in the Django Admin interface, picks a URL and add the content. The user can also select if the page requires login or not. Installation First add the sites and flatpages contrib apps to your INSTALLED_APPS: INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'django.contrib.sites', 'django.contrib.flatpages', ] If you wasn’t using the sites app, you may also need to add a SITE_ID to the settings.py file: SITE_ID = 1 Now update your urls.py with the flatpages urls: from django.conf.urls import url, include from django.contrib import admin urlpatterns = [ url(r'^pages/', include('django.contrib.flatpages.urls')), url(r'^admin/', admin.site.urls), ] Migrate the database: $ python manage.py migrate Operations to perform: Apply all migrations: admin, auth, contenttypes, core, flatpages, sessions, sites Running migrations: Rendering model states... DONE Applying sites.0001_initial... OK Applying flatpages.0001_initial... OK Applying sites.0002_alter_domain_unique... OK Add a default template for the flatpages. The default location is … -
Estimating the Effort of Development of a Django Application
This is a very common problem that every developer will eventually face at some point. Your boss or a client comes with a set of requirements and asks you to estimate how long it will take to implement them. How to precisely (as possible) estimate the effort of development of a Web application? That’s a really tough question. People in the academia have being conducting research on that topic for at least the past 40 years, and yet we fail to get it right. I wanted to write a little bit on that subject to try to highlight the challenges of making an accurate estimation and perhaps share some of my experience about it. The Chaos Report 2015 Every year the Standish Group conducts a survey to investigate the cause of failure of software projects. In 2015, out of the 8,380 investigated projects, 16.2% (success) was delivered on-time and on-budget, 52.7% (challenged) faced challenges and was delivered over-budget and over the time estimate. The other 31.1% (impaired) was cancelled at some point during the development cycle. Check this numbers in relation to the time overruns of the original estimates, from the Chaos Report 2015: Time Overruns For the same combined … -
Store Measurements Sanely with django-measurement
Storing measurement data for multiple types of units is quite a frustrating task. Fortunately with django-measurement providing a good wrapper for python-measurement it makes this almost a non-issue. In this video get a quick overview of how to use django-measurement to store that pesky data.Watch Now... -
Deploying Django with Fabric
This is a followup to my previous posting Deploying Django with Gunicorn and Supervisor. I have been using Fabric for five or even more years. Fabric is a very flexible and powerful tool for automating deployment routines. My fabfile.py is composed for Git, but you can easily change it to Mercurial or even Subversion. It's much easier to deploy when you use deploy keys, but it is still possible to use HTTP. I personally hate entering passwords, so I would recommend using deploy keys and SSH. I'm assuming that you already have installed nginx and configured your database instance. You will only need two commands to bootstrap and deploy: $ fab staging bootstrap # Clones your project from the repository and perform the initial setup $ fab staging deploy # Pushes your latest changes Obviously, replacing staging with production will change your environment. Here's my typical fabfile.py: import os from contextlib import contextmanager from fabric.api import cd, env, prefix, run, sudo, task PROJECT_NAME = 'YOUR PROJECT NAME' PROJECT_ROOT = '/var/www/%s' % PROJECT_NAME VENV_DIR = os.path.join(PROJECT_ROOT, '.venv') REPO = 'git@bitbucket.org:monmar/%s.git' % PROJECT_NAME env.hosts = [] @task def staging(): env.hosts = ['user@staging-server'] env.environment = 'staging' @task def production(): env.hosts = ['user@production-server'] env.environment … -
Django Tips #15 Using Mixins With Class-Based Views
I was reading today the book Two Scoops of Django by Audrey and Daniel Roy Greenfeld and found some interesting tips about using class-based views with mixins. Some general rules to use mixins to compose your own view classes, as suggested by Kenneth Love, which I grabbed from the above mentioned book: The base view classes provided by Django always go to the right; Mixins go to the left of the base view; Mixins should inherit from Python’s built-in object type Following an example to illustrate the rules: class FormMessageMixin(object): @property def form_valid_message(self): return NotImplemented form_invalid_message = 'Please correct the errors below.' def form_valid(self, form): messages.success(self.request, self.form_valid_message) return super(FormMessageMixin, self).form_valid(form) def form_invalid(self, form): messages.error(self.request, self.form_invalid_message) return super(FormMessageMixin, self).form_invalid(form) class DocumentCreateView(FormMessageMixin, CreateView): model = Document fields = ('name', 'file') success_url = reverse_lazy('documents') form_valid_message = 'The document was successfully created!' In a similar way you could reuse the same FormMessageMixin in a UpdateView for example, and also override the default form_invalid_message: class DocumentUpdateView(FormMessageMixin, UpdateView): model = Document fields = ('name', ) success_url = reverse_lazy('documents') form_valid_message = 'The document was successfully updated!' form_invalid_message = 'There are some errors in the form below.' Since Django 1.9 we have the built-in mixins LoginRequiredMixin and UserPassesTestMixin. … -
uWSGI File Wrapper and Python 3.5
A couple of weeks ago we hit a small but hard to track issue in one of our clients. They opened an issue telling us that the downloaded files appeared as corrupt. After closer examination, the downloaded files had 0 bytes. The HTTP response was a solid 200 OK with a 0 bytes file attachment. This was very strange, given that no automatic error reports had been triggered and there was clearly an error. Morover we noticed that this issue in particular happened whenever the backend Django app returned a file in the response. This is quite rare in a production environment, given that you most certainly don’t want the Django app handling files, but rather links to the files. However, one usual exception for this rule are small dynamic reports (for bigger ones you’ll want to send an email link when its ready). On the Django logs we could clearly see that the reports were properly created and no errors were present neither there nor in the nginx logs. That left us to take a peek at the application server logs, in this case, uWSGI. It was there that we got to see the following:io.UnsupportedOperation: fileno During handling of … -
Introducing django-perf-rec, our Django performance testing tool
N.B. This is a cross-post from the YPlan tech blog. During PyCon UK I had the opportunity to work on open-sourcing our in-house Django performance testing tool, which has now been released as django-perf-rec. We created it over two years ago, and have been using and improving it since. It has been helping us to pre-emptively fix performance problems in our code, and now it can help you! In the old days we used to often see performance regressions when we introduced a new feature to existing code, and we’d have to retroactively understand and fix them. For example, we might add a feature that accesses a new foreign key on a model, and because select_related / prefetch_related hadn’t been added to the appropriate QuerySet, we’d see an N+1 query problem appear. Often the problems would only manifest in real slowness in production, as our test and development environments don’t contain much data. We tried to lock these down with Django’s assertNumQueries (docs) in tests like: def test_books(self): with self.assertNumQueries(4): self.client.get('/books/') This worked on a basic level, but if the test failed on you, you’d be left with little information on what caused the failure, and be forced to manually …