# Django 5.2.9 - dokumentacja

# Pisanie pierwszej aplikacji Django, część 7.

Ten tutorial zaczyna się, gdzie został przerwany Tutorial 6. Kontynuujemy tworzenie aplikacji internetowych ankiet i skupimy się na dostosowaniu do naszych potrzeb automatycznie wygenerowanego panelu administracyjnego Django, który pierwotnie odkrywaliśmy w Tutorialu 2.

Gdzie szukać pomocy:

Jeśli masz trudności w przejściu tego tutorialu, przejdź do sekcji Uzyskiwanie pomocy często zadawanych pytań.

## Dostosuj formularz panelu administracyjnego

Rejestrując model `Question` przez `admin.site.register(Question)`, umożliwiłeś Django skonstruowanie domyślnej reprezentacji formularza. Często będziesz chciał zmienić to jak formularz panelu administracyjnego wygląda i działa. Będziesz to robił mówiąc Django, jakie wybierasz opcje, rejestrując obiekt.

Zobaczmy jak to działa zmieniając kolejność pól w formularzu edycji. Zamień linię `admin.site.register(Question)` na:

`polls/admin.py`

    from django.contrib import admin

    from .models import Question

    class QuestionAdmin(admin.ModelAdmin):
        fields = ["pub_date", "question_text"]

    admin.site.register(Question, QuestionAdmin)

Będziesz realizował ten wzorzec stworzenie klasy model admin, następnie przekazanie jej jako drugiego argumentu do `admin.site.register()` – w dowolnym momencie, kiedy potrzebujesz zmienić opcje admina dla modelu.

Konkretnie ta zmiana powyżej powoduje, że „Publication date” jest przed polem „Question”:

Nie robi to wrażenia z tylko dwoma polami, ale dla formularzy admina z tuzinami pól, wybranie intuicyjnego porządku jest ważnym szczegółem użyteczności.

A mówiąc o formularzach z tuzinami pól, możesz chcieć podzielić formularz na fieldsety:

`polls/admin.py`

    from django.contrib import admin

    from .models import Question

    class QuestionAdmin(admin.ModelAdmin):
        fieldsets = [
            (None, {"fields": ["question_text"]}),
            ("Date information", {"fields": ["pub_date"]}),
        ]

    admin.site.register(Question, QuestionAdmin)

Pierwszym elementem każdej krotki w `fieldsets` jest tytuł fieldsetu. Tak wygląda teraz nasz formularz:

## Dodawanie powiązanych obiektów

Ok, mamy stronę admina dla pytań, ale `Question` ma wiele wyborów i strona admina tych wyborów nie wyświetla.

Jeszcze nie wyświetla.

Są dwa sposoby rozwiązania tego problemu. Pierwszy to zarejestrować `Choice` z adminem tak jak zrobiliśmy przed chwilą z `Question`:

`polls/admin.py`

    from django.contrib import admin

    from .models import Choice, Question

    # ...
    admin.site.register(Choice)

Teraz „Choices” są dostępną opcją w adminie Django. Formularz „Add choice” wygląda tak:

W tym formularzu pole „Question” jest polem wyboru zawierającym wszystkie pytania z bazy danych. Django wie, że `ForeignKey` powinno być reprezentowane w adminie jako `<select>`. W naszym przypadku istnieje tylko jedno pytanie w tym momencie.

Zwróć też uwagę na link „Add another question” przy „Question”. Każdy obiekt z relacją `ForeignKey` do innego zyskuje go automatycznie. Kiedy klikniesz „Add another question”, dostaniesz okno popup z formularzem „Add question”. Jeśli dodasz pytanie w tym oknie i klikniesz „Save”, Django zapisze pytanie do bazy danych i dynamicznie doda je jako wybrane pytanie w formularzu „Add choice”, na który patrzysz.

Ale tak naprawdę to niewydajny sposób na dodawanie obiektów `Choice` do systemu. Byłoby lepiej, jeśli mógłbyś dodać zestaw wyborów bezpośrednio, gdy tworzysz obiekt `Question`. Zróbmy tak.

Usuń wywołanie `register()` dla modelu `Choice`. Następnie zmodyfikuj kod rejestracji `Question`, by wyglądał tak:

`polls/admin.py`

    from django.contrib import admin

    from .models import Choice, Question

    class ChoiceInline(admin.StackedInline):
        model = Choice
        extra = 3

    class QuestionAdmin(admin.ModelAdmin):
        fieldsets = [
            (None, {"fields": ["question_text"]}),
            ("Date information", {"fields": ["pub_date"], "classes": ["collapse"]}),
        ]
        inlines = [ChoiceInline]

    admin.site.register(Question, QuestionAdmin)

Mówi to Django: „Obiekty `Choice` są edytowane na stronie admina `Question`. Domyślnie wyświetl pól wystarczająco na 3 wybory”.

Załaduj stronę „Add question”, by zobaczyć jak to wygląda:

Działa to tak: Są trzy miejsca na powiązane wybory – tak jak jest wskazane przez `extra` – i za każdym razem, kiedy powrócisz na stronę „Change dla już stworzonego obiektu, dostaniesz kolejne trzy dodatkowe miejsca.

At the end of the three current slots you will find an „Add another Choice” link. If you click on it, a new slot will be added. If you want to remove the added slot, you can click on the X to the top right of the added slot. This image shows an added slot:

Jest jednak jeden mały problem. Wyświetlenie wszystkich pól do wypełnienia powiązanych obiektów `Choice` zajmuje ogromną ilość miejsca na ekranie. Z tego powodu Django oferuje sposób tabelaryczny na wyświetlenie „inline” powiązanych obiektów. Aby go użyć, zmień deklarację `ChoiceInline`, aby brzmiała:

`polls/admin.py`

    class ChoiceInline(admin.TabularInline): ...

Z `TabularInline` (zamiast `StackedInline`), powiązane obiekty są wyświetlane w bardziej zwartym, opartym na tabelce formacie:

Zwróć uwagę, że jest tam dodatkowa kolumna „Delete?”, która pozwala na usuwanie wierszy dodanych przy użyciu przycisku „Add another Choice” oraz wierszy, które zostały wcześniej zapisane.

## Dostosuj widok listy panelu administracyjnego

Teraz, kiedy strona admina dla pytania wygląda dobrze, poprawmy nieco stronę „change list” – tę, która wyświetla wszystkie pytania w systemie.

Tak ona wygląda w tym momencie:

By default, Django displays the `str()` of each object. But sometimes it’d be more helpful if we could display individual fields. To do that, use the `list_display` admin option, which is a list of field names to display, as columns, on the change list page for the object:

`polls/admin.py`

    class QuestionAdmin(admin.ModelAdmin):
        # ...
        list_display = ["question_text", "pub_date"]

Na wszelki wypadek dodajmy też metodę `was_published_recently()` z Tutoriala 2:

`polls/admin.py`

    class QuestionAdmin(admin.ModelAdmin):
        # ...
        list_display = ["question_text", "pub_date", "was_published_recently"]

Teraz strona „change list” pytań wygląda w ten sposób:

Możesz klikać w nagłówki kolumn, aby sortować po tych wartościach – z wyjątkiem przypadku nagłówka `was_published_recently`, ponieważ sortowanie po wyniku arbitralnej metody nie jest wspierane. Zwróć też uwagę, że nagłówek kolumny dla `was_published_recently` jest domyślnie nazwą metody (z podkreślnikami zamienionymi na spacje) i każda linia zawiera reprezentację w ciągu znaków wyniku.

You can improve that by using the `display()` decorator on that method (extending the `polls/models.py` file that was created in Tutorial 2), as follows:

`polls/models.py`

    from django.contrib import admin

    class Question(models.Model):
        # ...
        @admin.display(
            boolean=True,
            ordering="pub_date",
            description="Published recently?",
        )
        def was_published_recently(self):
            now = timezone.now()
            return now - datetime.timedelta(days=1) <= self.pub_date <= now

Po więcej informacji na temat właściwości konfigurowalnych przez ten dekorator, zobacz `list_display`.

Zmień znów swój plik `polls/admin.py` i dodaj ulepszenie do strony „change list” `Questions`: filtry, z użyciem `list_filter`. Dodaj następującą linię do `QuestionAdmin`:

    list_filter = ["pub_date"]

Doda to sidebar „Filter”, który pozwala ludziom filtrować listę obiektów używając pola `pub_date`:

Typ wyświetlanego filtru zależy od typu pola, po którym filtrujesz. Ponieważ `pub_date` to `DateTimeField`, Django wie, aby dać odpowiednie opcje w filtrze: „Any date”, „Today”, „Past 7 days”, „This month”, „This year”.

Dobrze się to zarysowuje. Dodajmy trochę możliwości wyszukiwania:

    search_fields = ["question_text"]

To dodaje pole wyszukiwania na górze „change listy”. Kiedy ktoś wprowadzi wyrażenie wyszukiwania, Django przeszuka pole `question_text`. Możesz użyć tyle pól, na ile masz ochotę – choć z powodu, że pod maską użyje to zapytania `LIKE`, ograniczenie liczby pól wyszukiwania do rozsądnej liczby ułatwi twojej bazie danych wykonać wyszukiwanie.

Teraz jest dobry moment, by zwrócić uwagę, że „change lista” daje ci wolną paginację. Domyślnie wyświetla ona 100 elementów na stronę. `Zmień paginację listy`, `pola wyszukiwania`, `filtry`, `hierarchie dat` i:attr:kolejność kolumn nagłówków <django.contrib.admin.ModelAdmin.list_display> tak, aby wspólnie współgrały tak, jak uważasz, że powinny.

## Dostosuj wygląd i pracę z panelem administracyjnym

Oczywiście, wyświetlanie napisu „Django administration” na górze każdej strony panelu administracyjnego jest bez sensu. To tylko tekst zastępczy.

Możesz go zmienić przy użyciu systemu szablonów Django. Panel administracyjny Django jest napędzany przez samo Django i jego interfejsy używają własnego systemu szablonów Django.

### Dostosowywanie szablonów twojego projektu

Create a `templates` directory in your `djangotutorial` directory. Templates can live anywhere on your filesystem that Django can access. (Django runs as whatever user your server runs.) However, keeping your templates within the project is a good convention to follow.

Otwórz swój plik ustawiń (`mysite/settings.py`, pamiętaj) i dodaj opcję `DIRS` w ustawieniu `TEMPLATES`:

`mysite/settings.py`

    TEMPLATES = [
        {
            "BACKEND": "django.template.backends.django.DjangoTemplates",
            "DIRS": [BASE_DIR / "templates"],
            "APP_DIRS": True,
            "OPTIONS": {
                "context_processors": [
                    "django.template.context_processors.request",
                    "django.contrib.auth.context_processors.auth",
                    "django.contrib.messages.context_processors.messages",
                ],
            },
        },
    ]

`DIRS` jest listą katalogów systemu plików, które należy sprawdzić ładując szablony Django; jest ścieżką wyszukiwania.

Organizacja szablonów

Tak samo jak pliki statyczne, moglibyśmy mieć wszystkie nasze szablony razem, w jednym dużym katalogu na szablony i mogłoby to całkowicie dobrze działać. Jednakże szablony, które należą do poszczególnej aplikacji powinny być umieszczane raczej w katalogu szablonów tej aplikacji (np. `polls/templates`) niż w katalogu szablonów projektu (`templates`). Omówimy bardziej szczegółowo w ``samouczku aplikacji wielokrotnego użytku </intro/reusable-apps>` dlaczego to robimy.

Teraz stwórz katalog o nazwie `admin` wewnątrz `templates` i skopiuj szablon `admin/base_site.html` z katalogu domyślnych szablonów panelu administracyjnego z kodu źródłowego Django (django/contrib/admin/templates) do tego katalogu.

Gdzie są pliki źródłowe Django?

Jeśli masz trudność w odnalezieniu miejsca, gdzie są zlokalizowane pliki źródłowe Django w twoim systemie, uruchom następujące polecenie:

/ 

    $ python -c "import django; print(django.__path__)"

    ...\> py -c "import django; print(django.__path__)"

Następnie zmień plik i zamień `{{ site_header|default:_('Django administration') }}` (wliczając nawiasy klamrowe) na nazwę swojej strony, która będzie pasować. Powinieneś skończyć z sekcją kodu typu:

    {% block branding %}
    <div id="site-name"><a href="{% url 'admin:index' %}">Polls Administration</a></div>
    {% if user.is_anonymous %}
      {% include "admin/color_theme_toggle.html" %}
    {% endif %}
    {% endblock %}

Używamy tego podejścia, aby nauczyć cię jak nadpisywać szablony. W prawdziwym projekcie, prawdopodobnie użyłbyś atrybutu `django.contrib.admin.AdminSite.site_header`, aby w prostszy sposób zrobić tę konkretną zmianę.

Ten plik szablonu zawiera pełno tekstów typu `{% block branding %}` i `{{ title }}`. Tagi `{%` i `{{` są częścią języka szablonów Django. Kiedy Django renderuje `admin/base_site.html`, ten język szablonów jest ewaluowany i produkuje ostateczną stronę HTML, tak jak widzieliśmy w Tutorialu 3.

Zwróć uwagę, że każdy domyślny szablon panelu administracyjnego Django może być nadpisany. Aby nadpisać szablon, zrób tę samą czynność, którą zrobiłeś z `base_site.html` – skopiuj je z domyślnego katalogu do swojego własnego katalogu i zrób zmiany.

### Zmiana szablonów twojej `aplikacji`

Bystrzy czytelnicy zapytają: Ale skoro `DIRS` było domyślnie puste, jak Django odnajdywało domyślne szablony admina? Odpowiedzią jest, tak długo jak `APP_DIRS` jest ustawione na `True`, Django automatycznie szuka podkatalogu `templates/` w każdym pakiecie aplikacji, aby użyć go jako fallback (nie zapomnij, że `django.contrib.admin` jest aplikacją).

Nasza aplikacja ankietowa nie jest bardzo złożona i nie potrzebuje customowych szablonów admina. Lecz gdyby urosła do bardziej skomplikowanej i wymagała modyfikacji standardowych szablonów admina do jakiś swoich funkcjonalności, byłoby bardziej rozsądne modyfikować szablony aplikacji niż te w projekcie. W ten sposób mógłbyś zawrzeć aplikację ankietową w dowolnym nowym projekcie i miałbyś pewność, że odnajdzie customowe szablony, których potrzebuje.

W dokumentacji ładowania szablonów więcej informacji o tym, jak Django odnajduje swoje szablony.

## Dostosuj stronę indeksu panelu administracyjnego

W podobnym tonie możesz chcieć zmienić wygląd i odbiór strony indeksu panelu admina Django.

Domyślnie wyświetla ona wszystkie aplikacje w `INSTALLED_APPS`, które zostały zarejestrowane z aplikacją admin, w kolejności alfabetycznej. Możesz chcieć dokonać istotnych zmian w wyglądzie. W końcu, strona główna jest prawdopodobnie najważniejszą stroną admina i powinna być łatwa w użyciu.

The template to customize is `admin/index.html`. (Do the same as with `admin/base_site.html` in the previous section – copy it from the default directory to your custom template directory). Edit the file, and you’ll see it uses a template variable called `app_list`. That variable contains every installed Django app. Instead of using that, you can hardcode links to object-specific admin pages in whatever way you think is best.

Kiedy już czujesz się dobrze z panelem administracyjnym, przeczytaj część 8 tego tutoriala, aby nauczyć się używać zewnętrznych pakietów.