django-admin: criando um AdminSite customizado sem perder o registro automático dos Models
Tags: django, webdev, python, braziliandevs
Published: 2022-11-11 20:50:52

Ao trabalhar com `django`, principalmente utilizando o `django-admin`, é bastante comum não nos preocuparmos, sempre, com os *models* que são registrados ao painel de *admin* padrão.

O motivo disso é bem simples: na maioria dos casos não precisamos tocar na instância padrão da classe `AdminSite`, que é a classe que define como o painel *admin* irá ser exibido. 

Quando não precisamos trocar a instância do `AdminSite` que utilizamos, o registro automático dos *models* sempre nos satisfaz.

Porém, ao surgir a necessidade de sobrescrever o `AdminSite` padrão, costumamos perder o registro automático dos *models* feitos pelos diversos pacotes (*libs*, bibliotecas, etc.) que instalamos no nosso projeto.

## Motivação

A motivação pra se lidar com esse problema, é que em algumas situações precisamos alterar o `AdminSite` padrão. Um exemplo clássico e que costuma surgir bastante é: quando é preciso ordenar os *Apps* e os *Models* na página inicial do nosso *admin*.

## O AdminSite Padrão

Quando executamos o comando abaixo, para iniciar um novo projeto (existem outras variações do comando, não se preocupe), por padrão, teremos o arquivo `core/urls.py` do código a seguir

```shell
django-admin startproject core .
```

```python
# core/urls.py
from django.contrib import admin
from django.urls import path

urlpatterns = [
    path('admin/', admin.site.urls),
]
```

Nesse arquivo, é registrada uma instância padrão do `AdminSite` que, por sua vez, é importada do caminho: `django.contrib.admin.site`.

Com o projeto em execução (`python manage.py runserver`), ao realizar login no painel de admin, teremos a seguinte tela:

![tela inicial do django-admin](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hl0vgdlwhjnjm5b22kzi.png)

## Alterando o AdminSite

Para alterar o `AdminSite` é bem simples, criando um arquivo `core/site.py`:

```python
# core/site.py
from django.contrib.admin import AdminSite

class CustomSite(AdminSite):
    pass

site = CustomSite()
```

E alterando, então, o `core/urls.py`:

```
# core/urls.py
from django.urls import path
from .site import site

urlpatterns = [
    path('admin/', site.urls),
]
```

A tela inicial do *admin* nesse momento, assume uma outra estética, sem permissão pra visualizar ou editar nada (pois não há nenhum *model* registrado):

![página inicial do admin do django sem models](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lcir5ojotolsi2do243n.png)

## AdminSite Padrão

No exemplo acima, substituímos o import do `admin.site` original pelo `.site.admin`. Podemos alterar, porém, esse código para substituir o `AdminSite` padrão do projeto e, dessa forma, não perderemos os *models* registrados automáticamente:

```python
# core/site.py
from django.contrib.admin import AdminSite

class CustomSite(AdminSite):
    def get_app_list(self, request):
        """
        Return a sorted list of all the installed apps that have been
        registered in this site.
        """
        app_dict = self._build_app_dict(request)

        # Sort the apps alphabetically.
        app_list = sorted(app_dict.values(), key=lambda x: x['name'].lower())

        # Sort the models alphabetically within each app.
        for app in app_list:
            app['models'].sort(key=lambda x: x['name'])
            app['models'].reverse()

        return app_list
```

Foi adicionado, aqui, um código apenas para alterar a ordem dos *models* dentro dos *apps* para que possamos ver efeitos visuais na tela (Referência do código [aqui](https://books.agiliq.com/projects/django-admin-cookbook/en/latest/set_ordering.html)). Além disso, foi removida aquela chamada `site = CustomSite()`.

A partir de agora, é preciso reverter o `core/urls.py`:

```python
# core/urls.py
from django.contrib import admin
from django.urls import path

urlpatterns = [
    path('admin/', admin.site.urls),
]
```

Além desses dois passos, é preciso criar um `core/apps.py` onde será criada uma instância da classe `AdminConfig` definindo quem é o `AdminSite` padrão:

```python
# core/apps.py
from django.contrib.admin.apps import AdminConfig

class MyAdminConfig(AdminConfig):
    default_site = 'core.site.CustomSite'
```

Por último, no `settings.py`:

```python
# core/settings.py

# ...

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

# ...
```

É preciso substituir o `django.contrib.admin` pelo `core.apps.MyAdminConfig`:

```python
# core/settings.py

# ...

INSTALLED_APPS = [
    'core.apps.MyAdminConfig',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

# ...
```

## Resultados

Quando entramos na página inicial do *admin* agora, temos a seguinte exibição:


![django-admin](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3htlmaxbne69hapujfre.png)

Quando alteramos o código comentando a seguinte linha, passamos a ter outro resultado:

```python
# app['models'].reverse()
```


![django-admin](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gy6kfk1f89ax032zc62e.png)

O código fonte final desse texto está disponível no [GitHub](https://github.com/EduardoJM/code-of-articles/tree/main/custom-admin-site).


## Referências

- [The Django admin site](https://docs.djangoproject.com/en/4.1/ref/contrib/admin) - Django Documentation

- [5. How to set ordering of Apps and models in Django admin dashboard](https://books.agiliq.com/projects/django-admin-cookbook/en/latest/set_ordering.html) - Django Admin Cookbook