This guide covers the basic Django project set, making sure Django admin is enabled, creating models and registering them in the Django Admin.

cd ~/tutorials
mkdir customadmin
cd customadmin
python3 -m venv env
source env/bin/activate
pip install --upgrade pip
pip install django django-environ whitenoise
django-admin startproject project .
pip freeze > requirements.txt
./manage.py startapp myapp

Modify settings for Django Admin

Ensure the admin app and mayapp are in INSTALLED_APPS in project/settings.py:

# settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'myapp',
]

For future admin template customizations create a directory templates in the root of the project.

mkdir templates

Make sure your TEMPLATES settings is configured to have the following context_processors:

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

The MIDDLEWARE setting must include: django.contrib.sessions.middleware.SessionMiddleware, django.contrib.auth.middleware.AuthenticationMiddleware, and django.contrib.messages.middleware.MessageMiddleware.

# settings.py
MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "whitenoise.middleware.WhiteNoiseMiddleware",
    "django.contrib.sessions.middleware.SessionMiddleware",
    "django.middleware.common.CommonMiddleware",
    "django.middleware.csrf.CsrfViewMiddleware",
    "django.contrib.auth.middleware.AuthenticationMiddleware",
    "django.contrib.messages.middleware.MessageMiddleware",
    "django.middleware.clickjacking.XFrameOptionsMiddleware",
]

The WhiteNoise is a library for serving static files in production. It is not required for the admin panel to work, but it is recommended for production use.

Configure URLs

Set up the admin URLs in your project:

# urls.py
from django.contrib import admin
from django.urls import path

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

You can further protect the admin panel by adding a custom URL:

from django.contrib import admin
from django.urls import path

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

Or using a value from environment variable to set a custom URL:

from django.contrib import admin
from django.urls import path
import environ

env = environ.Env()

urlpatterns = [
  path(env('ADMIN_URL', default='admin/'), admin.site.urls),
]

Create Superuser

Create an admin account to access the interface:

DJANGO_SUPERUSER_PASSWORD=somethingsupersecret123 python manage.py createsuperuser --username=admin --email=admin@example.com --noinput

This command allows unattended creation of the superuser.

Define Models

You need to define models that represent the data structure of your application and which you want to manage through the admin interface.

Since we'll need to showcase a lot of interesting cases for Django Admin features and customizations, let's create models structure similar to e-commerce like project to make it complex enough.

Here is an example of a simple model structure:

# models.py
from django.db import models
from django.utils import timezone

from myapp.tuples import ORDER_STATUSES_CHOICES, ORDER_STATUSES


class Category(models.Model):
    name = models.CharField(max_length=200)
    slug = models.SlugField(max_length=200, unique=True)
    is_active = models.BooleanField(default=True, db_index=True)

    class Meta:
        ordering = ('name',)
        verbose_name = 'category'
        verbose_name_plural = 'categories'

    def __str__(self):
        return self.name


class Product(models.Model):
    name = models.CharField(max_length=200)
    slug = models.SlugField(max_length=200, unique=True)
    category = models.ManyToManyField(Category, related_name='products')
    is_active = models.BooleanField(default=True, db_index=True)

    class Meta:
        ordering = ('name',)
        verbose_name = 'product'
        verbose_name_plural = 'products'

    def __str__(self):
        return self.name


class Customer(models.Model):
    first_name = models.CharField(max_length=200)
    last_name = models.CharField(max_length=200)
    phone = models.CharField(max_length=200, unique=True)

    class Meta:
        ordering = ('first_name',)
        verbose_name = 'customer'
        verbose_name_plural = 'customers'

    def __str__(self):
        return ' '.join([self.first_name, self.last_name])


class Order(models.Model):
    customer = models.ForeignKey(Customer, on_delete=models.CASCADE)
    created_dt = models.DateTimeField(auto_now_add=True)
    completed_dt = models.DateTimeField(null=True, blank=True)
    status = models.IntegerField(default=ORDER_STATUSES.new, choices=ORDER_STATUSES_CHOICES)

    class Meta:
        ordering = ('-created_dt',)
        verbose_name = 'order'
        verbose_name_plural = 'orders'

    def __str__(self):
        return str(self.id)

    def save(self, *args, **kwargs):
        if self.completed_dt:
            self.status = ORDER_STATUSES.complete
        if self.status == ORDER_STATUSES.complete and not self.completed_dt:
            self.completed_dt = timezone.now()
        super().save(*args, **kwargs)


class OrderItem(models.Model):
    order = models.ForeignKey(Order, on_delete=models.CASCADE)
    product = models.ForeignKey(Product, on_delete=models.CASCADE)
    quantity = models.PositiveIntegerField(default=1)

    class Meta:
        verbose_name = 'order item'
        verbose_name_plural = 'order items'
        unique_together = ('order', 'product')

    def __str__(self):
        return self.product.name
    ```
Make sure to specify the `__str__` method for each model to display the object's name in the admin interface.

You can also customize the model's metadata by setting the `Meta` class attributes.

For example: 

Meta class attributes `verbose_name` and `verbose_name_plural` are used to set the human-readable name of the model in the admin interface.

The Meta class attribute `ordering` attribute is used to specify the default ordering of objects in the admin interface.

The models `get_absolute_url` method can be used to return the canonical URL of the object.

Create a file `myapp/tuples.py` and put this in it.

from collections import namedtuple

ORDER_STATUSES = namedtuple('ORDER_STATUSES', 'new processing shipped complete canceled')._make(range(5))

ORDER_STATUSES_CHOICES = ( (ORDER_STATUSES.new, 'New'), (ORDER_STATUSES.processing, 'Processing'), (ORDER_STATUSES.shipped, 'Shipped'), (ORDER_STATUSES.complete, 'Complete'), (ORDER_STATUSES.canceled, 'Canceled'), )

Now create new migrations and apply them:

python manage.py makemigrations python manage.py migrate

Now let's run the development server and open our admin panel:

python manage.py runserver

The output should be like this:

Watching for file changes with StatReloader Performing system checks...

System check identified no issues (0 silenced). July 09, 2023 - 07:18:13 Django version 4.2.3, using settings 'project.settings' Starting development server at http://127.0.0.1:8000/ Quit the server with CONTROL-C.

Open your browser at this URL: (http://127.0.0.1:8000/notadmin123/)[http://127.0.0.1:8000/notadmin123/] because that's where our admin is located.



The credentials are `admin` / `somethingsupersecret123`

You will see this basic admin panel without our models in there.

![image](https://appliku-serverless-static.s3.eu-central-1.amazonaws.com/imgs/f716092aae3f0edd52517f87a09f980a.png)

Let's fix that.




## How to register Django model in the admin site

Let's see some examples how you can register models in Django admin.

### Basic Registration

The simplest way to register a model without creating ModelAdmin classes:

```python
# admin.py
from django.contrib import admin
from .models import Book, Author

admin.site.register(Book)
admin.site.register(Author)

ModelAdmin Registration

For customized admin interfaces:

# admin.py
from django.contrib import admin
from .models import Book

class BookAdmin(admin.ModelAdmin):
    list_display = ('title', 'author', 'publication_date')
    list_filter = ('author', 'publication_date')
    search_fields = ('title', 'author__name')
    date_hierarchy = 'publication_date'
    ordering = ('-publication_date',)

admin.site.register(Book, BookAdmin)

Decorator Registration

Alternative registration using decorators:

# admin.py
from django.contrib import admin
from .models import Book

@admin.register(Book)
class BookAdmin(admin.ModelAdmin):
    list_display = ('title', 'author', 'publication_date')

With the theory and examples out of the way, let's register our e-commerce like models.

Open the myapp/admin.py file and let's create very basic admin models.

from django.contrib import admin
from .models import Category, Product, Customer, Order


class CategoryAdmin(admin.ModelAdmin):
    list_display = ('name', 'slug', 'is_active', 'id',)


admin.site.register(Category, CategoryAdmin)


class ProductAdmin(admin.ModelAdmin):
    list_display = ('name', 'slug', 'is_active', 'id',)


admin.site.register(Product, ProductAdmin)


class CustomerAdmin(admin.ModelAdmin):
    list_display = ('first_name', 'last_name', 'phone', 'id',)


admin.site.register(Customer, CustomerAdmin)


class OrderAdmin(admin.ModelAdmin):
    list_display = ('customer', 'created_dt', 'completed_dt', 'status', 'id',)


admin.site.register(Order, OrderAdmin)

Refresh the admin page and you should see the "MYAPP" block appear and our 4 models listed.

image

In this step, we have registered models in the admin panel. Thanks to the list_display attribute, we tell the Django admin site what fields to display in the table for each model.

I will create a few records in every table so we can see how our admin modifications affect the admin site.