DEV Community

ahmed elboshi
ahmed elboshi

Posted on • Updated on

Creating Custom Admin Actions with Extra Data in Django: A Step-by-Step Guide

Welcome to our tutorial on creating custom actions with extra data in Django! In this tutorial, we will learn how to create custom actions that allow us to perform specific tasks on a queryset of objects in the Django admin dashboard. We will also learn how to capture extra data from the user through a form and use it in our custom action.

We will be using a simple example of a shop app with a Product model. Our custom action will be a "Decrease Price" action that allows us to decrease the price of a product by a percentage. We will create a form that captures the discount percentage from the user and use it to update the price of the selected products.

Here is a summary of what we will cover in this tutorial:

  1. Setting up the Django project and app
  2. Setting up the Product model and adding it to the Django admin
  3. Setting up the Intermediate Form and view for capturing the discount percentage
  4. Create View Template
  5. Add URLS
  6. Defining the custom action and action function

Let's get started!

Setting Up the Project

First, we will need to set up a Django project and app. If you already have a Django project and app set up, you can skip this step.

Create a new Django project by running the following command:

django-admin startproject myproject
Enter fullscreen mode Exit fullscreen mode

Navigate to the project directory:

cd myproject
Enter fullscreen mode Exit fullscreen mode

Create a new Django app by running the following command:

python3 manage.py startapp shop
Enter fullscreen mode Exit fullscreen mode

Open the myproject/settings.py file and add shop to the INSTALLED_APPS list.

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'shop.apps.ShopConfig',
]
Enter fullscreen mode Exit fullscreen mode

Setting Up the Product Model

Next, we will need to set up a model for our app. For this tutorial, we will create a simple Product model with a name and price field.

Open the shop/models.py file and add the following code:

from django.db import models

class Product(models.Model):
    name = models.CharField(max_length=200)
    price = models.DecimalField(max_digits=10, decimal_places=2)

    def __str__(self):
        return self.name

Enter fullscreen mode Exit fullscreen mode

Run the following command to create a migration file for the model:

python3 manage.py makemigrations
Enter fullscreen mode Exit fullscreen mode

Run the following command to apply the migration:

python3 manage.py migrate
Enter fullscreen mode Exit fullscreen mode

Adding the Model to the Admin Dashboard
Now that we have our model set up, we can add it to the Django admin dashboard.

Open the shop/admin.py file and add the following code:

from django.contrib import admin
from .models import Product

admin.site.register(Product)
Enter fullscreen mode Exit fullscreen mode

Run the following command to create a superuser:

python3 manage.py createsuperuser
Enter fullscreen mode Exit fullscreen mode

Run the development server by running the following command:

python3 manage.py runserver
Enter fullscreen mode Exit fullscreen mode

Navigate to http://127.0.0.1:8000/admin in your web browser and log in with the superuser credentials you created. You should now see the Product model in the dashboard.

django dashboard

Add Some Products using admin dashboard

products list admin view

Setting up the Intermediate Form

The first step is to create a form for the intermediate page that will capture the data that we need to perform the action. In this case, we will create a form that allows the user to enter a percentage value that will be used to update the price of the products.

create new file in shop app and call it forms.py shop/forms.py
Here is the code for the form:

# shop/forms.py
from django import forms

class ProductPriceUpdateForm(forms.Form):
    percentage = forms.DecimalField(label='Percentage', min_value=0, max_value=100)

Enter fullscreen mode Exit fullscreen mode

This form has a single field, percentage, which is a float field that allows the user to enter a decimal value.

Create view to handle custom action form

*Note don't forget to check is staff user and have permission to change product model *

Next, we need to create the view that will display the form and handle the form submission. Here is the code for the view:

# shop/views.py
from django.contrib import messages
from django.shortcuts import redirect
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from django.views.generic import FormView
from django.contrib.auth.mixins import PermissionRequiredMixin
from .forms import ProductPriceUpdateForm
from .models import Product


class ProductPriceUpdateView(PermissionRequiredMixin,FormView):
    """
    View for updating product prices in the shop.
    Only users with the `shop.change_product` permission can access this view.
    """
    form_class = ProductPriceUpdateForm
    template_name = 'admin/shop/price_update.html'
    permission_required = ['shop.change_product']

    def form_valid(self, form):
        """
        Update the prices of all products in the shop by the percentage specified in the form.
        """
        percentage = form.cleaned_data['percentage']
        products = Product.objects.all()
        for product in products:
            product.price = product.price - (product.price * percentage / 100)
            product.save()
        messages.success(self.request, _('Prices updated successfully'))
        return redirect('admin:shop_product_changelist')

    def form_invalid(self, form):
        """
        Display an error message if the form is invalid.
        """
        messages.error(self.request, _('Error updating prices'))
        return super().form_invalid(form)

    def get_context_data(self, **kwargs):
        """
        Add the title and form URL to the context data.
        """
        context = super().get_context_data(**kwargs)
        context['title'] = _('Update Prices')
        context['form_url'] = reverse('shop:shop_product_price_update')
        context['products'] = Product.objects.all()
        return context

Enter fullscreen mode Exit fullscreen mode

This is a Django view for updating the prices of products in the database. It is a FormView that uses the ProductPriceUpdateForm form class to handle user input.

This view requires a specific permission to be accessed, specified by the permission_required attribute.

The form_valid method is called when the form is valid and successfully submitted. It retrieves all products in the database and updates their prices based on the percentage specified in the form. If the update is successful, it displays a success message and redirects the user to the list view of products in the admin panel.

The form_invalid method is called when the form is invalid or there is an error in processing the form. It displays an error message and calls the parent form_invalid method.

The get_context_data method is used to add additional context data to the template context. It adds the title of the page and the URL of the form to the context.

This view requires the shop.change_product permission to access.

Create View Template

This template extends the admin/base_site.html template, which provides the basic layout and styling for the Django Admin interface.

To display the form fields, we use the {/{ form.percentage }/} template tag, which will render the percentage field from the ProductPriceUpdateForm form.

To create the template, follow these steps:

  1. Create a new directory named templates at the root level of your Django app shop/templates .
  2. Inside the templates directory, create a new directory named admin.
  3. Inside the admin directory, create a new directory named shop.
  4. Inside the shop directory, create a new file named price_update.html.
  5. Copy and paste the template code into the price_update.html file.
  6. That's it! The template should now be set up and ready to use.

Note:
you can place template in any template folder just navigate to file in view

template code :

{% extends "admin/base_site.html" %}
{% load i18n %}

{% block title %}{{ title }}{% endblock %}

{% block content %}
    <form method="post" novalidate>
        {% csrf_token %}
        <div class="form-group">
            {{ form.percentage }}
        </div>
        <button type="submit" class="btn btn-primary">{% trans 'Update Prices' %}</button>
    </form>
{% endblock %}

Enter fullscreen mode Exit fullscreen mode

Add URLS

First create file inside shop app directory call it urls.py

#shop/urls.py
from . import views
from django.urls import path

app_name='shop'
urlpatterns = [
    path("update-products-price/", views.ProductPriceUpdateView.as_view(), name="shop_product_price_update")
]

Enter fullscreen mode Exit fullscreen mode

Then update urls.py file in myproject directory

#myproject/urls.py
from django.contrib import admin
from django.urls import path,include

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

Enter fullscreen mode Exit fullscreen mode

now run our server

python3 manage.py runserver
Enter fullscreen mode Exit fullscreen mode

then navigate to
http://127.0.0.1:8000/admin/update-products-price/

update price view

Wait a minute this view will update all products prices but it must just update products selected by action is that right?

Yes, you are correct. Currently, the view is updating the price of all products in the database. To update only the selected products, we need to create action to pass the selected Product objects to the intermediate view and use those Products to update the prices.

Here is how we can do that:

Create Custom Admin Action

First, we need to create custom admin action to pass the selected Product objects to the intermediate view. Here is the code for the admin action:

# shop/admin.py
from django.contrib import admin
from django.http import HttpResponseRedirect
from django.urls import reverse

from .models import Product

def update_prices(modeladmin, request, queryset):
    # Get the list of selected product IDs
    product_ids = queryset.values_list('id', flat=True)
    # Redirect to the intermediate view with the selected product IDs
    return HttpResponseRedirect(reverse('shop:shop_product_price_update') + '?ids=' + ','.join(str(p) for p in product_ids))

update_prices.short_description = "Update prices"

@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
    list_display = ('name', 'price')
    actions = [update_prices]
Enter fullscreen mode Exit fullscreen mode

In the update_prices action, we first retrieve the list of IDs for the selected Product objects. Then, we redirect the user to the intermediate view with the list of IDs as a query parameter.

Next, we need to modify the intermediate view to retrieve the list of IDs and use them to update the prices of the selected products so let's update form_valid and get_context_data in intermediate view.

Here is the updated code for the intermediate view:

# shop/views.py
from django.contrib import messages
from django.shortcuts import redirect
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from django.views.generic import FormView
from django.contrib.auth.mixins import PermissionRequiredMixin
from .forms import ProductPriceUpdateForm
from .models import Product


class ProductPriceUpdateView(PermissionRequiredMixin,FormView):
    """
    View for updating product prices in the shop.
    Only users with the `shop.change_product` permission can access this view.
    """
    form_class = ProductPriceUpdateForm
    template_name = 'admin/shop/price_update.html'
    permission_required = ['shop.change_product']

    def form_valid(self, form):
        """
        Update the prices of all products in the shop by the percentage specified in the form.
        """
        percentage = form.cleaned_data['percentage']
        # get product ids from url
        product_ids = self.request.GET.get('ids').split(',')

        # Get the selected products
        products = Product.objects.filter(id__in=product_ids)
        for product in products:
            product.price = product.price - (product.price * percentage / 100)
            product.save()
        messages.success(self.request, _('Prices updated successfully'))
        return redirect('admin:shop_product_changelist')

    def form_invalid(self, form):
        """
        Display an error message if the form is invalid.
        """
        messages.error(self.request, _('Error updating prices'))
        return super().form_invalid(form)

    def get_context_data(self, **kwargs):
        """
        Add the title and form URL to the context data.
        """
        context = super().get_context_data(**kwargs)
        context['title'] = _('Update Prices')
        context['form_url'] = reverse('shop:shop_product_price_update')

        # get product ids from url
        product_ids = self.request.GET.get('ids').split(',')

        context['products'] = Product.objects.filter(id__in=product_ids)
        return context

Enter fullscreen mode Exit fullscreen mode

In the form_valid method, we first retrieve the list of selected product IDs from the query parameters using self.request.GET.get('ids').split(',') . Then, we use the Product.objects.filter() method to get the selected Product objects using the id__in filter. Finally, we loop through the selected products and update their prices using the percentage value entered by the user.

So now try to use the action and test if it will just update selected products or not.

Top comments (0)