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