<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Deep into Django]]></title><description><![CDATA[I love diving into the core of my favorite framework - Django - discovering and writing about new tech stuff.]]></description><link>https://deepintodjango.com</link><generator>RSS for Node</generator><lastBuildDate>Mon, 20 Apr 2026 18:10:10 GMT</lastBuildDate><atom:link href="https://deepintodjango.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Selection widget for which columns to display in Django admin]]></title><description><![CDATA[When working in the Django admin with a lot of users, you might sometimes want to offer the functionality to users to choose which columns they want to see in the changelist page. For some users some columns might be more relevant than to others.
One...]]></description><link>https://deepintodjango.com/selection-widget-for-which-columns-to-display-in-django-admin</link><guid isPermaLink="true">https://deepintodjango.com/selection-widget-for-which-columns-to-display-in-django-admin</guid><category><![CDATA[Django]]></category><category><![CDATA[Python]]></category><category><![CDATA[widgets]]></category><dc:creator><![CDATA[Jurrian Tromp]]></dc:creator><pubDate>Thu, 22 Jan 2026 17:25:21 GMT</pubDate><content:encoded><![CDATA[<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1707306776149/7f6f24de-dd18-4574-b2e0-093f6781c90b.png" alt class="image--center mx-auto" /></p>
<p>When working in the Django admin with a lot of users, you might sometimes want to offer the functionality to users to choose which columns they want to see in the changelist page. For some users some columns might be more relevant than to others.</p>
<p>One way to achieve this is by putting a button in the top right corner that will lead you to a intermediary page like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1707303521241/a9a4ad71-4ef6-440b-b0fb-57430ae04763.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-using-the-filteredselectmultiple-widget">Using the FilteredSelectMultiple widget</h3>
<p>For this you need a special django form, that will show the so called <code>FilteredSelectMultiple</code> widget that is shown above. This widget takes quite some effort to get working properly, as normally this only works out-of-the-box by using <code>filter_horizontal = [...]</code> or <code>filter_vertical = [...]</code> in a ModelAdmin.</p>
<p>The trick here is to add the <code>class Media</code> which will ensure that the correct javascript is loaded to make this widget work. As you see in the Gist below in <code>filter_columns.html</code> , you also need to include a stylesheet for the form.css or it will not look as nice.</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django <span class="hljs-keyword">import</span> forms
<span class="hljs-keyword">from</span> django.contrib.admin.widgets <span class="hljs-keyword">import</span> FilteredSelectMultiple


<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">FilterColumnsForm</span>(<span class="hljs-params">forms.Form</span>):</span>
    <span class="hljs-string">"""Form with only one field to filter the columns.
    Django widget for horizontal selection, works only in the admin and only with media included.
    """</span>
    filter_columns = forms.MultipleChoiceField(
        label=<span class="hljs-literal">False</span>,
        widget=FilteredSelectMultiple(<span class="hljs-string">'columns'</span>, is_stacked=<span class="hljs-literal">False</span>)
    )

    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Media</span>:</span>
        js = (<span class="hljs-string">'/admin/jsi18n'</span>,)
</code></pre>
<p>This setup allows you to use <code>FilteredSelectMultiple</code> basically everywhere outside the regular admin configuration for if you want to customize the use or options.</p>
<h3 id="heading-modeladmin-mixin-to-create-an-intermediary-page">ModelAdmin mixin to create an intermediary page</h3>
<p>In <code>get_urls()</code> you want to append a new path that for the intermediary page for when you click on the "Columns" button. Based on the <code>list_display</code> parameter you can create some</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">filter_columns</span>(<span class="hljs-params">self, request</span>):</span>
    <span class="hljs-string">"""View for intermediary page when clicking on the "Columns" button.
    Will render an admin view with standard context, showing the `FilterColumnsForm`.
    """</span>
    cl = self.get_changelist_instance(request)

    <span class="hljs-comment"># Determine which to display in the form and which are already selected</span>
    display = []
    selected = []
    all_fields = type(self).list_display
    <span class="hljs-keyword">for</span> field_name <span class="hljs-keyword">in</span> all_fields:
        text = label_for_field(field_name, cl.model, model_admin=cl.model_admin, return_attr=<span class="hljs-literal">False</span>)
        display.append((field_name, text))
        <span class="hljs-keyword">if</span> field_name <span class="hljs-keyword">in</span> cl.list_display:
            selected.append(field_name)

    <span class="hljs-comment"># Initialize form with selected and set the available choices</span>
    form = FilterColumnsForm(initial={<span class="hljs-string">'filter_columns'</span>: selected})
    form[<span class="hljs-string">'filter_columns'</span>].field.choices = display

    <span class="hljs-comment"># Add context for showing the menu and bars</span>
    context = self.admin_site.each_context(request)
    context[<span class="hljs-string">'title'</span>] = _(<span class="hljs-string">'Choose which columns to show'</span>)
    context[<span class="hljs-string">'form'</span>] = form
    context[<span class="hljs-string">'opts'</span>] = self.model._meta  <span class="hljs-comment"># pylint: disable=protected-access</span>
    request.current_app = self.admin_site.name
    <span class="hljs-keyword">return</span> TemplateResponse(request, <span class="hljs-string">'admin/filter_columns.html'</span>, context)
</code></pre>
<p>The whole code can be found <a target="_blank" href="https://gist.github.com/jurrian/7ce23f918f8d06f7b9d5a3dcebb3db38">here</a> on Github:</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="7ce23f918f8d06f7b9d5a3dcebb3db38"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/jurrian/7ce23f918f8d06f7b9d5a3dcebb3db38" class="embed-card">https://gist.github.com/jurrian/7ce23f918f8d06f7b9d5a3dcebb3db38</a></div><p> </p>
<p>Use this structure as it matters where the templates are placed, replace app by your own name:</p>
<blockquote>
<p>app/admin.py<br />app/forms.py<br />app/templates/admin/filter_columns.html<br />app/templates/admin/change_list.html</p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[Managing superusers when using the Django admin as a backend for employees]]></title><description><![CDATA[Out-of-the-box Django comes with a superuser checkbox that can be set on each user in the admin. Superusers are somewhat equivalent to the root user in Linux: it gives all permissions no matter what group you are in.
Staff users and super users
Super...]]></description><link>https://deepintodjango.com/managing-superusers-when-using-the-django-admin-as-a-backend-for-employees</link><guid isPermaLink="true">https://deepintodjango.com/managing-superusers-when-using-the-django-admin-as-a-backend-for-employees</guid><category><![CDATA[Django]]></category><category><![CDATA[Python]]></category><dc:creator><![CDATA[Jurrian Tromp]]></dc:creator><pubDate>Mon, 04 Sep 2023 14:07:41 GMT</pubDate><content:encoded><![CDATA[<p>Out-of-the-box Django comes with a <a target="_blank" href="https://docs.djangoproject.com/en/dev/topics/auth/default/#creating-superusers">superuser</a> checkbox that can be set on each user in the admin. Superusers are somewhat equivalent to the root user in Linux: it gives all permissions no matter what group you are in.</p>
<h3 id="heading-staff-users-and-super-users">Staff users and super users</h3>
<p>Superuser is a powerful feature but if you have other staff users, e.g. employees of your company that also log into the admin, this can be a problem:</p>
<ol>
<li><p>Staff users can change their own user and set superuser for themselves or others, even if they are not supposed to.</p>
</li>
<li><p>Users with superuser permissions can see every admin page and automatically have permissions to view, create, update and delete. Even if you've set certain group and user permissions, these will be overruled by superuser authority.</p>
</li>
</ol>
<p>So from a security and usability perspective, the best thing to do is create dedicated groups for certain employees, with their own permissions. This will also ensure that they will only be bothered with the models that are relevant to them.</p>
<p>There will also be a group of developers and system admins who will be the actual superusers of the system. If appropriate all available permissions can be assigned to this group, you are in control what is off-limits. The superuser feature however will only be used for special elevated occasions, just like the root user.</p>
<h3 id="heading-making-the-superuser-expire">Making the superuser expire</h3>
<p>From experience, people who have superuser access will keep that access. But just like the Linux root user, you should reserve the use of this power for certain (destructive) operations. You don't want people deleting the contents of an entire model by accident. Therefore, it's best to have superuser permission that expires by itself after a while.</p>
<p>Below is a gist of a mixin to change the superuser checkbox into a datetime field. The next time the permission for superuser will be evaluated, the field will be checked set empty if the datetime has expired.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1693831855083/7a7ec180-c9b3-456f-8edb-abbad7e93cbb.png" alt class="image--center mx-auto" /></p>
<p>Apply the mixin like this:</p>
<pre><code class="lang-python"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">User</span>(<span class="hljs-params">ExpiringSuperuserMixin, AbstractUser</span>):</span>
    ...
</code></pre>
<p>And don't forget to set <code>AUTH_USER_MODEL = 'yourapp.User'</code> in your settings.py</p>
<h3 id="heading-special-privilege-for-assigning-superuser">Special privilege for assigning superuser</h3>
<p>At this point, any user that has the "Can change user" permission can also still elevate a user to superuser. To prevent this a <code>can_assign_superuser</code> permission is created that will be checked in <code>UserAdmin.get_readonly_fields()</code>. If the user does not have this permission it will just show the superuser field as read-only.</p>
<p>You probably also want to make the user's "Groups" and "User permission" fields also read-only this way so they cannot assign themselves to things they are not allowed to.</p>
<p>Go ahead and try the code:</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="9c41c936929d9d0ec869f33960d67149"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/jurrian/9c41c936929d9d0ec869f33960d67149" class="embed-card">https://gist.github.com/jurrian/9c41c936929d9d0ec869f33960d67149</a></div>]]></content:encoded></item><item><title><![CDATA[Keeping accurate amounts in Django with CurrencyField]]></title><description><![CDATA[At Wundermart we started saving monetary amounts in Django, we naively started with DecimalField(decimal_places=2) for our financial data. After some time we realized that you will lose data this way when applying calculations, for example:
€100 * 0....]]></description><link>https://deepintodjango.com/keeping-accurate-amounts-in-django-with-currencyfield</link><guid isPermaLink="true">https://deepintodjango.com/keeping-accurate-amounts-in-django-with-currencyfield</guid><category><![CDATA[Django]]></category><category><![CDATA[Python]]></category><category><![CDATA[django rest framework]]></category><dc:creator><![CDATA[Jurrian Tromp]]></dc:creator><pubDate>Fri, 21 Jul 2023 12:41:14 GMT</pubDate><content:encoded><![CDATA[<p>At <a target="_blank" href="https://wundermart.com">Wundermart</a> we started saving monetary amounts in Django, we naively started with <code>DecimalField(decimal_places=2)</code> for our financial data. After some time we realized that you will lose data this way when applying calculations, for example:</p>
<p>€100 * 0.21 VAT = € 82.644628099</p>
<p>When you <em>save</em> this to the database it will round to 2 decimal places. If you then reapply the same VAT again you notice you've lost a cent:</p>
<p>€ 82.64 * 1.21 VAT = € 99.9944 = € 99.99</p>
<p>However, when your field allows for 4 decimal places to be stored in the database, you will have enough information to round exactly to what you started with:</p>
<p>€ 82.6446 * 1.21 VAT = € 99.999966 = € 100.00</p>
<p>The calculation above is done in Python this way:</p>
<p><code>Decimal('99.999966').quantize(Decimal('0.00'), rounding='ROUND_HALF_EVEN')</code></p>
<p>The first argument of quantize determines how many decimals you want to round to.</p>
<p>ROUND_HALF_EVEN is called <a target="_blank" href="https://rounding.to/understanding-the-bankers-rounding/">Banker's rounding</a> and is considered today's standard for rounding monetary values since it's the least biased form of rounding. Though it has to be said that there does not seem to be one uniform standard way for dealing with money, local legislation might be different.</p>
<h3 id="heading-putting-it-in-a-custom-decimalfield">Putting it in a custom DecimalField</h3>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="fb455cad65e1aad5a7e5af610133c4c3"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/jurrian/fb455cad65e1aad5a7e5af610133c4c3" class="embed-card">https://gist.github.com/jurrian/fb455cad65e1aad5a7e5af610133c4c3</a></div><p> </p>
<p>This subclass of DecimalField will enforce all fields that process money to have the same 4-decimal accuracy in the database. Meanwhile, when the field is read-only, it will show 2 decimals for representation.</p>
<p>Note that it will still show 4 decimals when editing in a form, if you'd round this to 2 the same problem will start happening. So it's imperative to keep the decimals in the database as-is and only round them when in the end representing it to the user.</p>
<p>For our situation 4 decimals will always be enough, it could be that your calculations or use-case would require an even higher accuracy. This can be set with <code>db_decimals</code>.</p>
<h3 id="heading-bonus-serializing-currency-with-django-rest-framework">Bonus: serializing currency with Django Rest Framework</h3>
<p>When not editing, CurrencyField will show 2 decimals in the admin forms. However, when you use Django Rest Framework the serialized output in APIs will still show 4 decimals. This might not be what you want, therefore you can use this customized DRF field:</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="bf4faf97d5b34ab9e067700dab539e6b"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/jurrian/bf4faf97d5b34ab9e067700dab539e6b" class="embed-card">https://gist.github.com/jurrian/bf4faf97d5b34ab9e067700dab539e6b</a></div><p> </p>
<p>To show two decimals for a field in the API, use it like this:</p>
<pre><code class="lang-python"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ArticleSerializer</span>(<span class="hljs-params">serializers.ModelSerializer</span>):</span>
    sales_price = CurrencySerializerField(required=<span class="hljs-literal">False</span>)
</code></pre>
<p>Feel free to modify these examples to your needs.</p>
]]></content:encoded></item><item><title><![CDATA[Diff mixin to detect changes in Django models instances]]></title><description><![CDATA[Suppose you change your Django model instance, how do you know if the data actually changed? There are a few ways to achieve this, for example using django-simple-history which will add a database entry for every change to keep history.
However, you ...]]></description><link>https://deepintodjango.com/diff-mixin-to-detect-changes-in-django-models-instances</link><guid isPermaLink="true">https://deepintodjango.com/diff-mixin-to-detect-changes-in-django-models-instances</guid><category><![CDATA[Django]]></category><category><![CDATA[Python]]></category><category><![CDATA[django orm]]></category><dc:creator><![CDATA[Jurrian Tromp]]></dc:creator><pubDate>Tue, 23 May 2023 15:54:46 GMT</pubDate><content:encoded><![CDATA[<p>Suppose you change your Django model instance, how do you know if the data actually changed? There are a few ways to achieve this, for example using <a target="_blank" href="https://django-simple-history.readthedocs.io/en/latest/">django-simple-history</a> which will add a database entry for every change to keep history.</p>
<p>However, you don't necessarily need to store this information in the database, as you can also copy the original data before changing the object. I've made a simple mixin that will copy the original from the database and stores it on the object itself:</p>
<pre><code class="lang-python"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SomeModel</span>(<span class="hljs-params">DiffMixin, models.Model</span>):</span>
    some_attribute = models.IntegerField()

instance = SomeModel()
instance.some_attribute = <span class="hljs-number">1</span>
instance.save()

instance.some_attribute = <span class="hljs-number">2</span>
instance.is_changed(<span class="hljs-string">'some_attribute'</span>)
</code></pre>
<p>The mixin copies the original just before saving and deleting and keeps the latest around as long as the object lives as <code>_original</code> .</p>
<p>You can expand on the mixin by for example providing more advanced comparison over all fields. When serializing the current and the original to a dictionary, <a target="_blank" href="https://dictdiffer.readthedocs.io/en/latest/">dictdiffer.diff()</a> could be a tool to establish a diff between the two.</p>
<p>Feel free to use and tweak my DiffMixin to your own needs:</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="80d71ac998a6a301b455d730d6199722"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/jurrian/80d71ac998a6a301b455d730d6199722" class="embed-card">https://gist.github.com/jurrian/80d71ac998a6a301b455d730d6199722</a></div>]]></content:encoded></item><item><title><![CDATA[Show deleted entries in Django Simple History]]></title><description><![CDATA[The django-simple-history package is awesome as it shows you all the activity on a certain Django model object. It allows seeing how it was mutated over time and who changed it. However, there is one caveat: you cannot easily see what objects were de...]]></description><link>https://deepintodjango.com/show-deleted-entries-in-django-simple-history</link><guid isPermaLink="true">https://deepintodjango.com/show-deleted-entries-in-django-simple-history</guid><category><![CDATA[django-simple-history]]></category><category><![CDATA[djangoql]]></category><category><![CDATA[Python]]></category><category><![CDATA[Django]]></category><dc:creator><![CDATA[Jurrian Tromp]]></dc:creator><pubDate>Mon, 24 Apr 2023 12:55:43 GMT</pubDate><content:encoded><![CDATA[<p>The <a target="_blank" href="https://django-simple-history.readthedocs.io/en/latest/">django-simple-history</a> package is awesome as it shows you all the activity on a certain Django model object. It allows seeing how it was mutated over time and who changed it. However, there is one caveat: you cannot easily see what objects were deleted as there is simply no direct reference anymore.</p>
<p>The problem had already been raised with some solutions already been provided, but these mainly focus on providing a filter to make them visible. The Gist below adds a new history page on the model's change list page. The benefit here is that you can override the <code>BaseHistoryAdmin</code> to add for example filters or search functionality, that you would not have normally.</p>
<p>For example, to combine Django Simple History with <a target="_blank" href="https://github.com/ivelum/djangoql">DjangoQL</a> you can add advanced searching like this:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django.contrib <span class="hljs-keyword">import</span> admin
<span class="hljs-keyword">from</span> djangoql.admin <span class="hljs-keyword">import</span> DjangoQLSearchMixin


<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SomeHistoryAdmin</span>(<span class="hljs-params">DjangoQLSearchMixin, BaseHistoryAdmin</span>):</span>
    <span class="hljs-comment"># Add your specific DjangoQL settings here</span>
    ...

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SomeModelAdmin</span>(<span class="hljs-params">WMSimpleHistoryAdmin</span>):</span>
    history_admin = SomeHistoryAdmin
    <span class="hljs-comment"># The rest of your model settings</span>
    ...

admin.site.register(SomeModel, SomeModelAdmin)
</code></pre>
<p>For adding additional filters to the history admin you could do something similar by specifying <code>SomeHistoryAdmin.list_filter</code>, by default it's already filtered on <code>history_type</code> so you can quickly identify which objects were added, changed or deleted.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1682339891713/e62ffc09-04ea-4a59-9839-221b7cdb0450.png" alt="Using DjangoQL to query orders that I deleted" class="image--center mx-auto" /></p>
<p>This example shows how to use DjangoQL to query orders that were deleted. By adding <code>templates/admin/change_list.html</code> a button will be created on <em>all</em> model change list pages just before the regular Add button, it will refer to the history page. Beware that this also adds a button on admin pages that are not based on <code>WMSimpleHistoryAdmin</code>.</p>
<p>Go ahead and tweak the Gist to your own needs:</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="a932b5064255602dcc6055f6475316ea"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/jurrian/a932b5064255602dcc6055f6475316ea" class="embed-card">https://gist.github.com/jurrian/a932b5064255602dcc6055f6475316ea</a></div>]]></content:encoded></item><item><title><![CDATA[JWT Authentication class for python requests]]></title><description><![CDATA[When using the requests library in Python you can use some built-in auth mechanisms like HTTPBasicAuth and HTTPDigestAuth. However, I could not find any package that allows doing a simple JSON Web Token (JWT) authentication, so I decided to make a sn...]]></description><link>https://deepintodjango.com/jwt-authentication-class-for-python-requests</link><guid isPermaLink="true">https://deepintodjango.com/jwt-authentication-class-for-python-requests</guid><category><![CDATA[JWT]]></category><category><![CDATA[Requests]]></category><category><![CDATA[authentication]]></category><category><![CDATA[Django]]></category><category><![CDATA[Python]]></category><dc:creator><![CDATA[Jurrian Tromp]]></dc:creator><pubDate>Mon, 13 Mar 2023 12:47:19 GMT</pubDate><content:encoded><![CDATA[<p>When using the <a target="_blank" href="https://requests.readthedocs.io/en/latest/user/authentication/">requests library</a> in Python you can use some built-in auth mechanisms like HTTPBasicAuth and HTTPDigestAuth. However, I could not find any package that allows doing a simple JSON Web Token (JWT) authentication, so I decided to make a snippet.</p>
<pre><code class="lang-python">jwt_auth = JWTAuth(
    auth_url=<span class="hljs-string">'https://endpoint.example/api/v1/auth'</span>,
    api_payload={
        <span class="hljs-string">'api_key'</span>: <span class="hljs-string">'&lt;API_KEY&gt;'</span>,
        <span class="hljs-string">'api_secret'</span>: <span class="hljs-string">'&lt;API_SECRET&gt;'</span>,
    }
)
<span class="hljs-comment"># Using it directly</span>
requests.get(<span class="hljs-string">'some-url'</span>, auth=jwt_auth)

<span class="hljs-comment"># Or when applied to a session for all requests:</span>
session = requests.Session()
session.auth = jwt_auth
</code></pre>
<p>JWTAuth needs to know your <code>auth_url</code> and <code>api_payload</code> to be able to authenticate with the server on your first request. It will only work when you receive back an access and refresh token. Based on your situation and API response, you might need to change this a little to make it work.</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="7a142e805bd1d8eb6f10b1b51ebef308"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/jurrian/7a142e805bd1d8eb6f10b1b51ebef308" class="embed-card">https://gist.github.com/jurrian/7a142e805bd1d8eb6f10b1b51ebef308</a></div>]]></content:encoded></item><item><title><![CDATA[Reducing queries for ForeignKeys in Django admin inlines]]></title><description><![CDATA[Speed up with raw_id_fields
ForeignKeys in the Django admin show as a select-box (<select>) by default. This will result in a query for every item in the select, only to get the name. This is not a problem if the model you're referencing will always ...]]></description><link>https://deepintodjango.com/reducing-queries-for-foreignkeys-in-django-admin-inlines</link><guid isPermaLink="true">https://deepintodjango.com/reducing-queries-for-foreignkeys-in-django-admin-inlines</guid><category><![CDATA[Django]]></category><category><![CDATA[prefetching]]></category><category><![CDATA[optimization]]></category><category><![CDATA[performance]]></category><category><![CDATA[Python]]></category><dc:creator><![CDATA[Jurrian Tromp]]></dc:creator><pubDate>Wed, 11 Jan 2023 14:47:05 GMT</pubDate><content:encoded><![CDATA[<h3 id="heading-speed-up-with-rawidfields">Speed up with raw_id_fields</h3>
<p>ForeignKeys in the Django admin show as a select-box (&lt;select&gt;) by default. This will result in a query for every item in the select, only to get the name. This is not a problem if the model you're referencing will always be small. But growing models with significant rows will become a problem soon. Having 3 times the same select-box with 1000 items will result in 3000 queries. It will also make the select unusable.</p>
<p>Instead, you could use the <code>ModelAdmin.raw_id_fields</code> which turns it into an input widget with a search icon. It will only query the name of the selected item:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673363860522/1896e32a-2d81-4fe6-b602-6d82dcf6fa67.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-prefetching-in-inlines">Prefetching in inlines</h3>
<p><code>raw_id_fields</code> can also be used in ModelAdmin inlines. However, when you have a lot of these inlines on a page, it's gonna hurt. Having 180 items will result in at least that amount of queries, and usually even double or more.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673364347375/8e29e55d-4bea-417b-925a-f26f45d42cfb.png" alt class="image--center mx-auto" /></p>
<p>Less queries = faster load, so to cope with this you can use <code>prefetch_related</code> which I <a target="_blank" href="https://deepintodjango.com/prefetching-in-django">wrote about earlier</a>. Apply the same <code>get_queryset</code> technique on the <code>ModelAdmin</code>, this cached prefetch data will also be available to your inlines.</p>
<p>This <em>should</em> reduce the number of queries to just a few for the entire page. But unfortunately there is a caveat...</p>
<h3 id="heading-the-bug-in-foreignkeyrawidwidget">The bug in ForeignKeyRawIdWidget</h3>
<p>Unfortunately, there is <a target="_blank" href="https://code.djangoproject.com/ticket/29294#comment:2">currently a bug</a> in the Django issue tracker that prevents the widget used by<code>raw_id_fields</code> in inlines from using the prefetched values.</p>
<blockquote>
<p><strong>TL;DR:</strong> in the Django code the related<code>ForeignKeyIdWidget</code> has to do a <code>QuerySet.get()</code> to get the name of the item. By doing so, the already cached prefetch_related values are ignored. In the said bug issue I proposed a solution, but it proved to be changing too much Django code in other places as well to be accepted.</p>
</blockquote>
<p>To work around this problem some code needs to be patched, a snippet with a mixin can be found lower on this page. The example below shows how to implement the patched <code>RawIdWidgetAdminMixin</code> in your code. Note that the mixin should precede ModelAdmin to work. Thanks to the mixin and <code>prefetch_related</code> your page will now consume way fewer queries!</p>
<pre><code class="lang-python"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CustomAdmin</span>(<span class="hljs-params">RawIdWidgetAdminMixin, ..., ModelAdmin</span>):</span>
    inlines = (<span class="hljs-string">'some-inline'</span>,)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_queryset</span>(<span class="hljs-params">self, request</span>):</span>
        <span class="hljs-keyword">return</span> (
            super().get_queryset(request)
            .prefetch_related(<span class="hljs-string">'your-inline-model'</span>)
        )
</code></pre>
<p>The patched ForeignKeyRawIdWidget can be found in this Github Gist I posted below. Perhaps the most important line is 56 where an object is returned (instead of a plain value). This ultimately prevents the <code>get()</code> as on line 30, we can get the attributes from the object instead of having to query them for each item.</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="25a872a1c160a0e8d5a6ed8c47bf0382"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/jurrian/25a872a1c160a0e8d5a6ed8c47bf0382" class="embed-card">https://gist.github.com/jurrian/25a872a1c160a0e8d5a6ed8c47bf0382</a></div><p> </p>
<p>The patch cannot be much smaller than this, as one change triggers another down the line. It can probably be smaller by monkey-patching the classes, but I prefer to keep it clean by subclassing.</p>
<h3 id="heading-benchmark">Benchmark</h3>
<p>A change-view page with 180 inline items with relations:</p>
<ol>
<li><p>Using no raw_id_fields: 1658 queries</p>
</li>
<li><p>With raw_id_fields and prefetching in Django 3.1: 384 queries</p>
</li>
<li><p>Patched raw_id_fields and prefetching: 18 queries</p>
</li>
</ol>
<p>This proves that if you have a many inline items on a page, the mixin can reduce the amount of queries. Using <code>raw_id_fields</code> already drastically improves queries compared to having select-boxes. The unpatched <code>raw_id_fields</code> still produces an unacceptable high number of queries, because prefetching is not working due to the bug. This number of queries can still cause serious query times, resulting in your page to be very slow.</p>
<p>Maybe in the future this bug will be fixed in Django, so this patch will not be necessary anymore.</p>
]]></content:encoded></item><item><title><![CDATA[Handling Django exceptions in the middleware]]></title><description><![CDATA[A problem that many Django programmers face is uncaught exceptions triggering a 500 HTTP response. Even when you think you have covered all cases, there might be some external package that raises something else than you expect.
Most people solve this...]]></description><link>https://deepintodjango.com/handling-django-exceptions-in-the-middleware</link><guid isPermaLink="true">https://deepintodjango.com/handling-django-exceptions-in-the-middleware</guid><category><![CDATA[Django]]></category><category><![CDATA[exceptionhandling]]></category><category><![CDATA[Middleware]]></category><category><![CDATA[Python]]></category><dc:creator><![CDATA[Jurrian Tromp]]></dc:creator><pubDate>Sun, 08 Jan 2023 15:02:21 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/18iU7bHCjeo/upload/3d8861a2c5912cf73cc59d083fd06da8.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>A problem that many Django programmers face is uncaught exceptions triggering a 500 HTTP response. Even when you think you have covered all cases, there might be some external package that raises something else than you expect.</p>
<p>Most people solve this by writing a broad exception on a higher level to cope with these unexpected cases. This will do of course but it's better to write code based on what you know should happen, and have a backstop in case something unexpected happens.</p>
<p>One elegant way to cope with this is by catching these uncaught exceptions in your Django middleware. The example below shows how to catch an exception raised in a Django admin action:</p>
<p>exceptions.py</p>
<pre><code class="lang-python"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CustomMiddlewareException</span>(<span class="hljs-params">Exception</span>):</span>
    <span class="hljs-string">"""This exception will be caught by middleware."""</span>
    <span class="hljs-keyword">pass</span>
</code></pre>
<p>admin.py</p>
<pre><code class="lang-python"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CustomAdmin</span>(<span class="hljs-params">ModelAdmin</span>):</span>
    actions = (<span class="hljs-string">'some_action'</span>,)
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">some_action</span>(<span class="hljs-params">self, request, queryset</span>):</span>
        <span class="hljs-keyword">raise</span> CustomMiddlewareException(<span class="hljs-string">'Something happened while processing an action'</span>)
</code></pre>
<p>middleware.py</p>
<pre><code class="lang-python"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ExceptionHandlingMiddleware</span>:</span>
    <span class="hljs-string">"""Handle uncaught exceptions instead of raising a 500.
    """</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self, get_response</span>):</span>
        self.get_response = get_response

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__call__</span>(<span class="hljs-params">self, request</span>):</span>
        <span class="hljs-keyword">return</span> self.get_response(request)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">process_exception</span>(<span class="hljs-params">self, request, exception</span>):</span>
        <span class="hljs-keyword">if</span> isinstance(exception, CustomMiddlewareException):
            <span class="hljs-comment"># Show warning in admin using Django messages framework</span>
            messages.warning(request, str(exception))
            <span class="hljs-comment"># Or you could return json for your frontend app</span>
            <span class="hljs-keyword">return</span> JsonResponse({<span class="hljs-string">'error'</span>: str(exception)})

        <span class="hljs-keyword">return</span> <span class="hljs-literal">None</span>  <span class="hljs-comment"># Middlewares should return None when not applied</span>
</code></pre>
<p>settings.py</p>
<pre><code class="lang-python">MIDDLEWARE = [
    ...
    <span class="hljs-string">'your-project-name.middleware.ExceptionHandlingMiddleware'</span>,
]
</code></pre>
<p>Always make sure this is the last middleware in the list, Django will process the middlewares top-down and might handle other exceptions in more specialized middlewares. Your new middleware should be a last resort when all else has failed.</p>
<p>The middleware can also be used to return a 400 Bad Request. Several types of exceptions can be evaluated resulting in different responses. It might be a good idea to end with a broad exception to make sure everything will always be handled.</p>
]]></content:encoded></item><item><title><![CDATA[Prefetching in Django: less queries]]></title><description><![CDATA[A lot has already been written about prefetching, as it is one of the best ways to improve performance for slow admin pages. I'd like to share some tricks to be able to work with large amounts of rows and inlines.
Using prefetch_related and select_re...]]></description><link>https://deepintodjango.com/prefetching-in-django</link><guid isPermaLink="true">https://deepintodjango.com/prefetching-in-django</guid><category><![CDATA[Django]]></category><category><![CDATA[performance]]></category><category><![CDATA[orm]]></category><category><![CDATA[optimization]]></category><category><![CDATA[Python]]></category><dc:creator><![CDATA[Jurrian Tromp]]></dc:creator><pubDate>Wed, 06 Apr 2022 22:50:59 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1649287280418/TLelNyGzw.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>A lot has already been written about prefetching, as it is one of the best ways to improve performance for slow admin pages. I'd like to share some tricks to be able to work with large amounts of rows and inlines.</p>
<h3 id="heading-using-prefetchrelated-and-selectrelated">Using prefetch_related and select_related</h3>
<p>The best way to reduce the amount of queries by inlines is to use <a target="_blank" href="https://docs.djangoproject.com/en/dev/ref/models/querysets/#prefetch-related">prefetch_related</a>, this will preload the things in a few queries. Prefetch_related will do the joining in Python, <a target="_blank" href="https://docs.djangoproject.com/en/dev/ref/models/querysets/#select-related">select_related</a> will actually create SQL joins. Depending on your use-case the one may be faster than the other, the best way to find out is to experiment. I usually go for prefetch_related or a combination, unless the number of rows gets too big. Another thing that will cause you headaches is dynamic attributes. In your admin list page this will quickly cause a lot of queries.</p>
<p>In this example <code>list_display</code> will need to get the name two relations deep causing two joins. By prefetching these relations, Django already populates the instances with the preloaded data.</p>
<pre><code class="lang-python"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SomeAdmin</span>(<span class="hljs-params">admin.ModelAdmin</span>):</span>
    list_display = (<span class="hljs-string">'supplier'</span>,)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_queryset</span>(<span class="hljs-params">self, request</span>):</span>
        <span class="hljs-keyword">return</span> (
            super().get_queryset(request)
            .select_related(<span class="hljs-string">'article__product'</span>)
            .prefetch_related(<span class="hljs-string">'article__supplier'</span>)
        )

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">supplier</span>(<span class="hljs-params">self, obj</span>):</span>
        <span class="hljs-keyword">return</span> obj.article.supplier.name
</code></pre>
<h2 id="heading-prefetching-with-multiple-results">Prefetching with multiple results</h2>
<p>This also works for queries that return multiple results. Suppose that you are trying to list some tags that are associated with your object. Without prefetching, the following would cause <code>all()</code> to do a separate query for each of your rows. That's of course highly inefficient and causes the number of queries to grow linear with the number of rows. Just adding the prefetch here reduces this to just one extra query to fetch all the tags at once.</p>
<pre><code class="lang-python"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SomeAdmin</span>(<span class="hljs-params">admin.ModelAdmin</span>):</span>
    list_display = (<span class="hljs-string">'tags'</span>,)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_queryset</span>(<span class="hljs-params">self, request</span>):</span>
        <span class="hljs-keyword">return</span> (
            super().get_queryset(request)
            .prefetch_related(<span class="hljs-string">'tags'</span>)
        )

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">tags</span>(<span class="hljs-params">self, obj</span>):</span>
        <span class="hljs-keyword">return</span> <span class="hljs-string">', '</span>.join(tag.name <span class="hljs-keyword">for</span> tag <span class="hljs-keyword">in</span> obj.tags.all())
</code></pre>
<p>There is just one important thing to consider here, <strong>as soon as you make a query that ends up in a</strong> <code>filter()</code> <strong>on your queryset, prefetching won't work.</strong> This will cause Django to create a query with a <code>WHERE</code> instead of using the already prefetched data. Note that also a <code>get()</code> will end up in a filtered queryset. Try stay away from filtering if you want to use prefetching, and check the actual queries that Django makes for you. One way is to set the <code>django.db.backends</code> logger to DEBUG to show your queries in your logging. Another way is to install <a target="_blank" href="https://django-debug-toolbar.readthedocs.io/en/latest/">django-debug-toolbar</a>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1709304192915/464121ee-1a50-42a0-a965-3bec88a5d72d.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-annotating-using-subquery">Annotating using Subquery</h3>
<p>But what if you want to show an aggregation? Using annotate you can prevent multiple queries with a Subquery. This ensures that each row will have a value that comes from one single SQL query:</p>
<pre><code class="lang-python"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SomeAdmin</span>(<span class="hljs-params">admin.ModelAdmin</span>):</span>
    list_display = (<span class="hljs-string">'sales_price'</span>,)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_queryset</span>(<span class="hljs-params">self, request</span>):</span>
        sales_price_subquery = Subquery(
            SalesPrice.objects
            .filter(
                kiosk_id=OuterRef(<span class="hljs-string">'kiosk_id'</span>),
                article_id=OuterRef(<span class="hljs-string">'article_id'</span>),
            )
            .values(<span class="hljs-string">'price'</span>)
        )

        <span class="hljs-keyword">return</span> (
            super().get_queryset(request)
            .annotate(sales_price=sales_price_subquery)
        )

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">sales_price</span>(<span class="hljs-params">self, obj</span>):</span>
        <span class="hljs-keyword">return</span> obj.sales_price
</code></pre>
<h3 id="heading-bonus-tip-enable-ordering">Bonus tip: enable ordering</h3>
<p>If you use dynamic attributes in your admin, you will quickly notice you won't be able to sort on them. Using annotate and F-expression you can use an attribute from another model and sort on it using <code>admin_order_field</code>:</p>
<pre><code class="lang-python"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SomeAdmin</span>(<span class="hljs-params">admin.ModelAdmin</span>):</span>
    list_display = (<span class="hljs-string">'article_number'</span>,)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_queryset</span>(<span class="hljs-params">self, request</span>):</span>
        <span class="hljs-keyword">return</span> (
            super().get_queryset(request)
            .annotate(_article_number=F(<span class="hljs-string">'article__article_number'</span>))
        )

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">article_number</span>(<span class="hljs-params">self, obj</span>):</span>
        <span class="hljs-keyword">return</span> obj.article.article_number
    article_number.admin_order_field = <span class="hljs-string">'_article_number'</span>  <span class="hljs-comment"># Use the annotated field for ordering</span>
</code></pre>
<p>Use an underscore to prevent the annotated field from clashing with your dynamic attribute, while keeping the display name the same in the admin.</p>
]]></content:encoded></item></channel></rss>