Заказ объектов admin.ModelAdmin

Скажем, у меня есть приложение для пиццы с классами Topping и Pizza, и они отображаются в Django Admin следующим образом:

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

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

Но я хочу их так:

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

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

Как мне настроить это в моем admin.py?

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
40
0
32 110
16
Перейти к ответу Данный вопрос помечен как решенный

Ответы 16

Это просто дикий удар в темноте, но есть ли шанс, что порядок, в котором вы вызываете admin.site.register (<класс модели>, <класс ModelAdmin>), может определять порядок отображения? На самом деле, я сомневаюсь, что это сработает, потому что я считаю, что Django поддерживает реестр объектов Model -> ModelAdmin, реализованных как стандартный словарь Python, который не поддерживает порядок итераций.

Если это ведет себя не так, как вы хотите, вы всегда можете поиграть с исходным кодом в django / contrib / admin. Если вам нужно поддерживать порядок итераций, вы можете заменить объект _registry в классе AdminSite (в admin / sites.py) на UserDict или DictMixin, который поддерживает порядок вставки ключей. (Но, пожалуйста, прислушайтесь к этому совету с недоверием, поскольку я сам никогда не вносил подобных изменений и я только делаю обоснованное предположение о том, как Django выполняет итерацию по коллекции объектов ModelAdmin. Я действительно думаю, что django / contrib Тем не менее, /admin/sites.py - это место, где можно найти этот код, и, в частности, класс AdminSite и методы register () и index () - это то, что вам нужно.)

Очевидно, что самым приятным здесь будет простой вариант, который вы можете указать в своем собственном модуле /admin.py. Я уверен, что вы надеялись получить именно такой ответ. Однако я не уверен, существуют ли такие варианты.

Да, я надеялся получить такой ответ и боялся такого. Я думаю, что вы правы, по крайней мере, из того, что я нашел в документации. Думаю, я оставлю это как есть. Я не хочу поддерживать свою собственную версию django.

Rui Ferreira 29.12.2008 21:27

Я добавил это в трек Django:

http://code.djangoproject.com/ticket/9928

Это еще не сделано!

raratiru 17.01.2016 14:36
Ответ принят как подходящий

На самом деле это описано в самом низу Написание вашего первого приложения Django, часть 7.

Вот соответствующий раздел:

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.

Это полезно знать, но я думаю, что вопрос касается пары классов Model в одном и том же установленном приложении. Изменение порядка приложений не поможет вам, если вы захотите изменить порядок классов модели в одном приложении.

Jeff 29.12.2008 22:03

Хорошая мысль. Они должны быть где-то перечислены, и, возможно, мне просто нужно применить там фильтр сортировки. Я собираюсь посмотреть на это завтра (здесь европейский часовой пояс ...) Спасибо

Rui Ferreira 29.12.2008 23:01

Я думаю, что стоит попробовать простое решение из 林伟雄 ниже. Только небольшое дополнение в settings.py.

mirek 18.01.2021 18:38

Мое решение заключалось в создании подклассов django.contrib.admin.sites.AdminSite и django.contrib.admin.options.ModelAdmin.

Я сделал это, чтобы отображать более описательные заголовки для каждого приложения и упорядочивать внешний вид моделей в каждом приложении. Итак, у меня есть dict в моем settings.py, который сопоставляет app_labels с описательными именами и порядком, в котором они должны появляться, модели упорядочены по порядковому полю, которое я предоставляю в каждом ModelAdmin, когда я регистрирую их на сайте администратора.

Хотя в документации поощряется создание собственных подклассов AdminSite и ModelAdmin, мое решение в конечном итоге выглядит уродливым хаком.

Обходной путь, который вы можете попробовать, - это настроить свой файл models.py следующим образом:

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

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

Не уверен, что это противоречит лучшим практикам django, но работает (проверено с помощью django trunk).

Удачи!

PS: извините, если этот ответ был опубликован слишком поздно, но он может помочь другим в подобных ситуациях в будущем.

Это то, что я использую. Хотя это обходной путь, это самый простой способ.

ePi272314 20.06.2016 21:06

грустный. если у вас есть 3 записи пиццы, он наконец скажет «3 1. Пицца».

Kiran P. 22.12.2016 14:22

Если вы хотите решить эту проблему за 10 секунд, просто используйте пробелы в verbose_name_plural, например:

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

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

Конечно, это не изящно, но может работать некоторое время, прежде чем мы получим лучшее решение.

Работает в последней версии Django 1.11. Обратите внимание, что пробелы в начале не отображаются браузером, поэтому это не оказывает визуального воздействия на заголовок. Также обратите внимание, что это работает с классами AppConfig, чтобы также изменить порядок разделов приложений.

Mariano Ruiz 26.07.2017 06:14

Супер резкий, но определенно самый быстрый и грязный!

nicorellius 18.09.2020 20:57

Также можно использовать небольшой фрагмент кода, который называется 'Заказ пользовательских приложений и моделей в Django Admin Index'. Фрагмент решает проблему всего за 3 простых редактирования.

Вот подробности. http://djangosnippets.org/snippets/2613/

В конце концов мне удалось это сделать благодаря этому Фрагмент Django, вам просто нужно знать настройку ADMIN_REORDER:

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

app1 не должен начинаться с префикса имени проекта, т.е. используйте app1 вместо mysite.app1.

Что ж, это сработало в версии 1.3, с какой проблемой вы столкнулись?

Emmanuel 15.04.2013 12:43

Что может быть неясным, так это то, что вам нужен весь фрагмент, а не только блок кода, опубликованный в этом ответе.

Peter G 16.09.2013 23:55

Вдохновленный фрагментом, я написал github.com/mishbahr/django-modeladmin-reorder

mishbah 03.08.2014 23:27

@mishbah, не могли бы вы обновить его для Django 2.x или какой-либо справочной ссылки о том, как это сделать для внутренних страниц?

saleem 11.11.2020 13:04

Если вы используете Подходить для AdminSite, вы можете настроить меню с помощью тег меню.

Используя тег меню, мне удалось изменить только внешний вид левого меню.

raratiru 17.01.2016 14:35

Вот фрагмент, который использовал Эммануэль, обновленный для Django 1.8:

В 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 ""

В settings.py:

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

(вставьте сюда свои собственные названия приложений. Администратор поместит отсутствующие приложения или модели в конец списка, если вы укажете как минимум две модели в каждом приложении.)

В вашей копии 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 %}

Для этого есть хороший пакет Django:

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

Я искал простое решение, в котором можно было бы упорядочивать приложения по их имени в панели администратора. Я придумал следующий тег шаблона:

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

Затем просто переопределите templates/admin/index.html и добавьте этот тег шаблона:

{% 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 %}

Затем настроили APP_ORDER в settings.py:

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

Отлично работает на Django 1.10.

Ага, отличная работа в Django 2.0. Для инициализации вы можете использовать ключ libraries в TEMPLATES['OPTIONS'] dict следующим образом: `` 'библиотеки': {'sort_apps': 'src.apps.utils.templatetags.customize_tags',} `` Без добавления в INSTALLED_APPS

teror4uks 11.02.2018 12:40

@ teror4uks не могли бы вы объяснить это еще немного?

saleem 11.11.2020 10:41

Ответ в июне 2018 г.

Этот ответ похож на Идея Василя

Я пытался решить подобные проблемы, и тут увидел такой фрагмент.

Я сделал некоторые модификации на основе этого клипа. Код выглядит следующим образом.

# 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
...

Обратите внимание, что список сортировки, используемый в этой функции сортировки, содержит сортировку всех приложений и их модулей в системе. Если вам это не нужно, разработайте функцию сортировки в соответствии со своими потребностями.

Отлично работает на Django 2.0

Если есть план по оптимизации этого кода или других реализаций, пожалуйста, поделитесь им со мной. Спасибо!

林伟雄 19.06.2018 21:13

Это вернуло ValueError: слишком много значений для распаковки (ожидалось 2), когда я пытался это сделать. Django 2.1 здесь.

Nathan 02.10.2018 13:38

Я не знаю, откуда произошла ошибка, не могли бы вы показать мне свой стек исключений? @Nathan

林伟雄 15.04.2019 07:53

Я написал свой собственный сценарий, чтобы позаботиться об этом. Я писал об этом здесь: zhuzhu-limited.com/django-admin-app-and-model-reordering

Nathan 16.04.2019 16:27

Это самое простое и элегантное решение! Отличная работа.

Rui Carvalho 09.06.2019 18:52

Это здорово. Прекрасно работает на Django 2.2 с администратором Django по умолчанию. Все изменения есть в settings.py, не нужно редактировать шаблоны, что требуется в большинстве других решений.

Nitin Nain 26.11.2019 15:56

Большой ! Работает с 3.1. Я добавил следующее после вашей команды yield: 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] Итак, у меня есть все в админке, и я могу изменить порядок только тех приложений, которые важны для меня. Такие явно определенные приложения тогда оказываются на вершине, но я думаю, что такое поведение неплохо.

mirek 18.01.2021 18:27

Вот версия, которая дает вам немного больше гибкости, а именно:

  • Вы можете частично определить порядок приложений, оставив остальное Django, чтобы добавить в список
  • Вы можете указать порядок модулей или не определять его, используя вместо этого '*'
  • Ваш определенный порядок приложений будет отображаться первым, затем все остальные приложения будут добавлены после него.
  • Чтобы проверить имя вашего приложения, либо посмотрите файл apps.py внутри каталога приложения и проверьте свойство name class Config(AppConfi):, либо, если его нет, используйте имя каталога для приложения в проекте.

Добавьте этот код где-нибудь в свой файл settings.py:

# ======[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
# =========================================

Это не что иное, как перезапись функции, определенной Django, в файле python2.7 / site-packages / django / contrib / admin / sites.py.

Этот метод get_app_list для class AdminSite(object): создает структуру данных со всеми приложениями в проекте, в том числе для приложения аутентификации Django, например:

[
  {
    "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"
  },
(...)
]

Скопируйте шаблон lib\site-packages\django\contrib\admin\templates\admin\index.html в каталог project1\templates\admin\, где project1 - это имя вашего проекта.

В скопированном файле, то есть project1\templates\admin\index.html, замените строки:

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

с участием:

{% 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 %}  

где:

  • app1 - это имя вашего приложения с моделями,
  • modeli - это название i-й модели в app1.

Если вы определили более одного приложения с моделями в своем проекте, просто добавьте еще одну таблицу в указанный выше файл index.html.

Поскольку мы меняем шаблон, мы можем свободно изменять его HTML-код. Например, мы можем добавить описание моделей, как было показано выше. Вы также можете восстановить ссылки Добавлять и Изменять - я удалил их, так как считаю их избыточными.

Ответ - практическая демонстрация решения от Ответ Дэйва Каспера.

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),
]

Другие вопросы по теме