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):dde draft (Rascunho),pde published (Publicado) ewde withdrawn (Retirado).statusé umCharFieldde um caractere comchoices=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'nolist_display(para ver o status na lista) e nolist_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 umquerysetque contém apenas os artigos marcados na lista. queryset.update(status='p')atualiza todos de uma vez, com um únicoUPDATEno banco, e devolve a quantidade de linhas alteradas.- Com essa quantidade (
count), escolhemos a mensagem no singular ou no plural, eself.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 mesmoshort_descriptionda 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.
- Marque um artigo, escolha Publicar artigos e clique em Go. Aparece a mensagem
1 artigo foi publicado.e o status dele muda para Publicado. - 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.