Reorder app and models in Django admin

Question:

I want to reorder my apps in my Django admin panel, I saw some responses from another similar question here in SO, so I go for install this method: django-modeladmin-reorder

I follow all the steps and it’s not working. Here’s my actual Django panel

enter image description here

#settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    # Disable Django's own staticfiles handling in favour of WhiteNoise, for
    # greater consistency between gunicorn and `./manage.py runserver`. See:
    # http://whitenoise.evans.io/en/stable/django.html#using-whitenoise-in-development
    'whitenoise.runserver_nostatic',
    'django.contrib.staticfiles',
    'admin_reorder',
    'organization_owners',
    'panel',
    'clients',
]

MIDDLEWARE_CLASSES = (
    'admin_reorder.middleware.ModelAdminReorder',
)


ADMIN_REORDER = (
    # Keep original label and models
    'organization_owners',
    'panel',
    'clients',
)

and also is in my requirements.txt

Django==2.0.1
django-extensions==1.9.8
django-modeladmin-reorder==0.2
djangorestframework==3.7.7
flake8==3.5.0
Asked By: Pilar Figueroa

||

Answers:

I have checked their github repo and it was updated two months ago to support Django 2.0 but it’s Python Package Index version 0.2 was last uploaded on 2016-09-08.

So there are chances that pip installation still installs the version that does not support Django 2.0

Note that the urlresolvers module was deprecated in Django 1.10 and removed in 2.0 (django-modeladmin-reorder still relies on urlresolvers)

What you can do:

  1. If you already know Django==1.8 try django-modeladmin-reorder on that first.

  2. Use their code at GitHub in your project. Here is the link of the latest commit. https://github.com/mishbahr/django-modeladmin-reorder/commit/f21929480c398c2628291d74af2f319421f651f3

Answered By: Dhaval Savalia

If you’re working with Django 2.0 you have to edit middleware.py with this commit. Even the repository has the support for Django 2.0, pip is stalling and older version (as @Dhaval Savalia said).

After that, this step:

Add the ModelAdminReorder to MIDDLEWARE_CLASSES:

MIDDLEWARE_CLASSES = (
    ...
    'admin_reorder.middleware.ModelAdminReorder',
    ...
)

Should be replaced by:

MIDDLEWARE = [ 
        ...
        'admin_reorder.middleware.ModelAdminReorder',
        ...
]

And that’s all.

Answered By: Pilar Figueroa

I recently wrote an article on this. It may help.
We need to use the template tag to re-order the apps and models like below.

add below code in settings

from collections import OrderedDict

APP_ORDER = OrderedDict([
  ("app1", ["Model2", "Model1", "Model3"]),
  ("app2", ["Model2", "Model5", "Model3"]),
  ("app3", ["Model1", "Model6", "Model3"]),
]) 

add below template tags

from django import template
from django.conf import settings


register = template.Library()


def pop_and_get_app(apps, key, app_label):
    for index, app in enumerate(apps):
        if app[key] == app_label:
            obj = apps.pop(index)
            return obj
    return None

@register.filter
def sort_apps(apps):
    new_apps = []
    order = settings.APP_ORDER
    for app_label in order.keys():
        obj = pop_and_get_app(apps, "app_label", app_label)
        new_apps.append(obj) if obj else None
    apps = new_apps + apps
    for app in apps:
        models = app.get("models")
        app_label = app.get("app_label")
        new_models = []
        order_models = settings.APP_ORDER.get(app_label, [])
        for model in order_models:
            obj = pop_and_get_app(models, "object_name", model)
            new_models.append(obj) if obj else None
        models = new_models + models
        app["models"] = models
    return apps

Override the admin/index.html like below

{% for app in app_list|sort_apps %}

Ref: https://learnbatta.com/blog/how-to-re-order-apps-models-django/

Answered By: anjaneyulubatta505

project/admin.py

from django.contrib.admin import AdminSite

class MyAdminSite(AdminSite):
    site_header = 'My Site'
    index_title = ''

    def get_app_list(self, request):
        app_order = [
            'app_1',
            'app_2',
            'auth',
        ]
        app_order_dict = dict(zip(app_order, range(len(app_order))))
        app_list = list(self._build_app_dict(request).values())
        app_list.sort(key=lambda x: app_order_dict.get(x['app_label'], 0))
        for app in app_list:
            if app['app_label'] == 'app_1':
                model_order = [
                    'Model1 verbose name',
                    'Model2 verbose name',
                    'Model3 verbose name',
                ]
                model_order_dict = dict(zip(model_order, range(len(model_order))))
                app['models'].sort(key=lambda x: model_order_dict.get(x['name'], 0))
        return app_list

project/apps.py

from django.contrib.admin.apps import AdminConfig

class MyAdminConfig(AdminConfig):
    default_site = 'project.admin.MyAdminSite'

project/settings.py

INSTALLED_APPS = [
    'project.apps.MyAdminConfig',
    <instead of django.contrib.admin>
    ...
Answered By: tulsluper

I followed anjaneyulubatta505 answer but it only changed the order in the index page to change the order in all admin pages
override app_list.html not index.html

app the template tag from anjaneyulubatta505 answer in any app

from django import template
from django.conf import settings


register = template.Library()


def pop_and_get_app(apps, key, app_label):
    for index, app in enumerate(apps):
        if app[key] == app_label:
            obj = apps.pop(index)
            return obj
    return None

@register.filter
def sort_apps(apps):
    new_apps = []
    order = settings.APP_ORDER
    for app_label in order.keys():
        obj = pop_and_get_app(apps, "app_label", app_label)
        new_apps.append(obj) if obj else None
    apps = new_apps + apps
    for app in apps:
        models = app.get("models")
        app_label = app.get("app_label")
        new_models = []
        order_models = settings.APP_ORDER.get(app_label, [])
        for model in order_models:
            obj = pop_and_get_app(models, "object_name", model)
            new_models.append(obj) if obj else None
        models = new_models + models
        app["models"] = models
    return apps

and add the order in your settings.py

from collections import OrderedDict

APP_ORDER = OrderedDict([
  ("app1", ["Model2", "Model1", "Model3"]),
  ("app2", ["Model2", "Model5", "Model3"]),
  ("app3", ["Model1", "Model6", "Model3"]),
]) 

and this is my app_list.html

{% load i18n admin_tags %}

{% if app_list %}
  {% for app in app_list|sort_apps %}
    <div class="app-{{ app.app_label }} module{% if app.app_url in request.path|urlencode %} current-app{% endif %}">
      <table>
        <caption>
          <a href="{{ app.app_url }}" class="section" title="{% blocktranslate with name=app.name %}Models in the {{ name }} application{% endblocktranslate %}">{{ app.name }}</a>
        </caption>
        {% for model in app.models %}
          <tr class="model-{{ model.object_name|lower }}{% if model.admin_url in request.path|urlencode %} current-model{% endif %}">
            {% if model.admin_url %}
              <th scope="row"><a href="{{ model.admin_url }}"{% if model.admin_url in request.path|urlencode %} aria-current="page"{% endif %}>{{ model.name }}</a></th>
            {% else %}
              <th scope="row">{{ model.name }}</th>
            {% endif %}

            {% if model.add_url %}
              <td><a href="{{ model.add_url }}" class="addlink">{% translate 'Add' %}</a></td>
            {% else %}
              <td></td>
            {% endif %}

            {% if model.admin_url and show_changelinks %}
              {% if model.view_only %}
                <td><a href="{{ model.admin_url }}" class="viewlink">{% translate 'View' %}</a></td>
              {% else %}
                <td><a href="{{ model.admin_url }}" class="changelink">{% translate 'Change' %}</a></td>
              {% endif %}
            {% elif show_changelinks %}
              <td></td>
            {% endif %}
          </tr>
        {% endfor %}
      </table>
    </div>
  {% endfor %}
{% else %}
  <p>{% translate 'You don’t have permission to view or edit anything.' %}</p>
{% endif %}
Answered By: Hazem Elmahy

The response that @tulsluper gave was exactly what I needed to point the way to the solution, but when I clicked on the category (app name) rather than the model in the admin panel it died. The main screen worked, but using the sidebar to drill down into the apps and models did not. My first attempts also screwed up the breadcrumbs, and this answer solves that.

To give a bit more value to the answer, I left access to my custom "Authentication and Authorization" (I just call it Accounts) which includes both User and Group (you have to re-register Group to the custom "users" app to get them both in the same app).

I also left the contrib version of the OAuth2 app in this because I can see where you might have problems figuring out how to re-order that without an example.

If you leave anything out it still will exist in the admin panel doing it this way, it just doesn’t get modified.

Since it turns out Python overloading is unavailable, I have an if in there to test the availability of an argument. Without this split, the breadcrumbs and app detail will break. Everything seems to work great in Django 4.2.4 on the main admin screen and in the sidebar and breadcrumbs (and orders the apps and models both).

Here is my working version of the project/admin.py program:

from django.contrib.admin import AdminSite

class CustomAdminSite(AdminSite):
    site_header = 'My Header'   # default: "Django Administration"
    index_title = 'Index Title' # default: "Site administration"
    site_title = 'Site Title'   # default: "Django site admin"
    site_url = "https://www.MyKewlSite.com"

    # Override the admin presentation order of the apps and models

    def get_app_list(self, request, app_label=None):

        if not app_label:
            # The order of the apps is set here (use the app_label name):
            app_order = [
                'app1',
                'app2',
                'app3',
                'users',
                'oauth2_provider',
            ]
            app_order_dict = dict(zip(app_order, range(len(app_order))))
            app_list = list(self._build_app_dict(request).values())
            app_list.sort(key=lambda x: app_order_dict.get(x['app_label'], 0))
        else:
            # Everything is sorted alphabetically by app and within each app.
            app_dict = self._build_app_dict(request, app_label)
            app_list = sorted(app_dict.values(), key=lambda x: x["name"].lower())

        # Iterate down the app list and set the presentation order of the models:
        for app in app_list:
            if app['app_label'] == 'users':
                model_order = [
                    'Users',
                    'Groups',
                ]
                model_order_dict = dict(zip(model_order, range(len(model_order))))
                app['models'].sort(key=lambda x: model_order_dict.get(x['name'], 0))

            elif app['app_label'] == 'oauth2_provider':
                model_order = [
                    'Applications',
                    'Grants',
                    'Id tokens',
                    'Access tokens',
                    'Refresh tokens',
                ]
                model_order_dict = dict(zip(model_order, range(len(model_order))))
                app['models'].sort(key=lambda x: model_order_dict.get(x['name'], 0))

    
            elif app['app_label'] == 'app3':
                model_order = [
                    'Model 2',
                    'Model 3',
                    'Model 1',
                ]
                model_order_dict = dict(zip(model_order, range(len(model_order))))
                app['models'].sort(key=lambda x: model_order_dict.get(x['name'], 0))
    
            elif app['app_label'] == 'app1':
                model_order = [
                    'Model 1',
                    'Model 3',
                    'Model 2',
                ]
                model_order_dict = dict(zip(model_order, range(len(model_order))))
                app['models'].sort(key=lambda x: model_order_dict.get(x['name'], 0))

        return app_list

You will need a project/apps.py (note that my project is called "admin_site" so you will see that rather than "project" here and in the settings.

from django.contrib.admin.apps import AdminConfig

class CustomAdminConfig(AdminConfig):
    default_site = 'admin_site.admin.CustomAdminSite'

You will also have to edit your project/settings.py and replace the contrib.admin with this custom admin version that we created above:

INSTALLED_APPS = (
    # 'django.contrib.admin',   
    'admin_site.apps.CustomAdminConfig',

Have fun – I have this running so even though it is not the accepted answer, it works well and it is solved like this on my project.

Answered By: FreedomRings