# Django 6.0.5 documentation

# Skriva din första Django-app, del 7

Denna handledning börjar där Tutorial 6 slutade. Vi fortsätter med web-poll applikationen och kommer att fokusera på att anpassa Djangos automatiskt genererade admin-webbplats som vi först utforskade i Tutorial 2.

Var du kan få hjälp:

Om du har problem med att gå igenom den här handledningen kan du gå till avsnittet Att få hjälp i FAQ.

## Anpassa administratörsformuläret

Genom att registrera modellen `Question` med `admin.site.register(Question)` kunde Django konstruera en standardformulärrepresentation. Ofta vill du anpassa hur adminformuläret ser ut och fungerar. Du gör detta genom att berätta för Django de alternativ du vill ha när du registrerar objektet.

Låt oss se hur detta fungerar genom att omorganisera fälten i redigeringsformuläret. Ersätt raden `admin.site.register(Question)` med:

`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)

Du följer det här mönstret - skapa en modelladministratörsklass och skicka den sedan som det andra argumentet till `admin.site.register()` - varje gång du behöver ändra administratörsalternativen för en modell.

Den här ändringen gör att ”Publiceringsdatum” kommer före fältet ”Fråga”:

Detta är inte imponerande med bara två fält, men för adminformulär med dussintals fält är det en viktig användbarhetsdetalj att välja en intuitiv ordning.

Och på tal om formulär med dussintals fält kanske du vill dela upp formuläret i fältuppsättningar:

`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)

Det första elementet i varje tupel i `fieldsets` är titeln på fältuppsättningen. Så här ser vårt formulär ut nu:

## Lägga till relaterade objekt

Okej, vi har vår administratörssida för frågor, men en ”fråga” har flera ”val” och administratörssidan visar inte valen.

Än så länge.

Det finns två sätt att lösa detta problem. Det första är att registrera `Choice` hos administratören precis som vi gjorde med `Question`:

`polls/admin.py`

    from django.contrib import admin

    from .models import Choice, Question

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

Nu är ”Choices” ett tillgängligt alternativ i Django-admin. Formuläret ”Lägg till val” ser ut så här:

I det formuläret är fältet ”Question” en valbox som innehåller alla frågor i databasen. Django vet att en `ForeignKey` ska representeras i admin som en `<select>` box. I vårt fall finns det bara en fråga vid denna tidpunkt.

Also note the ”Add another question” button (displayed as a plus sign) to the right of the ”Question” field. Every `ForeignKey` relationship gets this button for free. When you click this button, you’ll get a popup window with the ”Add question” form. If you add a question in that window and click ”Save”, Django will save the question to the database and dynamically add it as the selected choice on the ”Add choice” form you’re looking at.

Men egentligen är det här ett ineffektivt sätt att lägga till `Choice`-objekt i systemet. Det vore bättre om du kunde lägga till ett gäng Choices direkt när du skapar `Question`-objektet. Låt oss få det att hända.

Ta bort anropet `register()` för modellen `Choice`. Redigera sedan registreringskoden för `Question` så att den lyder:

`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)

Detta säger till Django: ”Objekten `Choice` redigeras på administratörssidan för `Question`. Som standard ska du tillhandahålla tillräckligt med fält för 3 val.”

Ladda sidan ”Lägg till fråga” för att se hur det ser ut:

Det fungerar på följande sätt: Det finns tre platser för relaterade Choices - som anges med `extra` - och varje gång du kommer tillbaka till sidan ”Change” för ett redan skapat objekt får du ytterligare tre extra platser.

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:

Ett litet problem, dock. Det tar mycket skärmutrymme att visa alla fält för att ange relaterade `Choice`-objekt. Av den anledningen erbjuder Django ett tabulärt sätt att visa inline relaterade objekt. För att använda det, ändra `ChoiceInline` deklarationen till att läsa:

`polls/admin.py`

    class ChoiceInline(admin.TabularInline): ...

Med denna `TabularInline` (istället för `StackedInline`) visas de relaterade objekten i ett mer kompakt, tabellbaserat format:

Observera att det finns en extra ”Delete?”-kolumn som gör det möjligt att ta bort rader som lagts till med knappen ”Add another Choice” och rader som redan har sparats.

## Anpassa ändringslistan för administratörer

Nu när administratörssidan för frågor ser bra ut ska vi göra några justeringar på sidan ”ändringslista” - den sida som visar alla frågor i systemet.

Så här ser det ut i nuläget:

Som standard visar Django `str()` för varje objekt. Men ibland skulle det vara till större hjälp om vi kunde visa enskilda fält. För att göra det använder du adminalternativet `list_display`, som är en lista med fältnamn som ska visas, som kolumner, på sidan med ändringslistan för objektet:

`polls/admin.py`

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

För säkerhets skull inkluderar vi även metoden `was_published_recently()` från Tutorial 2:

`polls/admin.py`

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

Nu ser sidan med frågeändringslistan ut så här:

Du kan klicka på kolumnrubrikerna för att sortera efter dessa värden - utom när det gäller rubriken `was_published_recently`, eftersom det inte finns stöd för sortering efter resultatet av en godtycklig metod. Observera också att kolumnrubriken för `was_published_recently` som standard är namnet på metoden (med understreck ersatta med mellanslag), och att varje rad innehåller strängrepresentationen av utdata.

Du kan förbättra det genom att använda `display()`-dekoratorn på den metoden (utöka `polls/models.py`-filen som skapades i Tutorial 2), enligt följande:

`polls/modeller.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

För mer information om de egenskaper som kan konfigureras via dekoratorn, se `list_display`.

Redigera filen `polls/admin.py` igen och lägg till en förbättring av sidan med ändringslistan `Question`: filter med hjälp av `list_filter`. Lägg till följande rad i `QuestionAdmin`:

    list_filter = ["pub_date"]

Det lägger till en ”Filter”-sidofält som låter människor filtrera ändringslistan efter fältet `pub_date`:

Vilken typ av filter som visas beror på vilken typ av fält du filtrerar på. Eftersom `pub_date` är en `DateTimeField`, vet Django att ge lämpliga filteralternativ: ”Vilket datum som helst”, ”Idag”, ”De senaste 7 dagarna”, ”Den här månaden”, ”Det här året”.

Det här ser bra ut. Låt oss lägga till lite sökfunktionalitet:

    search_fields = ["question_text"]

Det lägger till en sökruta längst upp i ändringslistan. När någon skriver in söktermer kommer Django att söka i fältet `question_text`. Du kan använda så många fält som du vill - men eftersom det använder en `LIKE`-fråga bakom kulisserna, kommer det att göra det lättare för din databas att göra sökningen genom att begränsa antalet sökfält till ett rimligt antal.

Nu är det också en bra tid att notera att ändringslistor ger dig gratis paginering. Standard är att visa 100 objekt per sida. `Change list pagination`, `search boxes`, `filters`, `date-hierarchies`, och `column-header-ordering` fungerar alla tillsammans som du tror att de ska.

## Anpassa administratörens utseende och känsla

Det är uppenbart att det är löjligt att ha ”Django administration” högst upp på varje adminsida. Det är bara platshållartext.

Du kan dock ändra det med hjälp av Djangos mallsystem. Django-admin drivs av Django själv och dess gränssnitt använder Djangos eget mallsystem.

### Anpassa mallarna för ditt projekt

Skapa en `templates` katalog i din `djangotutorial` katalog. Mallar kan finnas var som helst på ditt filsystem som Django kan komma åt. (Django körs som vilken användare som helst som din server kör.) Att hålla dina mallar inom projektet är dock en bra konvention att följa.

Öppna din inställningsfil (`mysite/settings.py`, kom ihåg) och lägg till ett `DIRS` alternativ i `TEMPLATES` inställningen:

`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` är en lista över filsystemkataloger som ska kontrolleras när Django-mallar laddas; det är en sökväg.

Organisera mallar

Precis som med de statiska filerna skulle vi kunna ha alla våra mallar samlade i en enda stor mallkatalog, och det skulle fungera alldeles utmärkt. Mallar som tillhör en viss applikation bör dock placeras i den applikationens mallkatalog (t.ex. `polls/templates`) snarare än i projektets (`templates`). Vi kommer att diskutera mer i detalj i återanvändbara apps handledning varför vi gör detta.

Skapa nu en katalog som heter `admin` inuti `templates`, och kopiera mallen `admin/base_site.html` från standardkatalogen för Djangos adminmallar i källkoden för Django själv (django/contrib/admin/templates) till den katalogen.

Var finns Djangos källfiler?

Om du har svårt att hitta var Djangos källfiler finns på ditt system kör du följande kommando:

/ 

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

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

Redigera sedan filen och ersätt `{{ site_header|default:_('Django administration') }}` (inklusive hakparenteserna) med namnet på din egen webbplats som du tycker passar. Du bör sluta med ett avsnitt av kod som:

    {% 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 %}

Vi använder detta tillvägagångssätt för att lära dig hur du kan åsidosätta mallar. I ett verkligt projekt skulle du förmodligen använda attributet `django.contrib.admin.AdminSite.site_header` för att lättare göra just den här anpassningen.

Den här mallfilen innehåller mycket text som `{% block branding %}` och `{{ title }}`. Taggarna `{%` och `{{` är en del av Djangos mallspråk. När Django renderar `admin/base_site.html`, kommer detta mallspråk att utvärderas för att producera den slutliga HTML-sidan, precis som vi såg i Tutorial 3.

Observera att alla Djangos standardmallar för administratörer kan åsidosättas. För att åsidosätta en mall gör du samma sak som du gjorde med `base_site.html` - kopiera den från standardkatalogen till din anpassade katalog och gör ändringar.

### Anpassa mallarna för din applikation

Smarta läsare kommer att fråga: Men om `DIRS` var tom som standard, hur hittade Django standardmallarna för admin? Svaret är att, eftersom `APP_DIRS` är inställd på `True`, letar Django automatiskt efter en `templates/` underkatalog inom varje applikationspaket, för användning som en fallback (glöm inte att `django.contrib.admin` är en applikation).

Vår poll-applikation är inte särskilt komplex och behöver inte anpassade admin-mallar. Men om det blir mer sofistikerat och kräver modifiering av Djangos standardadministratörsmallar för vissa av dess funktioner, skulle det vara mer förnuftigt att modifiera applikationens mallar, snarare än de i projektet. På så sätt kan du inkludera pollsapplikationen i alla nya projekt och vara säker på att den hittar de anpassade mallar den behöver.

Se dokumentation för laddning av mallar för mer information om hur Django hittar sina mallar.

## Anpassa indexsidan för administratörer

På ett liknande sätt kanske du vill anpassa utseendet på Django-administratörens indexsida.

Som standard visas alla appar i `INSTALLED_APPS` som har registrerats i adminprogrammet i alfabetisk ordning. Du kanske vill göra betydande ändringar i layouten. När allt kommer omkring är indexet förmodligen den viktigaste sidan i admin, och den bör vara lätt att använda.

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.

När du känner dig bekväm med administratören kan du läsa del 8 av denna handledning för att lära dig hur du använder tredjepartspaket.