Python and Django are probably the best tools I’ve used in my work so far. Easy to understand and get started in, and very capable. 

However, Django can be frustrating to work with at times. For example, Django arranges its apps and models by alphabetical order by default. This might work 90% of the time, but what if you need to arrange your apps in a different order? And to that extent, what about the models (or table names)? 

This post will dive into how I accomplished that for my project.

So how is it done?

 

There are actually quite a lot of examples on Stack Overflow and the web showing how to reorder apps. This post has a pretty simple example, but doesn’t go very in-depth. Furthermore, there is a package called django-modeladmin-reorder. If you want to use that, refer to this Stack Overflow post; depending on your version of Django and Python, you might need to refer to this post.

However, if you don’t want to rely on an outside package, want something that is fairly easy to understand, is reusable, and maintainable, then read on.

 

Reorder Apps

 

This will serve as the baseline for our project. You’re going to need to do the following:

  1. Make a new folder called templatetags. Place it in your app folder (not your project folder!)
  2. Inside that folder, create a new .py file. You can name it reorder.py
  3. In your templates/admin folder, create a new .html file. We’ll name this reordered_app_list.html
  4. You’ll need an index.html file in your templates/admin folder as well. If you already have this, we’ll be modifying it later.

 

reorder.py

 

You’re going to need to import several packages from Django. 

 

from django.contrib.admin import site
from django.apps import apps
from django import template
from django.utils.text import capfirst
from django.contrib.auth.models import User

We are going to register a new template tag, and include it in an html template. 

register = template.Library()

 

@register.inclusion_tag(‘admin/reordered_app_list.html’,
takes_context=True)
def render_model_list(context):

Next, we need a for loop. This will loop through each model that is registered in the site. We need this so that we can build an app dictionary and model dictionary to parse through later.

We also need to declare a few variables within the for loop. 

app_dict = {}

for model, model_admin in site._registry.items():

model_dict = {}

app_label = model._meta.app_label
has_module_perms = User.has_module_perms(User, app_label)
info = (app_label, model._meta.model_name)

 

With that, we need to find the URLs for each individual model and append them into the dictionary. Each url applies to either the admin url, or the add url. The latter is used for either adding a model instance, or editing a current model instance.

if has_module_perms:
      model_dict = {
        ‘name’: capfirst(model._meta.verbose_name_plural),
        ‘object_name’: model._meta.object_name,
      }
    try:
        model_dict[‘admin_url’] = reverse(
             ‘admin:%s_%s_changelist’ % info,
             current_app=site.name
        )
    except NoReverseMatch:
        pass
    try:
        model_dict[‘add_url’] = reverse(
             ‘admin:%s_%s_add’ % info,
             current_app=site.name
        )
    except NoReverseMatch:
        pass

Creating the App Dictionary

 

Next we start building the app dictionary with its associated models. We do this in a conditional that checks the app_dict dictionary to see if it already has an app in it. If not, it creates the new entry.

if app_label in app_dict:
        app_dict[app_label][‘models‘].append(model_dict)
    else:
        app_dict[app_label] = {
            ‘name‘: apps.get_app_config(app_label).verbose_name,
            ‘app_label’: app_label,
            ‘app_url‘: reverse(
                ‘admin:app_list‘,
                kwargs={‘app_label‘: app_label},
                current_app=site.name
             ),
            ‘has_module_perms‘: has_module_perms,
            ‘models‘: [model_dict],
        }

That’s the bulk of the code right there. Now, all that’s left is to sort through app_dict, first for the App names, then the Model names.

Where the Magic happens

Finally! The (fun?) part. Here, we are going to make two lists: an app_order and a model_order list. (Actually, they’re dictionaries).

 

app_ordering = {
   “Survey_App“: 1,
   “Authentication and Authorization“: 2,
}

model_ordering = {
   User Information“: 1,
   “Devices“: 2,
   “User Pets“: 3,

   …

   “Users“:7,

   “Groups“:8,

 

First, we have to make a new app list.

 

app_list = sorted(app_dict.values(), key=lambda x: app_ordering[x[‘name’]])

This will make a new app list dictionary, which we will loop through near the end of the function.

 

# Sort the models by model ordering dict within each app.

for app in app_list:
    app[‘models’].sort(key=lambda x:model_ordering[x[‘name’]])

return {‘app_list’: app_list, ‘request’: context[‘request’]}

 

Apply to a Template

 

Now that we have the function, we need to make a template for it. Remember that tag just before the function?

 

 

@register.inclusion_tag(‘admin/reordered_app_list.html’,
takes_context=True)

 

 

This tells the function that it is going to “include” the template ‘reordered_app_list.html‘. If you haven’t already, go ahead and create that file now.

 

{% load i18n %}
<div>
{% if app_list %}

{% for app in app_list %}

<div class=”app-{{ app.app_label }} module”>

<table>

<caption>
<a href=”{{ app.app_url }}” class=”section” title=”{% blocktrans with name=app.name %}Models in the {{ name }} application{% endblocktrans %}”>{{ app.name }}</a>
</caption>
{% for model in app.models %}
    <tr class=”model-{{ model.object_name|lower }}”>
    {% if model.admin_url %}
        <th scope=”row”><a href=”{{ model.admin_url }}”>{{ model.name }}</a></th>
    {% else %}
        <th scope=”row”>{{ model.name }}</th>

{% if model.add_url %}
    <td><a href=”{{ model.add_url }}” class=”addlink”>{% trans ‘Add’ %}</a></td>
{% else %}
    <td>&nbsp;</td>
{% endif %}

{% if model.admin_url %}
    {% if model.view_only %}
    <td><a href=”{{ model.admin_url }}” class=”viewlink”>{% trans ‘View’ %}</a></td>
    {% else %}
    <td><a href=”{{ model.admin_url }}” class=”changelink”>{% trans ‘Change’ %}</a></td>
    {% endif %}
{% else %}
    <td>&nbsp;</td>
{% endif %}

</tr>

{% endfor %}

</table>

</div>

{% endfor %}
{% else %}
<p>{% trans “You don’t have permission to view or edit anything.” %}</p>
{% endif %}

</div>

The template has built-in template tags that check for app and model urls, and appends the information to the page as needed. 

With that done, we need to modify the index.html page that Django uses. If you haven’t yet, make a new index.html file in your templates/admin folder. We will extend Django’s own index.html content into ours.

 

{% extends “admin/index.html” %}
{% load i18n static admin_urls reorder %}

 

{% block bodyclass %}{{ block.super }} dashboard{% endblock %}

{% block breadcrumbs %}{% endblock %}

{% block content %}
<div id=”content-main”>

{% render_model_list %}

</div>
{% endblock %}

 

In the second line, we are loading the reorder file. This makes our custom template tag available to be used anywhere in the index.html page.

We place the {% render_model_list %} template tag inside of the content block.

With that done, run your server and check out your newly reordered app/model list page!

 

All the Code below!

 

Feel free to use this code for your own projects, and let me know if you have any suggestions! I will update this later with a link to GitHub.

model_dict = {}
app_dict = {}

for model, model_admin in site._registry.items():

    app_label = model._meta.app_label
    has_module_perms = User.has_module_perms(User, app_label)
    info = (app_label, model._meta.model_name)

    if has_module_perms:
      model_dict = {
        ‘name’: capfirst(model._meta.verbose_name_plural),
        ‘object_name’: model._meta.object_name,
      }
    try:
        model_dict[‘admin_url’] = reverse(
             ‘admin:%s_%s_changelist’ % info,
             current_app=site.name
        )
    except NoReverseMatch:
        pass
    try:
        model_dict[‘add_url’] = reverse(
             ‘admin:%s_%s_add’ % info,
             current_app=site.name
        )
    except NoReverseMatch:
        pass

    if app_label in app_dict:
        app_dict[app_label][‘models’].append(model_dict)
    else:
        app_dict[app_label] = {
            ‘name’: apps.get_app_config(app_label).verbose_name,
            ‘app_label’: app_label,
            ‘app_url’: reverse(
                ‘admin:app_list’,
                kwargs={‘app_label’: app_label},
                current_app=site.name
             ),
            ‘has_module_perms’: has_module_perms,
            ‘models’: [model_dict],
        }

app_ordering = {
    “Survey_Server_App”: 1,
    “Authentication and Authorization”: 2,
}

app_list = sorted(app_dict.values(), key=lambda x: app_ordering[x[‘name’]])

model_ordering = {
    “User Information”: 1,
    “Device Information”: 2,
    “Harassment Reports”: 3,
    “Activity Logs”: 4,
    “Support Requests”: 5,
    “GPS Tracks”: 6,
    “Cell Logs”: 7,
    “Traffic Logs”: 8,
    “Phone Logs”: 9,
    “SMS Logs”: 10,
    “Users”: 11,
    “Groups”: 12,
}

# Sort the models by model ordering dict within each app.
for app in app_list:
    app[‘models’].sort(key=lambda x: model_ordering[x[‘name’]])

return {‘app_list’: app_list, ‘request’: context[‘request’]}