Ordering admin.ModelAdmin objects in Django Admin
Asked Answered
G

23

54

Let's say I have my pizza application with Topping and Pizza classes and they show in Django Admin like this:

PizzaApp
-
Toppings      >>>>>>>>>>      Add / Change

Pizzas        >>>>>>>>>>      Add / Change

But I want them like this:

PizzaApp
-
Pizzas        >>>>>>>>>>      Add / Change

Toppings      >>>>>>>>>>      Add / Change

How do I configure that in my admin.py?

Giveaway answered 29/12, 2008 at 17:25 Comment(1)
I added it to Django's trac: http://code.djangoproject.com/ticket/9928Giveaway
S
14

This is actually covered at the very bottom of Writing your first Django app, part 7.

Here's the relevant section:

Customize the admin index page

On a similar note, you might want to customize the look and feel of the Django admin index page.

By default, it displays all the apps in INSTALLED_APPS that have been registered with the admin application, in alphabetical order. You may want to make significant changes to the layout. After all, the index is probably the most important page of the admin, and it should be easy to use.

The template to customize is admin/index.html. (Do the same as with admin/base_site.html in the previous section -- copy it from the default directory to your custom template directory.) Edit the file, and you'll see it uses a template variable called app_list. That variable contains every installed Django app. Instead of using that, you can hard-code links to object-specific admin pages in whatever way you think is best.

Shantae answered 29/12, 2008 at 18:41 Comment(3)
This is good to know, but I think the question related to a couple of Model classes in the same installed app. Changing the order of the apps wouldn't help you if you wanted to change the order of your Model classes within a single app.Paramount
It's good thinking. They have to be listed somewhere and maybe I just have to apply a sorting filter there. I'm going to look at this tomorrow (European timezone here...) ThanksGiveaway
I think the easy solution from 林伟雄 bellow is worth to try. Small addition into settings.py only.Concordant
B
49

A workaround that you can try is tweaking your models.py as follows:

class Topping(models.Model):
    .
    .
    .
    class Meta:
        verbose_name_plural = "2. Toppings"

class Pizza(models.Model):
    .
    .
    .
    class Meta:
        verbose_name_plural = "1. Pizzas"

Not sure if it is against the django's best practices but it works (tested with django trunk).

Good luck!

PS: sorry if this answer was posted too late but it can help others in future similar situations.

Baccy answered 17/3, 2011 at 21:56 Comment(4)
This is what I am using. Although a workaround, it is the simplest aproach.Adinaadine
sad. it would say "3 1. Pizzas" at last if you have 3 pizza entries.Spermary
Yes I mean "cool" workaound but seems ridicoulus Django didn't implement a better solution. Ordering by number would feel more obvious or even better the ORDER IN WHICH YOU REGISTER the model, in this way we are adding a number on a display name .. very weird.Finery
In addition, if you need to place something in between, let's say you have 10 models and you need to place something at position 2, you have to manually switch all number 2 --> 10 adding it. very not cool .Finery
V
33

If you want to solve this in 10 seconds just use spaces in verbose_name_plural, for example:

class Topping(models.Model):
    class Meta:
        verbose_name_plural = "  Toppings" # 2 spaces

class Pizza(models.Model):
    class Meta:
        verbose_name_plural = " Pizzas" # 1 space

Of course it isn't ellegant but may work for a while before we get a better solution.

Variation answered 19/10, 2011 at 18:33 Comment(4)
Works in latest Django 1.11. Note that the spaces at the beginning are not rendered by the browser, so it doesn't have a visual impact in the title. Also note that this works with AppConfig classes, to re-order the applications sections too.Stateless
Super janky but definitely the quickest and dirtiest!Alive
10 years later, and still, hands down the fastest, easiest solution. Thanks!Muirhead
The most beautiful method for Django 4.xPlatform
Q
22

I eventually managed to do it thanks to this Django snippet, you just need to be aware of the ADMIN_REORDER setting:

ADMIN_REORDER = (
    ('app1', ('App1Model1', 'App1Model2', 'App1Model3')),
    ('app2', ('App2Model1', 'App2Model2')),
)

app1 must not be prefixed with the project name, i.e. use app1 instead of mysite.app1.

Quenelle answered 8/3, 2012 at 20:38 Comment(4)
Well, this worked on version 1.3, what is the problem you're facing ?Quenelle
What may not be clear is that you need the whole snippet, not just the code block posted in this answer.Aborticide
Inspired by the snippet I wrote github.com/mishbahr/django-modeladmin-reorderEurus
@Eurus would you please update it for Django 2.x or any reference link on how to do for inner pages?Edee
C
19

There's now a nice Django package for that:

https://pypi.python.org/pypi/django-modeladmin-reorder

Caldron answered 14/9, 2015 at 19:56 Comment(0)
R
18

Answer in June 2018

This answer is similar to Vasil's idea

I tried to solve similar problems, and then I saw such the fragment.

I made some modifications based on this clip. The code is as follows.

# myproject/setting.py
...
# set my ordering list
ADMIN_ORDERING = [
    ('pizza_app', [
        'Pizzas',
        'Toppings'
    ]),
]
# Creating a sort function
def get_app_list(self, request):
    app_dict = self._build_app_dict(request)
    for app_name, object_list in ADMIN_ORDERING:
        app = app_dict[app_name]
        app['models'].sort(key=lambda x: object_list.index(x['object_name']))
        yield app


# Covering django.contrib.admin.AdminSite.get_app_list
from django.contrib import admin

admin.AdminSite.get_app_list = get_app_list
...

Note that the sorting list used in this sorting function contains the sorting of all app and its modules in the system. If you don't need it, please design the sorting function according to your own needs.

It works great on Django 2.0

Renunciation answered 19/6, 2018 at 18:10 Comment(9)
If there is a plan to optimize this code or other implementations, please generously share it with me. Thank you!Every
This returned a ValueError: too many values to unpack (expected 2) when I tried to do this. Django 2.1 here.Streptomycin
I don't know the Error from where, Could you show me your exception stack?@StreptomycinEvery
i actually wrote my own script to take care of this. I wrote about it here: zhuzhu-limited.com/django-admin-app-and-model-reorderingStreptomycin
This is the simplest and more elegant solution out there! Well done.Ajmer
This is cool. Works perfectly fine on Django 2.2 with the default Django Admin. All the changes are in settings.py, no need to edit templates, which is required in most other solutions.Uzzial
Great ! Works with 3.1. I have added following after your yield command: explicit_app_names = [app_name for app_name, object_list in ADMIN_ORDERING] for app_name in app_dict: if app_name not in explicit_app_names: yield app_dict[app_name] So I have everything in admin and can reorder only those apps which are important for me. Such explicitly defined apps are then on top, but I think such behaviour is not bad.Concordant
@Streptomycin I'm sorry it took so long to see the news. The link you provided can't be accessed. I don't know if your problem is solved. I guess this problem is caused by the python version, You can try the following modifications: - for app_ name, object_ list in ADMIN_ ORDERING: + for app_ name, object_ list in ADMIN_ ORDERING.items():Every
A less hacky solution is to provide a custom AdminSite in which you override get_app_list().Intelligibility
S
14

This is actually covered at the very bottom of Writing your first Django app, part 7.

Here's the relevant section:

Customize the admin index page

On a similar note, you might want to customize the look and feel of the Django admin index page.

By default, it displays all the apps in INSTALLED_APPS that have been registered with the admin application, in alphabetical order. You may want to make significant changes to the layout. After all, the index is probably the most important page of the admin, and it should be easy to use.

The template to customize is admin/index.html. (Do the same as with admin/base_site.html in the previous section -- copy it from the default directory to your custom template directory.) Edit the file, and you'll see it uses a template variable called app_list. That variable contains every installed Django app. Instead of using that, you can hard-code links to object-specific admin pages in whatever way you think is best.

Shantae answered 29/12, 2008 at 18:41 Comment(3)
This is good to know, but I think the question related to a couple of Model classes in the same installed app. Changing the order of the apps wouldn't help you if you wanted to change the order of your Model classes within a single app.Paramount
It's good thinking. They have to be listed somewhere and maybe I just have to apply a sorting filter there. I'm going to look at this tomorrow (European timezone here...) ThanksGiveaway
I think the easy solution from 林伟雄 bellow is worth to try. Small addition into settings.py only.Concordant
B
5

Here's the snippet Emmanuel used, updated for Django 1.8:

In templatetags/admin_reorder.py:

from django import template
from django.conf import settings
from collections import OrderedDict

register = template.Library()

# from http://www.djangosnippets.org/snippets/1937/
def register_render_tag(renderer):
    """
    Decorator that creates a template tag using the given renderer as the 
    render function for the template tag node - the render function takes two 
    arguments - the template context and the tag token
    """
    def tag(parser, token):
        class TagNode(template.Node):
            def render(self, context):
                return renderer(context, token)
        return TagNode()
    for copy_attr in ("__dict__", "__doc__", "__name__"):
        setattr(tag, copy_attr, getattr(renderer, copy_attr))
    return register.tag(tag)

@register_render_tag
def admin_reorder(context, token):
    """
    Called in admin/base_site.html template override and applies custom ordering
    of apps/models defined by settings.ADMIN_REORDER
    """
    # sort key function - use index of item in order if exists, otherwise item
    sort = lambda order, item: (order.index(item), "") if item in order else (
        len(order), item)
    if "app_list" in context:
        # sort the app list
        order = OrderedDict(settings.ADMIN_REORDER)
        context["app_list"].sort(key=lambda app: sort(order.keys(),
            app["app_url"].strip("/").split("/")[-1]))
        for i, app in enumerate(context["app_list"]):
            # sort the model list for each app
            app_name = app["app_url"].strip("/").split("/")[-1]
            if not app_name:
                app_name = app["name"].lower()
            model_order = [m.lower() for m in order.get(app_name, [])]
            context["app_list"][i]["models"].sort(key=lambda model:
            sort(model_order, model["admin_url"].strip("/").split("/")[-1]))
    return ""

In settings.py:

ADMIN_REORDER = (
    ('app1', ('App1Model1', 'App1Model2', 'App1Model3')),
    ('app2', ('App2Model1', 'App2Model2')),
)

(insert your own app names in here. Admin will place missing apps or models at the end of the list, so long as you list at least two models in each app.)

In your copy of base_site.html:

{% extends "admin/base.html" %}
{% load i18n admin_reorder %}

{% block title %}{{ title }} | {% trans 'Django site admin' %}{% endblock %}

{% block branding %}
{% admin_reorder %}
<h1 id="site-name">{% trans 'Django administration' %}</h1>
{% endblock %}

{% block nav-global %}{% endblock %}
Bushwa answered 15/5, 2015 at 2:37 Comment(0)
B
4

If you're using Suit for the AdminSite you can do menu customization using the menu tag.

Balsam answered 6/11, 2013 at 13:12 Comment(1)
Using the menu tag, I succeeded only in changing the left menu appearance.Artemisia
R
4

You can reorder and hide the apps and models in admin pages. *I use Django 4.1.7.

For example, there are the same 3 models in app1/models.py and app2/models.py as shown below:

# "app1/models.py
# "app2/models.py

from django.db import models

class Model1(models.Model):
    class Meta:
        verbose_name_plural = 'Model1'

class Model2(models.Model):
    class Meta:
        verbose_name_plural = 'Model2'

class Model3(models.Model):
    class Meta:
        verbose_name_plural = 'Model3'

Then, there are the same 3 admins in app1/admin.py and app2/admin.py as shown below:

# "app1/admin.py
# "app2/admin.py

from django.contrib import admin
from .models import Model1, Model2, Model3

@admin.register(Model1)
class Model1Admin(admin.ModelAdmin):
    pass

@admin.register(Model2)
class Model2Admin(admin.ModelAdmin):
    pass

@admin.register(Model3)
class Model3Admin(admin.ModelAdmin):
    pass

Then, 3 apps including AUTHENTICATION AND AUTHORIZATION(auth) are displayed in Home admin page as shown below:

enter image description here

Now, set ADMIN_ORDERING and treat it in overridden get_app_list() in settings.py as shown below. *You can see the original get_app_list() in GitHub:

# "core/settings.py"

ADMIN_ORDERING = (
    ('app2', (
        'Model3', 
        'Model1', 
        'Model2'
    )),
    ('auth', (
        'User', 
        'Group'
    )),
    ('app1', (
        'Model2', 
        'Model3', 
        'Model1'
    ))
)

from django.contrib import admin

def get_app_list(self, request, app_label=None):
    app_dict = self._build_app_dict(request, app_label)
    
    if not app_dict:
        return
        
    NEW_ADMIN_ORDERING = []
    if app_label:
        for ao in ADMIN_ORDERING:
            if ao[0] == app_label:
                NEW_ADMIN_ORDERING.append(ao)
                break
    
    if not app_label:
        for app_key in list(app_dict.keys()):
            if not any(app_key in ao_app for ao_app in ADMIN_ORDERING):
                app_dict.pop(app_key)
    
    app_list = sorted(
        app_dict.values(), 
        key=lambda x: [ao[0] for ao in ADMIN_ORDERING].index(x['app_label'])
    )
     
    for app, ao in zip(app_list, NEW_ADMIN_ORDERING or ADMIN_ORDERING):
        if app['app_label'] == ao[0]:
            for model in list(app['models']):
                if not model['object_name'] in ao[1]:
                    app['models'].remove(model)
        app['models'].sort(key=lambda x: ao[1].index(x['object_name']))
    return app_list

admin.AdminSite.get_app_list = get_app_list

Then, the order of 3 apps and the models is changed in Home admin page as shown below. *The order of them is also changed in other admin pages not just in Home admin page:

enter image description here

Next, remove Model1 in app2, auth itself and all models in app1 to hide them as shown below:

# "core/settings.py"

ADMIN_ORDERING = (
    ('app2', (
        'Model3', 
        # 'Model1', 
        'Model2'
    )),
    # ('auth', (
    #     'User', 
    #     'Group'
    # )),
    ('app1', (
        # 'Model2', 
        # 'Model3', 
        # 'Model1'
    ))
)

# ...

Then, Model1 in app2, auth itself and all models in app1 are hidden in Home admin page as shown below. *They are also hidden in other admin pages not just in Home admin page:

enter image description here

In addition, even if the apps and models which don't exist are set to ADMIN_ORDERING as shown below:

# "core/settings.py"

ADMIN_ORDERING = (
    ('app2', (
        'Model3', 
        'Model1', 
        'Model2',
        'Model4', # Doesn't exist
        'Model5'  # Doesn't exist
    )),
    ('auth', (
        'Model1', # Doesn't exist
        'User', 
        'Group',
        'Model2'  # Doesn't exist
    )),
    ('app1', ( 
        'Group',  # Doesn't exist
        'Model2', 
        'Model3', 
        'Model1',
        'User'    # Doesn't exist
    )),
    ('app3', ( # Doesn't exist
        'Model1', # Doesn't exist
        'Model2', # Doesn't exist
        'Model3'  # Doesn't exist
    ))
)

# ...

Then, they are ignored and not displayed as shown below. Actually, I wanted to return error if the apps and models which don't exist are set to ADMIN_ORDERING but I don't have much time to think about it:

enter image description here

Representation answered 15/5, 2023 at 8:47 Comment(0)
T
3
ADMIN_ORDERING = {
    "PizzaApp": [
        "Pizzas",
        "Toppings",
    ],
}


def get_app_list(self, request):
    app_dict = self._build_app_dict(request)
    for app_name, object_list in app_dict.items():
        if app_name in ADMIN_ORDERING:
            app = app_dict[app_name]
            app["models"].sort(
                key=lambda x: ADMIN_ORDERING[app_name].index(x["object_name"])
            )
            app_dict[app_name]
            yield app
        else:
            yield app_dict[app_name]


admin.AdminSite.get_app_list = get_app_list

This solution works for me, modified the one from 林伟雄.

You get to keep the default auth ordering AND specify your own.

Tamtam answered 23/8, 2021 at 16:50 Comment(0)
S
2

I was looking for a simple solution where I could order the apps by their name in the admin panel. I came up with the following template tag:

from django import template
from django.conf import settings
register = template.Library()

@register.filter
def sort_apps(apps):
    apps.sort(
        key = lambda x:
        settings.APP_ORDER.index(x['app_label'])
        if x['app_label'] in settings.APP_ORDER
        else len(apps)
    )
    print [x['app_label'] for x in apps]
    return apps

Then, just override templates/admin/index.html and add that template tag:

{% extends "admin/index.html" %}
{% block content %}
{% load i18n static sort_apps %}
<div id="content-main">

{% if app_list %}
    {% for app in app_list|sort_apps %}
        <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>
            {% endif %}

            {% 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 %}
                <td><a href="{{ model.admin_url }}" class="changelink">{% trans 'Change' %}</a></td>
            {% else %}
                <td>&nbsp;</td>
            {% endif %}
            </tr>
        {% endfor %}
        </table>
        </div>
    {% endfor %}
{% else %}
    <p>{% trans "You don't have permission to edit anything." %}</p>
{% endif %}
</div>
{% endblock %}

Then customized the APP_ORDER in settings.py:

APP_ORDER = [
    'app1',
    'app2',
    # and so on...
]

It works great on Django 1.10

Stanfill answered 4/12, 2016 at 19:35 Comment(2)
Yep, nice work in Django 2.0. For initialize you can using libraries key in TEMPLATES['OPTIONS'] dict like that: ``` 'libraries': { 'sort_apps': 'src.apps.utils.templatetags.customize_tags', } ``` Without adding to INSTALLED_APPSBalladeer
@Balladeer would you please explain it a little moreEdee
A
2

Here's a version that gives you a bit more flexibility, namely:

  • You can partially define apps ordering, leaving the rest for Django to add to the list
  • You can specify order on modules, or avoid defining it, by using '*' instead
  • Your defined apps ordering will appear first, then all the rest of apps appended after it
  • To check the name of your app, either look at the file apps.py inside the app's directory and check for name property of the class Config(AppConfi): or in case that is not present, use the name of the directory for the app in the project.

Add this code somewhere in your settings.py file:

# ======[Setting the order in which the apps/modules show up listed on Admin]========
# set my ordering list
ADMIN_ORDERING = [
    ('crm', '*'),
    ('property', '*'),
]


# Creating a sort function
def get_app_list(self, request):
    """
    Returns a sorted list of all the installed apps that have been
    registered in this site.

    Allows for:
        ADMIN_ORDERING = [
            ('app_1', [
                'module_1',
                'module_2'
            ]),
            ('app_2', '*'),
        ]
    """

    app_dict = self._build_app_dict(request)

    # Let's start by sorting the apps alphabetically on a list:
    app_list = sorted(app_dict.values(), key=lambda x: x['name'].lower())

    # Sorting the models alphabetically within each app.
    for app in app_list:
        if app['app_label'] in [el[0] for el in ADMIN_ORDERING]:
            app_list.remove(app)
        else:
            app['models'].sort(key=lambda x: x['name'])

    # Now we order the app list in our defined way in ADMIN_ORDERING (which could be a subset of all apps).
    my_ordered_apps = []
    if app_dict:
        for app_name, object_list in ADMIN_ORDERING:
            app = app_dict[app_name]
            if object_list == '*':
                app['models'].sort(key=lambda x: x['name'])
            else:
                app['models'].sort(key=lambda x: object_list.index(x['object_name']))
            my_ordered_apps.append(app)

        # Now we combine and arrange the 2 lists together
        my_ordered_apps.extend(app_list)

    return my_ordered_apps

# Covering django.contrib.admin.AdminSite.get_app_list
from django.contrib import admin
admin.AdminSite.get_app_list = get_app_list
# =========================================

This is nothing more than overwriting the function defined by Django on the file python2.7/site-packages/django/contrib/admin/sites.py.

That get_app_list method of class AdminSite(object): produces a data structure with all apps on the project, including for Django's auth app, such as:

[
  {
    "app_label": "auth",
    "app_url": "/admin/auth/",
    "has_module_perms": "True",
    "models": [
      {
        "add_url": "/admin/auth/group/add/",
        "admin_url": "/admin/auth/group/",
        "name": "<django.utils.functional.__proxy__ object at 0x11057f990>",
        "object_name": "Group",
        "perms": {
          "add": "True",
          "change": "True",
          "delete": "True"
        }
      },
      {
        "add_url": "/admin/auth/user/add/",
        "admin_url": "/admin/auth/user/",
        "name": "<django.utils.functional.__proxy__ object at 0x11057f710>",
        "object_name": "User",
        "perms": {
          "add": "True",
          "change": "True",
          "delete": "True"
        }
      }
    ],
    "name": "<django.utils.functional.__proxy__ object at 0x108b81850>"
  },
  {
    "app_label": "reservations",
    "app_url": "/admin/reservations/",
    "has_module_perms": "True",
    "models": [
      {
        "add_url": "/admin/reservations/reservationrule/add/",
        "admin_url": "/admin/reservations/reservationrule/",
        "name": "<django.utils.functional.__proxy__ object at 0x11057f6d0>",
        "object_name": "ReservationRule",
        "perms": {
          "add": "True",
          "change": "True",
          "delete": "True"
        }
      }
    ],
    "name": "Availability"
  },
  {
    "app_label": "blog",
    "app_url": "/admin/blog/",
    "has_module_perms": "True",
    "models": [
      {
        "add_url": "/admin/blog/category/add/",
        "admin_url": "/admin/blog/category/",
        "name": "Categories",
        "object_name": "Category",
        "perms": {
          "add": "True",
          "change": "True",
          "delete": "True"
        }
      },
      {
        "add_url": "/admin/blog/post/add/",
        "admin_url": "/admin/blog/post/",
        "name": "<django.utils.functional.__proxy__ object at 0x11057f110>",
        "object_name": "Post",
        "perms": {
          "add": "True",
          "change": "True",
          "delete": "True"
        }
      },
      {
        "add_url": "/admin/blog/tag/add/",
        "admin_url": "/admin/blog/tag/",
        "name": "<django.utils.functional.__proxy__ object at 0x11057f390>",
        "object_name": "Tag",
        "perms": {
          "add": "True",
          "change": "True",
          "delete": "True"
        }
      }
    ],
    "name": "Blog"
  },
(...)
]
Ajmer answered 9/6, 2019 at 17:14 Comment(0)
P
0

This is just a wild stab in the dark, but is there any chance that the order in which you call admin.site.register(< Model class >, < ModelAdmin class >) can determine the display order? Actually, I doubt that would work because I believe Django maintains a registry of the Model -> ModelAdmin objects implemented as a standard Python dictionary, which does not maintain iteration ordering.

If that doesn't behave the way you want, you can always play around with the source in django/contrib/admin. If you need the iteration order maintained, you could replace the _registry object in the AdminSite class (in admin/sites.py) with a UserDict or DictMixin that maintains insertion order for the keys. (But please take this advice with a grain of salt, since I've never made these kinds of changes myself and I'm only making an educated guess at how Django iterates over the collection of ModelAdmin objects. I do think that django/contrib/admin/sites.py is the place to look for this code, though, and the AdminSite class and register() and index() methods in particular are what you want.)

Obviously the nicest thing here would be a simple option for you to specify in your own /admin.py module. I'm sure that's the kind of answer you were hoping to receive. I'm not sure if those options exist, though.

Paramount answered 29/12, 2008 at 18:17 Comment(1)
Yes, I was hoping to receive that kind of answer and fearing this kind. I think you're right though, at least from what I could find in the docs. I guess I'll leave this as it is. I don't want to maintain my own version of django.Giveaway
G
0

My solution was to make subclasses of django.contrib.admin.sites.AdminSite and django.contrib.admin.options.ModelAdmin .

I did this so I could display a more descriptive title for each app and order the appearance of models in each app. So I have a dict in my settings.py that maps app_labels to descriptive names and the order in which they should appear, the models are ordered by an ordinal field I provide in each ModelAdmin when I register them with the admin site.

Although making your own subclasses of AdminSite and ModelAdmin is encouraged in the docs, my solution looks like an ugly hack in the end.

Grange answered 16/7, 2009 at 23:48 Comment(0)
B
0

Copy lib\site-packages\django\contrib\admin\templates\admin\index.html template to project1\templates\admin\ directory, where project1 is the name of your project.

In the copied file, i.e. project1\templates\admin\index.html, replace lines:

{% block content %} 
...
{% endblock %}

with:

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

{% if app_list %}
    <div class="module">
        <table>
            <caption>App 1</caption>
            <tr>  <th>  <a href="/admin/app1/model1/">Model 1</a>  </th>  <td>Description of model 1</td>  </tr>
            <tr>  <th>  <a href="/admin/app1/model2/">Model 2</a>  </th>  <td>Description of model 1</td>  </tr>
            <tr>  <th>  <a href="..."                >...</a>      </th>  <td>...</td>                     </tr>
        </table>
    </div>

    <div class="module">
        <table>
            <caption>Authentication and authorization</caption>
            <tr>  <th>  <a href="/admin/auth/user/"  >Users</a>    </th>  <td>List of users</td>           </tr>
            <tr>  <th>  <a href="/admin/auth/group/" >Groups</a>   </th>  <td>List of users' groups</td>   </tr>
        </table>
    </div>
{% else %}
    <p>{% trans "You don't have permission to view or edit anything." %}</p>
{% endif %}

</div>
{% endblock %}  

where:

  • app1 is the name of your application with models,
  • modeli is the name of i-th model in app1.

If you defined more than one application with models in your project, then simply add another table in the above index.html file.

Because we change the template, we can freely change its HTML code. For example we can add a description of the models as it was shown above. You can also restore Add and Change links - I deleted them since I think they are redundant.

The answer is a practical demonstration of the solution from Dave Kasper's answer.

Brio answered 11/7, 2019 at 12:17 Comment(0)
A
0

custom_admin.py

from django.contrib.admin import AdminSite


class CustomAdminSite(AdminSite):
    def get_urls(self):
        urls = super(MyAdminSite, self).get_urls()
        return urls

    def get_app_list(self, request):
        app_dict = self._build_app_dict(request)
        ordered_app_list = []

        if app_dict:
            # TODO: change this dict
            admin_ordering = {
                'app1': ('Model1', 'Model2'),
                'app2': ('Model7', 'Model4'),
                'app3': ('Model3',),
            }

            ordered_app_list = []
            for app_key in admin_ordering:
                app = app_dict[app_key]
                app_ordered_models = []
                for model_name in admin_ordering[app_key]:
                    for model in app_dict[app_key]['models']:
                        if model['object_name'] == model_name:
                            app_ordered_models.append(model)
                            break

                app['models'] = app_ordered_models
                ordered_app_list.append(app)

        return ordered_app_list

admin_site = CustomAdminSite()

urls.py

from custom_admin import admin_site

urlpatterns = [
    path('admin/', admin_site.urls),
]
Apply answered 28/11, 2020 at 8:13 Comment(0)
M
0

Add the below code to your settings.py:

def get_app_list(self, request):
    """
    Return a sorted list of all the installed apps that have been
    registered on this site.
    """
    ordering = {
        # for superuser
        'Group': 1,
        'User': 2,

        # fist app
        'TopMessage': 101,
        'Slider': 102,
        'ProductCategory': 103,
        'Product': 104,
        'ProductPicture': 105,
        
        # 2nd app
        'ProductReview': 201,
        'Promotion': 202,
        'BestSeller': 203,
    }
    app_dict = self._build_app_dict(request)
    app_list = sorted(app_dict.values(), key=lambda x: x['name'].lower())

    for app in app_list:
        app['models'].sort(key=lambda x: ordering[x['object_name']])

    return app_list


admin.AdminSite.get_app_list = get_app_list

And make changes to the ordering dictionary to match your apps and models. That's it.

The benefit of my solution is that it will show the 'auth' models if the user is a superuser.

Museum answered 5/5, 2021 at 19:42 Comment(0)
H
0

Django, by default, orders the models in admin alphabetically. So the order of models in Event admin is Epic, EventHero, EventVillain, Event

Instead you want the order to be

  • EventHero, EventVillain, Epic then event.

The template used to render the admin index page is admin/index.html and the view function is ModelAdmin.index.

def index(self, request, extra_context=None):
"""
Display the main admin index page, which lists all of the installed
apps that have been registered in this site.
"""
app_list = self.get_app_list(request)
context = {
    **self.each_context(request),
    'title': self.index_title,
    'app_list': app_list,
    **(extra_context or {}),
}

request.current_app = self.name

return TemplateResponse(request, self.index_template or
    'admin/index.html', context)

The method get_app_list, set the order of the models.:

def get_app_list(self, request):
"""
Return a sorted list of all the installed apps that have been
registered in this site.
"""
app_dict = self._build_app_dict(request)

# Sort the apps alphabetically.
app_list = sorted(app_dict.values(), key=lambda x: x['name'].lower())

# Sort the models alphabetically within each app.
for app in app_list:
    app['models'].sort(key=lambda x: x['name'])

return app_list

So to set the order we override get_app_list as:

class EventAdminSite(AdminSite):
def get_app_list(self, request):
    """
    Return a sorted list of all the installed apps that have been
    registered in this site.
    """
    ordering = {
        "Event heros": 1,
        "Event villains": 2,
        "Epics": 3,
        "Events": 4
    }
    app_dict = self._build_app_dict(request)
    # a.sort(key=lambda x: b.index(x[0]))
    # Sort the apps alphabetically.
    app_list = sorted(app_dict.values(), key=lambda x: x['name'].lower())

    # Sort the models alphabetically within each app.
    for app in app_list:
        app['models'].sort(key=lambda x: ordering[x['name']])

    return app_list

The code app['models'].sort(key=lambda x: ordering[x['name']]) sets the fixed ordering. Your app now looks like this.

enter image description here

Check the Documentation

Hamlen answered 20/7, 2021 at 5:43 Comment(0)
H
0

In Settings.py file setting the order in which the apps/modules show up listed on Admin

set my ordering list

ADMIN_ORDERING = [
    ('Your_App1', '*'),
    ('Your_App2', '*'),
]

Creating a sort function below ADMIN_ORDERING

def get_app_list(self, request):
    """
    You Can Set Manually Ordering For Your Apps And Models   

        ADMIN_ORDERING = [
            ('Your_App1', [
                'module_1',
                'module_2'
            ]),
            ('Your_App2', '*'),
        ]
    """

    app_dict = self._build_app_dict(request)

    # Let's start by sorting the apps alphabetically on a list:
    app_list = sorted(app_dict.values(), key=lambda x: x['name'].lower())

    # Sorting the models alphabetically within each app.
    for app in app_list:
        if app['app_label'] in [el[0] for el in ADMIN_ORDERING]:
            app_list.remove(app)
        else:
            app['models'].sort(key=lambda x: x['name'])

    # Now we order the app list in our defined way in ADMIN_ORDERING (which could be a subset of all apps).
    my_ordered_apps = []
    if app_dict:
        for app_name, object_list in ADMIN_ORDERING:
            app = app_dict[app_name]
            if object_list == '*':
                app['models'].sort(key=lambda x: x['name'])
            else:
                app['models'].sort(key=lambda x: object_list.index(x['object_name']))
            my_ordered_apps.append(app)

        # Now we combine and arrange the 2 lists together
        my_ordered_apps.extend(app_list)

    return my_ordered_apps

# Covering django.contrib.admin.AdminSite.get_app_list
from django.contrib import admin
admin.AdminSite.get_app_list = get_app_list
Hamlen answered 20/7, 2021 at 6:23 Comment(0)
D
0

Quite a simple and self contained approach can be using the decorator pattern to resort your app and modules in this way:

# admin.py

from django.contrib import admin

def app_resort(func):                                                                                            
    def inner(*args, **kwargs):                                                                                            
        app_list = func(*args, **kwargs)
        # Useful to discover your app and module list:
        #import pprint                                                                                          
        #pprint.pprint(app_list)

        app_sort_key = 'name'
        app_ordering = {
            "APP_NAME1": 1,
            "APP_NAME2": 2,
            "APP_NAME3": 3,
        }

        resorted_app_list = sorted(app_list, key=lambda x: app_ordering[x[app_sort_key]] if x[app_sort_key] in app_ordering else 1000)

        model_sort_key = 'object_name'
        model_ordering = {
            "Model1": 1,
            "Model2": 2,
            "Model3": 3,
            "Model14": 4,
        }
        for app in resorted_app_list:
            app['models'].sort(key=lambda x: model_ordering[x[model_sort_key]] if x[model_sort_key] in model_ordering else 1000)
        return resorted_app_list
    return inner                                                                                            
                   
admin.site.get_app_list = app_resort(admin.site.get_app_list)

This code sorts at the top only the defined keys in the ordering dict, leaving at the bottom all the other. The approach is clean but it requires get_app_list to be executed before... which is probably not a good idea when performance is important and you have a large app_list.

Devoirs answered 25/11, 2021 at 7:55 Comment(0)
C
0

At least, there are 3 ways to do model ordering in Django admin panel:


By changing verbose_name_plural in Meta class of model

not bad way, but you will see numbers in admin panel

class A(models.Model):
    ...
    class Meta:
        verbose_name_plural = "2. A"

class B(models.Model):
    ...
    class Meta:
        verbose_name_plural = "1. B"

By using third-party library django-modeladmin-reorder

it is simple and fast, but not django-clean as well as this library is very old and abandoned


By overriding the default admin site method get_app_list

django-clean method, but more time expenses to configure

# First of - override the default admin site
# https://docs.djangoproject.com/en/5.0/ref/contrib/admin/#overriding-default-admin-site
# Note that it is project files, not application files!!!

#myproject/admin.py
from django.contrib import admin

class MyAdminSite(admin.AdminSite):
    ...

#myproject/apps.py
from django.contrib.admin.apps import AdminConfig

class MyAdminConfig(AdminConfig):
    default_site = "myproject.admin.MyAdminSite"

#myproject/settings.py
INSTALLED_APPS = [
    ...
    #'django.contrib.admin', remove this!
    'myproject.apps.MyAdminConfig',# replaces 'django.contrib.admin'
    ...
]

# The second step - overriding a method

#myproject/admin.py
from django.contrib import admin
from my_app.models import A, B, C# import your models from application

class MyAdminSite(admin.AdminSite):
    def get_app_list(self, request, app_label=None):
        order = [B, A]# place there your first models in order, remaining ones will be after them
        app_list = admin.AdminSite.get_app_list(self, request)
        for app in app_list:
            if app.get('app_label') == 'my_app':# your app's name in apps.py of application
                app["models"].sort(key=lambda x: order.index(x['model']) if x['model'] in order else len(order))

        return app_lis
Crozier answered 26/3 at 10:39 Comment(0)
D
0
  1. set ordering list in project setting.py
# set ordering list
# app label, model class name
ADMIN_ORDERING = [
    ('auth', []),
    ('APP', [
        'ModelClassA',
        'ModelClassB',
        'ModelClassC',
    ]),
]

  1. define costom get_app_list function in utils.py
def get_app_list(self, request):
    app_dict = self._build_app_dict(request)

    app_dict_copy = copy.deepcopy(app_dict)
    # 先按照配置中自定义的顺序排序
    for app_label, object_list in settings.ADMIN_ORDERING:
        app = app_dict_copy.pop(app_label)
        object_dict = {value: idx for idx, value in enumerate(object_list)}
        app['models'].sort(key=lambda x: object_dict.get(x['object_name'], len(object_list)+1))
        yield app

    # 未在配置中声明的app使用默认排序
    app_list = sorted(app_dict_copy.values(), key=lambda x: x['name'].lower())
    for app in app_list:
        app['models'].sort(key=lambda x: x['name'])
        yield app

  1. use in url.py file
from django.contrib import admin

from .utils.admin import get_app_list


admin.AdminSite.get_app_list = get_app_list

urlpatterns = [
    path('admin/', admin.site.urls),
    # ...
]
Darlenadarlene answered 21/5 at 3:14 Comment(0)
G
-1

This has become a lot easier in Django 4.1:

The AdminSite.get_app_list() method now allows changing the order of apps and models on the admin index page.

You can subclass and override this method to change the order the returned list of apps/models.

Gilstrap answered 1/4, 2022 at 5:16 Comment(1)
could you show how to use it I'm a beginnerCowitch

© 2022 - 2024 — McMap. All rights reserved.