Pular para o conteúdo
dicas de djangopor Regis do Python

Dica 29 - Django Admin: Criando actions no Admin

5 min de leitura
Assistir ao vídeo no YouTube: Dica 29 - Django Admin: Criando actions no Admin

Versões usadas no vídeo: Django 2.2.19 e Python 3.8.

https://docs.djangoproject.com/en/3.1/ref/contrib/admin/actions/

Na lista de registros do Django Admin existe o menu Action (Ação), que aplica uma operação a todos os itens marcados. O Django já traz uma action pronta, a Delete selected ("apagar os selecionados"). Neste tutorial vamos criar a nossa própria action: Publicar artigos, que muda o status de todos os artigos selecionados para "Publicado" de uma vez só e mostra uma mensagem dizendo quantos artigos foram publicados.

Pré-requisitos

O vídeo usa o projeto do repositório dicas-de-django, com os models Article e Category e o ArticleAdmin das dicas anteriores (o get_published_date e o get_category com short_description são da dica 28).

1. O campo status no model

Primeiro, o artigo precisa de um status. Em models.py considere

# myproject/core/models.py
# ... (veja o arquivo completo no GitHub)

STATUS_CHOICES = (
    ('d', 'Rascunho'),
    ('p', 'Publicado'),
    ('w', 'Retirado'),
)


class Article(models.Model):
    id = HashidAutoField(primary_key=True)
    title = models.CharField('título', max_length=200)
    subtitle = models.CharField('sub-título', max_length=200)
    slug = AutoSlugField(populate_from='title')
    # ... (category e published_date, veja o arquivo completo no GitHub)
    status = models.CharField(max_length=1, choices=STATUS_CHOICES)

    class Meta:
        ordering = ('title',)
        verbose_name = 'artigo'
        verbose_name_plural = 'artigos'

    def __str__(self):
        return self.title

# ... (veja o arquivo completo no GitHub)

Código completo: myproject/core/models.py

  • STATUS_CHOICES é uma tupla de pares (valor gravado no banco, texto exibido): d de draft (Rascunho), p de published (Publicado) e w de withdrawn (Retirado).
  • status é um CharField de um caractere com choices=STATUS_CHOICES. No banco fica gravada só a letra; no Admin aparece o texto, e o formulário vira uma caixa de seleção.

2. A migração

python manage.py makemigrations
python manage.py migrate

Como o campo status não tem default e a tabela já tem artigos, o makemigrations pergunta o que colocar nos registros existentes:

You are trying to add a non-nullable field 'status' to article without a default; we can't do that (the database needs something to populate existing rows).
Please select a fix:
 1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
 2) Quit, and let me add a default in models.py
Select an option: 1
Please enter the default value now, as valid Python
The datetime and django.utils.timezone modules are available, so you can do e.g. timezone.now
Type 'exit' to exit this prompt
>>> 'd'

Escolhendo a opção 1 e digitando 'd', todos os artigos que já existem ficam como Rascunho. A migração gerada é esta:

# myproject/core/migrations/0005_article_status.py
# Generated by Django 2.2.19 on 2021-03-08 01:08

from django.db import migrations, models


class Migration(migrations.Migration):

    dependencies = [
        ('core', '0004_auto_20200614_0901'),
    ]

    operations = [
        migrations.AddField(
            model_name='article',
            name='status',
            field=models.CharField(choices=[('d', 'Rascunho'), ('p', 'Publicado'), ('w', 'Retirado')], default='d', max_length=1),
            preserve_default=False,
        ),
    ]

preserve_default=False indica que o 'd' foi usado só para preencher os registros antigos; o campo no model continua sem valor padrão.

3. A action no admin.py

E em admin.py

# myproject/core/admin.py
from django.conf import settings
from django.contrib import admin
from daterange_filter.filter import DateRangeFilter
from .models import Article, Category
# from .forms import ArticleAdminForm


@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
    list_display = ('id', 'title', 'slug', 'get_published_date', 'get_category', 'status')
    search_fields = ('title',)
    list_filter = (
        ('published_date', DateRangeFilter),
        'category',
        'status',
    )
    readonly_fields = ('slug',)
    date_hierarchy = 'published_date'
    # form = ArticleAdminForm
    actions = ('make_published',)

    def make_published(self, request, queryset):
        count = queryset.update(status='p')

        if count == 1:
            msg = '{} artigo foi publicado.'
        else:
            msg = '{} artigos foram publicados.'

        self.message_user(request, msg.format(count))

    make_published.short_description = "Publicar artigos"

    def get_published_date(self, obj):
        if obj.published_date:
            return obj.published_date.strftime('%d/%m/%Y')

    get_published_date.short_description = 'Data de Publicação'

    def get_category(self, obj):
        if obj.category:
            return obj.category.title

    get_category.short_description = 'Categoria'


@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
    list_display = ('title', 'slug')
    actions = None

    def has_add_permission(self, request, obj=None):
        return False

    if not settings.DEBUG:
        def has_delete_permission(self, request, obj=None):
            return False

O que entrou nesta dica:

  • 'status' no list_display (para ver o status na lista) e no list_filter (para filtrar por status na barra lateral).
  • actions = ('make_published',) registra a action pelo nome do método. A action padrão Delete selected continua disponível.
  • O método make_published(self, request, queryset) é a action. O Django chama esse método com a requisição e um queryset que contém apenas os artigos marcados na lista.
  • queryset.update(status='p') atualiza todos de uma vez, com um único UPDATE no banco, e devolve a quantidade de linhas alteradas.
  • Com essa quantidade (count), escolhemos a mensagem no singular ou no plural, e self.message_user() mostra a mensagem no topo da página, na faixa verde do Admin.
  • make_published.short_description = "Publicar artigos" é o texto que aparece no menu de actions (o mesmo short_description da dica 28). Sem ele, o menu mostraria "Make published".

Repare no CategoryAdmin: actions = None remove o menu de actions daquele model, inclusive o Delete selected.

4. Testando no Admin

Rode o servidor e entre em http://localhost:8000/admin/core/article/:

python manage.py runserver

A lista agora tem a coluna Status, com todos os artigos como Rascunho. Abra o menu Action e veja as duas opções: Delete selected artigos e Publicar artigos.

  1. Marque um artigo, escolha Publicar artigos e clique em Go. Aparece a mensagem 1 artigo foi publicado. e o status dele muda para Publicado.
  2. Marque dois artigos e repita. A mensagem agora é 2 artigos foram publicados.

Diferente do Delete selected, a nossa action não tem página de confirmação: ela é executada assim que você clica em Go.

Veja também

Mais dois vídeos sobre actions no Django Admin, citados na descrição do vídeo:

Observação: a partir do Django 3.2, dá para declarar a action com o decorador @admin.action(description='Publicar artigos') em vez do atributo short_description, que continua funcionando.

Sugerir uma correção nesta página