Rzeczy które powinieneś znać w adminie Django cz II


Poznaj więcej sztuczek w panelu administracyjnym Django, które przyspieszą twoją pracę. 


Witajcie w kolejnej części wpisu na temat pracy z adminem w Django. W poprzedniej części dowiedzieliście się jak poprawnie zarejestrować model w adminie. Znacie metody tworzenia filtrów, zarówno tych wbudowanych jak i własnych, z bardziej rozbudowaną logiką. Potraficie dodawać ciekawe metody, które przyśpieszają pracę podczas korzystania z panelu a także nie są Wam obce techniki optymalizacji. Jeżeli jeszcze nie zapoznaliście się z poprzednią częścią to gorąco zachęcam do przeczytania jej zanim zaczniecie ten wpis, bo często będę się powoływać do fragmentów kodu, które zostały już wyjaśnione i bez ich zrozumienia możecie mieć teraz kilka trudności

W tym wpisie opiszę pozostałe, ciekawe możliwości na liście oraz wprowadzę Was w stronę dodawania i edycji obiektu. Tam również istnieje kilka ciekawych sztuczek, które zdecydowanie warto poznać. Jeżeli jesteście ciekawi zapraszam

Było filtrowanie a co z wyszukiwaniem?

Gdy rekordów w bazie jest stosunkowo mało to same filtry wystarczą. Gdy natomiast danych zaczyna przybywać przyda się coś dokładniejszego, coś co pozwoli skutecznie odseparować pożądane elementy. W panelu administracyjnym jest wbudowana wyszukiwarka. Wystarczy ją zarejestrować na pole które nas interesuje. Poniżej przykład dodania wyszukiwania w artykule.

class ArticleAdmin(admin.ModelAdmin):
    [...]
    search_fields = ('title', '^slug', '=id', 'category__title') 

Jak widzicie pojawiło się kilka nowych elementów. Taki zapis pozwala na wyszukiwanie artykułów w których wyszukiwana fraza spełniają przynajmniej jedno z podanych kryteriów:

  • zawiera się w tytule artykułu
  • rozpoczyna slug artykułu ( do tego służy zapis z ^ )
  • jest równe id artykułu ( do tego służy zapis z = )
  • zawiera się w tytule kategorii ( do tego służy zapis __ )

Akcje na wielu elementach

Ile razy zdarzyło się Wam, że musieliście wprowadzić podobne zmiany w wielu obiektach jednocześnie? Zakładam, że nie raz. Przynajmniej u mnie jest to dość regularne zadanie. Dopóki nie byłem zaznajomiony z akcjami zmiany wprowadzałem ręcznie na bazie, bo tak było mi po prostu najwygodniej. Jednak kiedy dostawałem coraz więcej zapytań od redakcji (klientów) o masową aktywację treści postanowiłem, że uproszczę sobie ten schemat. Szukałem rozwiązania, które by to zautomatyzowało,  bo w końcu dobry programista to leniwy programista ;). Po jakimś czasie zauważyłem ciekawe zastosowanie akcji. To również jest wbudowany mechanizm w adminia Django. Domyślną akcją jest usuwanie zaznaczonych obiektów. Żeby utworzyć własną akcję wystarczy dodać funkcję, która w parametrze dostaje admina, request oraz queryset. Domyślacie się co od razu zrobiłem? Prosta akcja, która pozwala na aktywację wszystkich wybranych obiektów. Realizacja tego zajęła mi maksymalnie 5 minut a z perspektywy czasu widzę, że zaoszczędziłem przynajmniej kilka godzin, także warto. Poniżej przykład takiej akcji, którą dodaję bardzo często.

def activate_selected(modeladmin, request, queryset):
    queryset.update(active=True)
activate_selected.short_description = 'Aktywuj wybrane artykuły'


class ArticleAdmin(admin.ModelAdmin):
    [...]
    actions = [activate_selected]

Edycja elementów

To już drugi wpis a my nadal omawiamy listę elementów i nie doszliśmy do możliwości edycji. Elementy można dodawać i edytować w dedykowanym panelu, ale twórcy tego framework dodali jeszcze jedną ciekawą możliwość. Wynika ona trochę z potrzeb, których nie są w stanie zaspokoić akcję, na przykład podmiany tytułu dla wielu artykułów jednocześnie. W akcjach nie mamy wpływu co wpisać w dane pole. Dlatego akcje najczęściej stosuje się właśnie do edycji pól logicznych, usuwania obiektów, generowania raportów, czy zlecania zadań w tle. Do bardziej zaawansowanej edycji można użyć edycji obiektów na liście. Osobiście rzadko z tego korzystam i chyba tylko raz klient uparł się na to rozwiązanie ale wiem, że są projekty w których może to być bardzo przydatne. Dlatego bez zbędnego przedłużania niżej macie fragment kodu, który to prezentuje:

class ArticleAdmin(admin.ModelAdmin):
    [...]
    list_editable = ('active', 'title', )

W ten prosty sposób możemy edytować opis i aktywować (lub dezaktywować) wiele artykułów bez konieczności wchodzenia w każdy obiekt osobno.

Ilość elementów na stronie

Na liście domyślnie wyświetlanych jest 100 obiektów, żeby to zmienić wystarczy wpisać własną wartość:

class ArticleAdmin(admin.ModelAdmin): 
    [...]
    list_per_page = 400

W tym przypadku zwiększyłem to do 400, bo tak było wygodniej. Dodam tylko, że to właśnie ten atrybut pozwolił mi skutecznie uporać się z długim czasem ładowania strony. Wtedy mieliśmy problem z chmurą, która często wydłużała czas odpowiedzi. Na liście obowiązkowo musieliśmy wyświetlać miniatury zdjęcia, a niestety po każde musieliśmy osobno zapytać. Oczywiście po pierwszym wejściu wpadały w cache i nie było problemu, ale nie życzę nikomu pracy, w której musi po kilka razy odświeżać stronę, żeby móc wejść na listę obiektów. A tym bardziej jeżeli te obiekty dodawane są stosunkowo często, np jako zgłoszenia konkursowe użytkowników. O ile dobrze pamiętam chmura miała czas odpowiedzi do maksymalnie 0.2 sekundy a nasz serwer do 5 sekund. Zmniejszyłem zatem ilość elementów na stronie do 25, dzięki czemu nawet bardzo duże obciążenie przy generowaniu i pobieraniu miniaturek nie kładło całego serwisu. Mała rzecz a łatwo o niej zapomnieć. Co prawda na tamten przypadek składało się znacznie więcej czynników i nikt nie podejrzewał chmury jako jeden z kluczowych czynników . Dlatego to rozwiązanie wyszło całkiem przypadkiem ale dało nam lekcję pokory i cierpliwości.

Edycja elementów

Mamy już wszystko co ważniejsze na liście elementów. Możemy zatem przejść do dodawania i edycji elementu. Omawiam to wspólnie, gdyż jest to ten sam formularz. Co prawda na oddzielnych widokach, ale większość schematów jest podobna.

Wchodząc w panel edycji obiektu w górnym prawym rogu widoczny jest przycisk historii. Klikając w niego widzimy kto wprowadzał zmiany, oraz które pola były edytowalne. Niestety nie mamy pełnej historii zmian, ale często wystarcza i to do zlokalizowania problemów.

Łatwo można dodać drugi przycisk. Jeżeli do klasy modelu dodamy metodę get_absolute_url, to panel automatycznie wyłapie to i doda przycisk z podglądem. Bardzo przydatny przycisk, który przenosi do podglądu tego artykułu na front. Poniżej przykład tej metody:

from django.core.urlresolvers import reverse


class Article(models.Model):
    [...]
    def get_absolute_url(self):
        return reverse('article-detail', kwargs={'slug': self.slug})

Oczywiście, żeby ta metoda zadziałała potrzebny jest widok i wpis w urls.py. Nie będę jednak tego tu tłumaczył, bo jeszcze bardziej wydłużyłoby to wpis. Mam nadzieję, że umiecie to zrobić. Jeżeli nie, to piszcie w komentarzu a dodam wpis o widokach generycznych. Może nawet porównam właśnie widoku funkcyjne z klasowymi? To może być ciekawy temat. W pracy akurat siedzę pomiędzy dwoma osobami, które mają zupełnie skrajne poglądy co do ich zastosowań. Jeden uwielbia tworzyć widoki generyczne, bo to w końcu programowanie obiektowe, a drugi z kolei uważa, że to sztuka dla sztuki i gdy nie musi to po prostu ich unika. Obaj są bardzo dobrymi programistami, także już samo to może dać wiele do myślenia ;)

Tymczasem ponownie odsyłam do filmiku , z którym w 30 minut zbudujecie blog. Są tam omówione solidne podstawy i jeżeli poświęcicie ten czas to eksperymenty ze mną będą dużo łatwiejsze.

Wracając do tematu, teraz u wszystkich powinny być dwa przyciski. Jeżeli ktoś chce go wyłączyć, może to łatwo zrobić w następujący sposób:

class ArticleAdmin(admin.ModelAdmin):
    [...]
    view_on_site = False

Sam nie pamiętam kiedy ostatnio to wyłączałem. Natomiast dostosowanie tego do własnych potrzeb to zupełnie inna sprawa. Nie chcemy przecież, żeby artykuł był widoczny przed publikacją a warto zobaczyć jak wygląda w całości, zanim będziemy gotowi pokazać go światu. Jeżeli w widoku będziemy filtrować tylko aktywne artykuły to wejście w ten link wygeneruje błąd 404. Jest na to przynajmniej kilka sposobów Jednym z nich jest nadpisanie metody view_on_site. Podam najprostszy sposób z możliwych, czyli dodanie parametru podgląd w adresie.

class ArticleAdmin(admin.ModelAdmin):
    [...]
    def view_on_site(self, obj):
        return obj.get_absolute_url() + "podglad=1"

Teraz możecie podglądać artykuł przed jego publikacją. Oczywiście nie jest to rozwiązanie idealne i powinno się to nieco bardziej zabezpieczyć, ale celem było pokazanie Wam możliwości. W przyszłości planuję wpis o podglądzie obiektów, bo jest kilka ważnych elementów, na które trzeba zwrócić uwagę. Można też to dodać na kilka sposobów. Jeżeli Was to ciekawi zostawcie proszę informację, to postaram się porównać zaproponowane rozwiązania.

Zmień układ pól

Co jeżeli komuś nie podoba się układ pól wygenerowany automatycznie? Jest to dość częsty przypadek. Ja gdy tworzę aplikację, z reguły na górę daję podstawowe informacje, takie jak tytuł, slug, data publikacji i checkboxy mówiące na przykład o tym, czy obiekt jest aktywny. Dopiero dalej wydzielam pozostałe pola. Poniżej przykład jak w łatwy sposób tym zarządzać.

class ArticleAdmin(admin.ModelAdmin):
    [...]
    fieldsets = (
        (u'Dane podstawowe', {
            'fields': (('title', 'slug'), ('pub_date', 'active', 'category',))
        }),
        (u'Treść', {
            'classes': ('collapse', 'wide'),
            'fields': (
                'body', 'lead',
            ),
        }),
    )

Teraz na spokojnie to przeanalizujmy. Jak widzicie, dane można pogrupować na kilka różnych sposóbów. Zarządza tym atrybut fieldsets, który jest listą sekcji. Sekcje również są w formie list a więc mamy listę zbudowaną z list. Każda z tych sekcji jako pierwszy element posiada tytuł, który ją reprezentuję. Można zamiast tekstu dać None, wtedy cała sekcaj nie będzie w żaden sposób wyróżniona nagłówkiem. Drugim elementem jest słownik. W słowniku podaje się klucz fields i rzadziej klucz classes . Ten drugi definiuje jakie klasy chcemy dodać do naszych sekcji. Sam rzadko tego stosuję, ale jeżeli już się zdarzy to jedynie klasy collapse , która pozwala na dynamiczne ukrywanie treści. Przydaje się to, gdy mamy sporo dużych pól tekstowych. Pod pierwszym kluczem słownika zdefiniowane są właśnie pola. I tu również w postaci list. Te również można pogrupować w mniejsze. I tak w moim przypadku mam:

  • Sekcja: Dane podstawowe z dwiema grupami pól
    • Tytuł i slug
    • Data publikacji, Aktywacja artykułu oraz kategoria
  • Sekcja Treść z klasami collapse oraz wide i jedną grupą pól zawierającą:
    • Tekst i zajawkę

Nie wszystkie pola powinniśmy edytować

Wyobraźcie sobie system, w którym zgłaszają się użytkownicy. Możemy tym użytkownikom wystawić opinię, oceny i inne notatki. Ale nie chcemy zmieniać danych tego użytkownika, jednocześnie widząc z kim mamy do czynienia. Żeby to zrobić warto posłużyć się polami do odczytu.

class ArticleAdmin(admin.ModelAdmin):
    readonly_fields = ('pub_date', )

W ten sposób w miejscu daty publikacji, będziemy mieli odpowiednią informację ale nie będziemy mogli jej zmienić. Bardzo przydatna funkcja o której sam dowiedziałem się niedawno. Jedna tylko uwaga. Jeżeli nie zdefiniujemy atrybutu fieldsets, to pola tylko do odczytu trafiają na sam dół formularza.

Przyciski zapisu pod ręką

Jeżeli dodawaliście jakikolwiek wpis w adminie Django to zapewne uzupełnialiście po kolei wszystkie wymagane pola, a następnie wpisywaliście obiekt przy użyciu przycisków na samym dole. I jest to wygodna forma dodawania obiektu. Ale w przypadku edycji już nie do końca. Nasz model jest stosunkowo prosty. Ma mało pól, ale gdyby dodać edytor tekstu, na przykład TinyMCE to prawdopodobnie cały formularz nie zmieści się na jednym ekranie i żeby aktywować obiekt trzeba będzie przejść na sam dół strony. Dlatego bardzo często, o ile nie zawsze przy rejestracji admina dodaję dodatkowe przyciski na górze strony.

class ArticleAdmin(admin.ModelAdmin):
    [...]
    save_on_top = True

Używaj pola slug

Pole slug, to pole które przydaje się bardzo często. Można go użyć na przykład do adresu URL. W django jest ciekawa wbudowana opcja automatycznego generowania slug na podstawie innego pola. To znaczy nie koniecznie pola slug. Chodzi o to, że można zezwolić na automatyczne uzupełnianie się pola poprzez nasłuchiwanie innego. Może łatwiej wyjaśni to przykład:

class ArticleAdmin(admin.ModelAdmin):
    [...]
    prepopulated_fields = {'slug': ('title', )}

W tym, konkretnym przypadku odnosimy sie właśnie do wspomnianiego wcześniej slug i title. Jest to prawdopodobonie niezbędnik każdego formularza, który ma pole slug.

Select nie zawsze jest wygodny

Podczas używania klucza do innego modelu Django automatycznie tworzy nam rozwijalną listę do wyboru w adminie. Bardzo wygodne, bo nie musimy się tym przejmować. W naszym przypadku w ten sposób wybiera się kategorię do artykułu. Problem pojawia się gdy kategorii zaczyna przybywać. Nie wiem czy ktokolwiek z Was używał zwykłego select gdy obiektów jest powiedzmy dwieście lub trzysta. Było to wygodne? Teraz wyobraźcie sobie, że pracowałem przy projekcie w którym w ten sposób wybierało się zdjęcia. A zdjęć było ponad dwieście ale tysięcy. Z rozwijalnej listy nie dało się tego wyciągnąć. To były moje początki i starszy programista pokazał mi bardzo ciekawe rozwiązanie, które prezentuję poniżej.

class ArticleAdmin(admin.ModelAdmin):
    [...]
    raw_id_fields = ('category', )

W atrybucie raw_id_fields wybieramy, które pola mają być jako tak zwana lupka zamiast rozwijalnej listy. Klikając w lupę pojawia się popup z listą obiektów. Dokładnie taką listą, nad którą wcześniej pracowaliśmy, także można spokojnie dodać tam filtry czy wyszukiwarkę i korzystanie z tego od razu staje się prostsze.

Dodawaj obiekty w inline

Inline to specjalny mechanizm łączenia formularzy. Jeżeli czytaliście oba wpisy o adminie uważnie, to wiecie, że artykuł ma relację to kategorii. Zatem kategoria ma listę podpiętych artykułów zgadza się? Artykuł, może nie jest tu najlepszym przykładem, bo jest dość rozbudowany ale nic nie stoi na przeszkodzie, żeby tego użyć. A więc istnieje możliwość dodawania artykułów w kategorii już podczas dodawania samej kategorii. Wystarczy dodać nową klasę w adminie i odpowiednią ją zarejestrować:

class ArticleAdminInline(admin.TabularInline):
    model = Article


@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
    list_display = ('id', 'title', 'articles_link')
    inlines = (ArticleAdminInline, )

W ten prosty sposób dodaliśmy taką możliwość. TabularInline to klasa z której dziedziczymy jeżeli chcemy żeby kolejne obiekty zaprezentowane były w formie tabelki. Sam dużo częściej stosuję StackedInline, który moim zdaniem jest czytelniejszy

class ArticleAdminInline(admin.StackedInline):
    model = Article

@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
    list_display = ('id', 'title', 'articles_link')
    inlines = (ArticleAdminInline, )

Oczywiście w tych klasach które są zagnieżdżone można wykorzystywać to czego się już nauczyliście. Zachęcam gorąco do eksperymentowania, bo w prosty sposób można stworzyć ciekawy panel.

Jeżeli mówimy o inline to koniecznie wspomnijmy o atrybucie extra , który definiuje ile obiektów domyślnie będzie dołączonych. Wszystkie są oczywiście puste, ale domyślne dodane, żeby szybciej się je dodawało. Domyślnie wyświetlają się trzy a ja najczęściej pokazuję jeden lub wcale. W tym drugim przypadku użytkownik musi sam zdecydować, że chce ten inline dodać. Robię to z wielu powodów. Ale to bardzo rozbudowany temat, bo te inline można zagnieżdżać i mieć dwa poziomy. Można je fajnie wykorzystać ale mają też kilka błędów. Dlatego w przyszłości na pewno dodam o tym wpis. A jak zmienić ten atrybut widać poniżej:

class ArticleAdminInline(admin.StackedInline):                                  
    model = Article                                                             
    extra = 0

Bonus

Na sam koniec mały bonus. Pierwszy raz użyłem tego po ponad 3 latach z tym frameworkiem. Gdy ktoś zadał mi pytanie czy można zmienić nagłówek z Administracja Djang o na własny zdziwiłem się, bo nie czułem potrzeby takiej zmiany. Szybko jednak znalazłem rozwiązanie. Jest ich kilka, ale najłatwiejsze polega na dodaniu tytułu w pliku urls.py tak jak poniżej:

from django.contrib import admin

admin.site.site_header = "Panel administracyjny"

I tak prawdopodobnie macie tam już ten import, więc wystarczy jedna linijka. Drobnostka, która sprawia, że projekt wygląda bardziej profesjonalnie.

Mam nadzieję, że wpis okazał się wartościowy i wynieśliście z niego coś dla siebie. Temat admina Django, jest tak obszerny, że nie wystarczyłoby mi i dziesięciu wpisów na omówienie wszystkiego dlatego skupiłem się na najbardziej kluczowych elementach.

W przyszłości na pewno dodam jeszcze wpis odnośnie filtrów horyzontalnych, własnych widgetów, czy innych już znacznie bardziej zaawansowanych sztuczek w panelu.

Wykorzystany kod napisany został w Python2.7 oraz Django1.11.

Jan 23, 2019

Najnowsze wpisy

Zobacz wszystkie