Замінити набір запитів за замовчуванням у адміністратора Django


79

Одна з моїх моделей має видалений прапор, який використовується для глобального приховування об’єктів:

class NondeletedManager(models.Manager):
    """Returns only objects which haven't been deleted"""

    def get_query_set(self):
        return super(NondeletedManager, self).get_query_set().exclude(deleted=True)

class Conversation(BaseModel):
    ...
    deleted = models.BooleanField(default=False)
    objects = NondeletedManager()
    all_conversations = models.Manager() # includes deleted conversations

Як я можу замінити набір запитів за замовчуванням, який використовується адміністративним модулем Django для включення видалених розмов?


Вам справді потрібні власні менеджери для цих простих запитів?
— Сезар,

3
Так, видалені об’єкти слід ігнорувати універсально (за винятком сторінок адміністратора), тому має сенс встановити за замовчуванням.
— Натан Єллін

Відповіді:


144

Ви можете замінити get_queryset метод у своєму класі адміністратора моделі.

class MyModelAdmin(admin.ModelAdmin):
    def get_queryset(self, request):
        qs = super(MyModelAdmin, self).get_queryset(request)
        if request.user.is_superuser:
            return qs
        return qs.filter(author=request.user)

Зверніть увагу, що в Django <= 1,5 метод був названий просто queryset.


3
Як би це працювало у цьому випадку? Чи можу я змінити набір запитів, створений, ModelAdmin.querysetдля включення видалених об'єктів? Я не хочу самостійно створювати набір запитів, замість того, щоб звертатися до суперкласу.
— Натан Єллін,

Подивіться на мою відповідь, щоб зрозуміти, що я маю на увазі. Чи існує альтернатива повній повторній реалізації функції?
— Натан Єллін,

4
Це допомагає поставити відповідь у своїй відповіді, а не просто посилатись. Зараз це посилання мертве, тому я оновлю, щоб дати пояснення.
— Dan


1
Я не зміг отримати цю роботу. Я отримую повідомлення про помилку із заявою, що .filter немає в об'єкті qs. По-друге, я також не можу використовувати набір запитів ні для чого, крім запиту. stackoverflow.com/questions/54472649/… Будь-яка допомога?
— Gary

9

Конрад правильний, але це складніше, ніж приклад, наведений у документації.

Видалені бесіди не можуть бути включені до набору запитів, який уже їх виключає. Отже, я не бачу іншого варіанту, ніж повторна реалізація admin.ModelAdmin.queryset.

class ConversationAdmin (admin.ModelAdmin):

    def queryset (self, request):
        qs = Conversation.all_conversations
        ordering = self.get_ordering(request)
        if ordering:
            qs = qs.order_by(*ordering)
        return qs

1
Я не думаю, що в цьому є щось погане. Використання двох менеджерів - це шлях. Однак правда, що адміністратор Django міг забезпечити гачок, так що вам не доведеться повторно реалізовувати частину замовлення.
— Thomas Orozco,

3

Що може бути не так із наступним:

class Conversation(BaseModel):
    ...
    deleted = models.BooleanField(default=False)
    objects = models.Manager() # includes deleted conversations
    nondeleted_conversations = NondeletedManager()

Тож у своїх власних додатках / проектах ви використовуєте Conversation.nondeleted_conversations()і дозволяєте вбудованому додатку адміністратора робити це.


1
Я ігнорую видалені об'єкти скрізь, крім сторінок адміністратора, тому вважаю, що це має бути за замовчуванням. Крім того, таким чином мені не потрібно оновлювати застарілий код, додаючи можливість видалення розмов.
— Натан Єллін

2

Прийняте рішення чудово працює для мене, але мені потрібна була трохи більша гнучкість, тому в підсумку я розширив подання списку змін, щоб додати власний параметр набору запитів. Тепер я можу налаштувати свій набір запитів / фільтр за замовчуванням як такий, і його все ще можна змінити, використовуючи інший фільтр (отримати параметри):

def changelist_view(self, request, extra_context=None):
    if len(request.GET) == 0 :
        q = request.GET.copy()
        q['status__gt'] = 4
        request.GET = q
        request.META['QUERY_STRING'] = request.GET.urlencode()

    return super(WorksheetAdmin,self).changelist_view(request, extra_context=extra_context)

2

Це можна зробити за допомогою проксі-моделі Django .

# models.py
class UnfilteredConversation(Conversation):
    class Meta:
        proxy = True

    # this will be the 'default manager' used in the Admin, and elsewhere
    objects = models.Manager() 

# admin.py
@admin.register(UnfilteredConversation)
class UnfilteredConversationAdmin(Conversation):
    # regular ModelAdmin stuff here
    ...

Або, якщо у вас є існуючий клас ModelAdmin, який ви хочете використати повторно:

admin.site.register(UnfilteredConversation, ConversationAdmin)

Цей підхід дозволяє уникнути проблем, які можуть виникнути при перевизначенні менеджера за замовчуванням в оригінальній моделі розмови - оскільки менеджер за замовчуванням також використовується у відносинах ManyToMany та зворотних відносинах ForeignKey.


1

Натан Єллін правильний, але ви можете змінити порядок менеджерів, і перший буде типовим, тоді він використовується адміністратором:

class Conversation(BaseModel):
    ...
    deleted = models.BooleanField(default=False)

    all_conversations = models.Manager() # includes deleted conversations
    objects = NondeletedManager()

Реалізація адмін get_queryset()використання ._default_managerзамість .objects, як показано в наступному

qs = self.model._default_manager.get_queryset()

ref Реалізація Django github BaseModelAdmin

Це гарантує лише те, що кожного разу, коли ви використовуєте YourModel.objects, ви не включатимете видалені об'єкти, а загальні подання та інші використовує також ._default_manager. Тоді, якщо ви не перевизначите get_queryset, це не рішення. Я щойно перевірив ListView та адміністратора.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.