4 sposoby na niestandardowy przycisk w adminie Django.


Poznajcie aż 4 sposoby na dodanie własnego przycisku do generowania obiektu w formie CSV z poziomu panelu administracyjnego Django.


Django to bez wątpienia jeden z wygodniejszych frameworków z jakimi miałem okazję pracować. Dostarcza wiele funkcjonalności, które wystarczają do zbudowania zdecydowanej większości prostych aplikacji. Problem pojawia się gdy chcemy zrobić coś bardziej niestandardowego albo coś czego twórcy nie przewidzieli. Tak właśnie jest z panelem administracyjnym. Muszę przyznać, że ten wbudowany daje bardzo dużo możliwości. Czy jest ładny i wygodny to kwestia gustu. Mi osobiście odpowiada. Ma wiele wbudowanych opcji, które ustawia się poprzez dodanie odpowiednich atrybutów. Jest to całkiem wygodne. Zdarzają się jednak sytuacje, w których nie wiemy jak coś zrealizować. Jedną z nich może być na przykład wygenerowanie całego artykułu jako plik CSV. Dziś przedstawię Wam aż 4 sposoby na dodanie przycisku do pobierania obiektu do CSV z poziomu panelu administracyjnego Django.

Widok

Model i admin artykułu prezentowałem już w jednym z pierwszych wpisów . Niestety tam było to przedstawione na Django 1.11 . W późniejszej części artykułu zaprezentuję co się zmieniło. Tymczasem przejdźmy do widoku, który pozwoli nam pobierać artykuł

import csv

from django.http import HttpResponse

from django.shortcuts import get_object_or_404

from .models import Article


def article_csv(request, pk):
    obj = get_object_or_404(Article, pk=pk)
    response = HttpResponse(content_type='text/csv')
    filename = f"article_{pk}.csv"
    response['Content-Disposition'] = f'attachment; filename="{filename}"'
    writer = csv.writer(response)
    
    # Headers
    writer.writerow([field.verbose_name for field in obj._meta.get_fields()])
    
    # Row
    writer.writerow([getattr(obj, field.get_attname()) for field in obj._meta.get_fields()])
    
    return response

W ten sposób stworzyliśmy prosty widok, który pobiera artykuł i wypisuje wszystkie jego atrybuty do pliku CSV. Zwróćcie uwagę w jaki sposób pobieram atrybuty modelu. Każdy obiekt ma metaklasę, która przez metodę get_fields udostępnia wszystkie atrybuty naszego modelu. Następnie z tego pola pobieram verbose_name do nagłówków oraz get_attname do pobierania konkretnego atrybutu z modelu. Radzę to zapamiętać, bo ja od kiedy to poznałem korzystam z tego regularnie.

Rejestrowanie widoku w URL

Nowością w Django 2 jest wygodne rejestrowanie adresów URL. Już nie trzeba bawić się w wyrażenia regularne. Zresztą zobaczcie sami

urlpatterns = [
    [ … ]
    path('article_csv/<int:pk>', article_csv, name='article_csv'),
]

Tym prostym sposobem mamy już zdefiniowany widok. Przejdźmy zatem do najważniejszej kwestii, czyli pierwszego z czterech sposobów na podpięcie tego widoku w adminie Django.

Po pierwsze... Pole tylko do odczytu

Jeśli czytaliście moje poprzednie wpisy odnośnie admina Django, to zapewne wiecie, że łatwo można dodać niestandardowe pole do panelu . Żeby to zrobić wystarczy zdefiniować nowy atrybut, bądź metodę w modelu lub adminie i podpiąć ją w atrybucie readonly_fields . Spróbujmy stworzyć zatem metodę w klasie admina

from django.utils.safestring import mark_safe

from .models import Article


class ArticleAdmin(admin.ModelAdmin):
    model = Article
    readonly_fields = ('article_csv', )
    

    def article_csv(self, obj):
        if not obj.pk:
            return mark_safe('<span>Dodaj artykuł, żeby móc pobrać CSV</span>') 

        url = reverse('article_csv', args=[obj.pk])
        return mark_safe('<a href={}>Pobierz</a>'.format(url))
        article_csv.short_description = 'Artykuł w CSV'


admin.site.register(Article, ArticleAdmin)

Zwróćcie uwagę na to w jaki sposób w nowszych wersjach Django przekazać tagi html w takiej metodzie. Wcześniej dodawało się kolejny atrybut, teraz należy skorzystać z funkcji mark_safe .

Sama metoda jest stosunkowo prosta. Jeśli obiekt jeszcze nie jest zapisany i nie ma PK, to wyświetlana jest zaślepka, w przeciwnym wypadku linkowany tekst to adres do widoku, który wcześniej stworzyliśmy.

Domyślnie pola tylko do odczytu zamieszczane są na samym dole, jednak można to nadpisać. Instrukcję zamieściłem w poprzednim wpisie.

Po drugie... Przycisk na liście

Wchodzenie do każdego obiektu, żeby wygenerować jego raport może okazać się uciążliwe. Dlatego warto wiedzieć, że jest możliwość zamieszczenia naszej stworzonej metody na listingu obiektów. Żeby to zrobić wystarczy nadpisać list_display

class ArticleAdmin(admin.ModelAdmin):
    [ … ]
    list_display = ('pk', 'title', 'active', 'article_csv')

Po trzecie... Nadpisywanie szablonu

Dla upartych można skorzystać z jeszcze innej opcji. W panelu dodawania i edycji obiektu są aż 3 elementy, w których zdefiniowane są akcje. Od góry jest to sekcja object-tools , w której są na przykład przycisk historii lub podglądu na stronie. Niżej jest sekcja z przyciskami zapisu obiektu. Wyświetla się ona tylko pod warunkiem, że w adminie mamy uzupełnione save_on_top . Na samym dole również jest sekcja zapisów obiektu, jednak tym razem jest ona za każdym razem niezależnie od konfiguracji. W każdym z tych 3 miejsc można łatwo dodać nasze przyciski. Żeby to zrobić wystarczy nadpisać szablon.

Jeśli chcemy żeby szablon dotyczył tylko jednej aplikacji wystarczy, że w naszym katalogu template dodamy plik z następującą ścieżką

./admin/my_app/change_form.html

Należy rozszerzyć domyślny plik change_form.html z admina i nadpisać odpowiednie bloczki. W moim przykładzie przycisk będzie we wszystkich ze wskazanych bloczków. Wybierzcie ten, który Wam najlepiej odpowiada. Poniżej przykład takiego pliku

{% extends "admin/change_form.html" %}

{% block object-tools %}
{{ block.super }}
<ul class="object-tools" style="margin-right:100px;">
    <li>
        <a href="{% url 'article_csv' original.pk %}">Pobierz CSV</a>
    </li>
</ul>
{% endblock %}


{% block submit_buttons_top %}
{% comment %} Działa jeśli w adminie dodasz save_on_top=True {% endcomment %}
<div class="submit-row">
    <a href="{% url 'article_csv' original.pk %}">Pobierz CSV </a>
</div>
{{ block.super }}
{% endblock %}

{% block submit_buttons_bottom %}
{{ block.super }}
<div class="submit-row">
    <a href="{% url 'article_csv' original.pk %}">Pobierz CSV</a>
</div>
{% endblock %}

Po czwarte... Własne akcje

Czwartą i ostatnią metodą na pobieranie modelu w postaci CSV z poziomu admina jest dodanie niestandardowej akcji. Te również opisywałem już wcześniej. Jednak wtedy była to prosta metoda do aktualizacji danych. Akcje mają tę zaletę, że możemy wybrać queryset a nie pojedynczy obiekt. Dzięki temu możemy od razu wygenerować raport z kilkoma artykułami. Żeby to zrobić musimy rozszerzyć naszego admina i dodać funkcję bardzo zbliżoną do stworzonego wcześniej widoku

import csv

import requests

from django.http import HttpResponse

from django.urls import reverse


def get_csv(modeladmin, request, queryset):
    response = HttpResponse(content_type='text/csv')
    response["Content-Disposition"] = "attachment; filename='articles.csv'"
    writer = csv.writer(response)
    for i, obj in enumerate(queryset):
        # Headers
        if i == 0:
            writer.writerow([field.verbose_name for field in obj._meta.get_fields()])

        # Row
        writer.writerow([getattr(obj, field.get_attname()) for field in obj._meta.get_fields()])

    return response
    get_csv.short_description = 'Pobierz artykuł w CSV'

Nagłówki dodawane są przy pierwszej iteracji przez pętle. Zmieniona jest również nazwa pliku. Wcześniej na końcu nazwy był ID konkretnego obiektu. Takie rozwiązanie jest bardziej elastyczne, ale nie zawsze jest potrzeba dodawania aż tak rozbudowanego narzędzia

Podsumowanie

Przedstawiłem Wam aż 4 sposoby na dodanie przycisku umożliwiającego zapis obiektu do CSV z poziomu panelu administracyjnego w Django. Mam nadzieję, że ta wiedza się Wam przyda. Jest jeszcze kilka innych sposobów ale moim zdaniem te są najbardziej oczywiste, najłatwiejsze do zapamiętania i stosunkowo proste do implementacji

Lip 02, 2019

Najnowsze wpisy

Zobacz wszystkie