Django Admin : How to display change form with prefilled values

Product selected

April 2020

Sometimes when editing a model in the Django admin, we would like to have some fields prefilled with some values such as foreign key, date … This is the purpose of this tutorial

You can find the source code in this repository

For this tutorial we will use the following models

# *coding: utf-8*
from __future__ import unicode_literals
from django.db import models
from django.contrib.auth.models import User

class Shop(models.Model):
    user = models.ForeignKey(User, null=True, blank=True, on_delete=models.CASCADE)
    name = models.TextField()
    email = models.CharField(max_length=200)
    address = models.CharField(max_length=1024,null=True, blank=True)
    createdAt = models.DateTimeField(auto_now_add=True)
    updatedAt = models.DateTimeField(auto_now=True)
    def __str__(self):
        return u'%s' % (self.name)

class Product(models.Model):
    user = models.ForeignKey(User, null=True, blank=True, on_delete=models.CASCADE)
    refShop = models.ForeignKey(Shop, db_index=True,on_delete=models.CASCADE)
    title = models.TextField(max_length=65)
    price = models.FloatField(default=0)
    withEndDate = models.BooleanField(default=False)
    endDate = models.DateField(blank=True,null=True)
    description = models.CharField(max_length=65, default='', null=True, blank=True)
    createdAt = models.DateTimeField(auto_now_add=True)
    updatedAt = models.DateTimeField(auto_now=True)
    available = models.BooleanField(default=True)
    def __str__(self):
        return u'%s - %s' % (self.refShop,self.title)

Nothing tricky. We define a Shop which can have Product.

So now let’s imagine that we created an admin access for the person who is managing the Shop. When connecting to the admin, this person should be able to see only his products and when adding a new product, the refShop foreign key should be already set (plus he should be able to see only his own shop in the list).

Create django admin access

Let’s create a usera and userb to give them access to our Django admin

python manage.py createsuperuser

just reply to the questions. For this demo, i set the password to usera and userb (please notice you could also have created these users using the Django admin interface). Now we will create two shops in the admin, one for the usera and one for the userb

Shop with two users

And we create few products some for usera and others for userb

List of products

Ok now if we login to the admin with the usera or the userb, we will be able to see all shops and all products which is a big security issue, because we want the usera to see only his shop and his products and same for userb. It’s time to dive into code and solve these issues

How to restrict content in Django Admin based on the connected user ?

First thing we need to do (with our Django admin superuser) is to remove the superuser status to usera and userb and then to assign them permissions to be able to manage tables Shop and Product.

Change user permission in django admin

Now in our admin.py file, we will create our ShopAdmin class

class ShopAdmin(admin.ModelAdmin):
    fieldsets = [
        (None, {'fields': ['user','name','email','address']}),
    ]
    list_display = ('user','name', 'email',  )
    ordering = ('user',)

And register it

admin.site.register(Shop,ShopAdmin)

And now we will override the get_queryset method (still in our admin.py file) to implement our logic. If the connected user is a superuser, he should be able to see all content in the Shop table otherwise he should be able to see only his own content.

    def get_queryset(self, request):
        qs = super(ShopAdmin, self).get_queryset(request)
        if request.user.is_superuser:
            return qs
        else:
            return qs.filter(user=request.user)

And now if you connect to the admin with usera or userb, you should be able to see only your user content

User list restriction

Here is the full code of you ShopAdmin class

from django.contrib import admin
from backoffice.models import *


class ShopAdmin(admin.ModelAdmin):
    fieldsets = [
        (None, {'fields': ['user','name','email','address']}),
    ]
    list_display = ('user','name', 'email',  )
    ordering = ('user',)

    def get_queryset(self, request):
        qs = super(ShopAdmin, self).get_queryset(request)
        if request.user.is_superuser:
            return qs
        else:
            return qs.filter(user=request.user)



# Register your models here.
admin.site.register(Shop,ShopAdmin)

We will do exactly the same for our Product admin


class ProductAdmin(admin.ModelAdmin):
    fieldsets = [
        (None, {'fields': ['user','refShop','title','price','withEndDate','endDate','description']}),
    ]
    list_display = ('user','refShop','title')
    ordering = ('user','refShop',)

    def get_queryset(self, request):
        qs = super(ProductAdmin, self).get_queryset(request)
        if request.user.is_superuser:
            return qs
        else:
            return qs.filter(user=request.user)
Product list view

Ok now if we try to add a product, the change form which will appear will contains all the shops and all the products. We would like to have only one shop (the one belonging to the connected user) and only products which belongs to our connected user.

Add product with prefilled form in Django admin

Prefill Django admin change form with values

To modify the list of objects in our foreign keys User and refShop (in the selectbox of the admin) we need to override the method formfield_for_foreignkey

    def formfield_for_foreignkey(self, db_field, request, **kwargs):
        try:
            user = request.user
            if request.user.is_superuser:
                return super(ProductAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
            else:
                if db_field.name == "user":
                    kwargs["queryset"] = User.objects.filter(id=user.id)
                    return super(ProductAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
                if db_field.name == "refShop":
                    kwargs["queryset"] = Shop.objects.filter(user=user.id)
                    return super(ProductAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
        except Exception as e:
            print(e)

First we check if the connected user is a superuser and if so we do nothing.

Otherwise if the fields are user or refShop we override the queryset to get our filtered content. Now if we refresh our admin interface, we will only see our unique connected user and his shop. But the values are still not selected in the interface. We need to click on the select box to choose them. To display the form with prefilled values we need to override the get_form method

Add product with prefilled form in Django admin
    def get_form(self, request, obj=None, **kwargs):
        form = super(ProductAdmin, self).get_form(request, obj=obj, **kwargs)
        user = User.objects.get(id=request.user.id)
        form.base_fields['user'].initial = user
        if request.user.is_superuser:
            pass
        else:
            shops = Shop.objects.filter(user_id=user.id)
            form.base_fields['refShop'].initial = shops[0]

        return form

And now if we refresh the interface, values will be automatically selected

Product selected

And voila you have learned how to prefilled change form in the Django Admin interface.

Christophe Surbier