Django admin crash course - how to build a basic admin panel

Django admin crash course - how to build a basic admin panel

Jun 9, 2022
Madhura Kumar

Django is a hugely popular web framework for Python known for enabling rapid development and getting things done. Django admin is a web interface that provides a rich environment for adding content to your site.

In this article, we’ll first explore how to create a Django admin panel with basic functionality. Then, we’ll demonstrate some intermediate and advanced concepts — including how to customize your admin panels to manage product inventory and generate reports.

Requirements

Before you begin this tutorial, you'll want the following installed on your system:

  • Python 3.7 or greater
  • Python Pip, the package manager

Let's start off by creating the basic Python app.

Use case: Django admin project for Kitchen Shop

For the purposes of this article, we'll build a minimal, real-world Django admin project for a fictional company named “Kitchen Shop.” Kitchen Shop sells kitchen utensils such as forks, knives, and pans.

We'll assume Kitchen Shop has another web app for their e-commerce website, which we will not cover in the article. However, we'll use Django admin to provide data management and reporting for some of the entities related to Kitchen Shop's business: products, product categories, and inventories.

By the end of the article, Kitchen Shop will be able to leverage Django admin's tools for administrative work and save work hours website development.

Building a basic admin panel with Django

Python comes with the venv module, which supports the creation of lightweight “virtual environments” with their own site directories. Let’s create a new virtual environment in our project root directory:

python -m venv .venv

Now the virtual environment must be “activated” using a script in its directory. In Linux, use:

source .venv/bin/activate

Or, in Windows:

.venv/Scripts/activate

We can now run the following command to install Django:

python -m pip install django

Let’s use the django-admin command to create a Django project directory structure for the KitchenShop project:

django-admin startproject KitchenShop

To change to the /KitchenShop directory, type the following:

cd KitchenShop

By default, Django uses a lightweight, compact database called SQLite. When you create a Django admin project, your code comes with instructions for creating administrative users and groups in your database. These instructions are called migrations. As you add or change your models (adding a field, deleting a model, etc.), migrations propagate those changes into your database schema.

If you open the migrations folder at .venv/Lib/site-packages/django/contrib/auth/migrations, you’ll see some migrations ready to be applied:

Let's execute the following command line to migrate users and groups tables in your SQLite database:

python manage.py migrate

As a result, you’ll get the following output:

Django comes with an administrative panel out of the box, which saves you from providing a separate Admin page with an authentication mechanism. Since this feature is protected against unauthorized access, you must provide Django with a superuser to manage the admin panel.

Create a superuser

To create a superuser, go to the KitchenShop directory where the manage.py file is and run the following command with the respective parameters:

python manage.py createsuperuser

Username: admin
Email address: admin@admin.com
Password: 1234!@#$
Password (again): 1234!@#$

Run our application

To run our application, type the runserver command to start the development server on the internal IP at port 8000:

python manage.py runserver

Once the server is running, you can view the site by navigating to http://127.0.0.1:8000/ in your local web browser:

Navigate to the admin site in your local web browser (http://127.0.0.1:8000/admin/). Then log in to the site using the credentials for your superuser account:

http://127.0.0.1:8000/admin/

The top level of the Admin site displays all of your models, sorted by “Django application.” You can click the Users or Groups links from the Authentication and Authorization section to see their existing records.

Click the Users entity to see your superuser record:

Creating a custom Python app

With the project created, our next step is to create the app. Django is divided into several apps in the same project. This facilitates code reuse and helps with the organization of the project.

What’s the difference between a project and an app?

A project is a collection of apps needed for a website to run. A project can contain multiple apps and an app can be in multiple projects. An app is a web application with a specific set of features, such as a blog, an issue tracker, or an inventory. In this article, we’ll work with a Django app named “custom” to manage product inventory data.

Create your custom app

To create your custom app, make sure you’re in the same directory as the manage.py file and type this command:

python manage.py startapp custom

This will create a new subdirectory called custom containing the admin.py, models.py, tests.py, views.py files, and the migrations folder. This structure will hold our new custom app within our project.

We still need to enable our newly created custom app in our Django project configuration. We can do this by opening the settings.py file in the KitchenShop directory and adding our app name (custom) 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',
    'custom'
]

Remember when we accessed the Groups and Users lists in the Django admin panel? That was possible because Django registers those entities out of the box. Now imagine you want to create a new, simple model class named Category, representing the product category within your Kitchen Shop project.

To manage Category model objects on your Django admin panel, you first must declare the Category class. Let's open the KitchenShop/custom/models.py file and add the following code:

from django.db import models
class Category(models.Model):
    description = models.TextField()

In the above code, the Category class must inherit the models as a Django class.Model base class. We only needed to declare the description field because the id field is inherited via the models.Model class.

Once we’ve declared the Category model class, the next step is to register it so that Django can represent it in the admin interface. We do this in the KitchenShop/custom/admin.py file where models are registered:

from django.contrib import admin

from custom.models import Category
@admin.register(Category)
class ProductAdmin(admin.ModelAdmin):
    pass

Now let's open the KitchenShop/KitchenShop/urls.py file and change the site_header and site_title variables to customize the header and title of your site in the admin panel:

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

admin.site.site_header = "🍴 Kitchen Shop Administration 🥣"
admin.site.site_title = "Kitchen Shop Admin"

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

The last time we applied migrations, our Django project still didn’t have the Categories model — but it must be included in our application. Fortunately, instead of forcing you to define and register all the models from the start, Django migrations let us evolve our models in incremental steps.

The makemigrations command tells Django to calculate the differences between your current database models (stored in SQLite database) and the Python model classes living in your project at this moment.

Run the following makemigrations command:

python manage.py makemigrations

The image above shows how the makemigrations command instructed Django to create the 0001_initial.py file containing incremental patches inside the /migrations folder.

Now let's execute the following migrate command to migrate the category table in the SQLite database:

python manage.py migrate

After you’ve created and applied your first custom migration, the SQLite database will already have the Category schema, but not the category data. Let’s load a sample data file into the database.

Create a new file named sample_categories.json at the KitchenShop/custom/ folder with the following content, representing categories of products:

[
  { "pk": 1, "model": "custom.Category", "fields": { "description": "Bowls" } },
  {
    "pk": 2,
    "model": "custom.Category",
    "fields": { "description": "Graters" }
  },
  {
    "pk": 3,
    "model": "custom.Category",
    "fields": { "description": "Heaters" }
  },
  {
    "pk": 4,
    "model": "custom.Category",
    "fields": { "description": "Knives" }
  },
  {
    "pk": 5,
    "model": "custom.Category",
    "fields": { "description": "Mashers" }
  },
  {
    "pk": 6,
    "model": "custom.Category",
    "fields": { "description": "Mixers" }
  },
  {
    "pk": 7,
    "model": "custom.Category",
    "fields": { "description": "Openers" }
  },
  { "pk": 8, "model": "custom.Category", "fields": { "description": "Pans" } },
  { "pk": 9, "model": "custom.Category", "fields": { "description": "Pots" } },
  {
    "pk": 10,
    "model": "custom.Category",
    "fields": { "description": "Scoopers" }
  },
  {
    "pk": 11,
    "model": "custom.Category",
    "fields": { "description": "Slicers" }
  },
  {
    "pk": 12,
    "model": "custom.Category",
    "fields": { "description": "Spatulas" }
  },
  {
    "pk": 13,
    "model": "custom.Category",
    "fields": { "description": "Strainers" }
  }
]

Loading data is simple. Each time you run loaddata, the data will be read from the fixture (sample_categories.json) and re-loaded into the database:

python manage.py loaddata custom/sample_categories.json

Now execute the runserver command to run the application again:

python manage.py runserver

Then navigate to the admin site at http://127.0.0.1:8000/admin/ to see how Django applied your last changes:

http://127.0.0.1:8000/admin/

Since the Category model class has been properly registered in Django, our admin panel now automatically offers category management!

To view the list of categories that we import through the manage.py loaddata command, click Categorys below the Custom section:

But wait — you may notice a couple strange things about this panel.

  • First, by default Django pluralizes the model name by simply adding the letter "s" to the end of "Category." That's why this says Categorys instead of Categories.
  • Second, Django lists each category with a generic "Category object" text plus the object ID.

Let's go back to the KitchenShop/custom/models.py file and modify the model code:

from django.db import models
class Category(models.Model):
    description = models.TextField()
    class Meta:
        verbose_name_plural = "Categories"
    def __str__(self):
        return f"{self.description}"

What have we just done?

  • The verbose_name_plural metadata option lets you tell Django how your model will be displayed when referred to in the plural.
  • The __str__ method tells Django how to use the description to print each category in the Django admin panel. For example, instead of an obscure text such as “Category object (17),” you want Django to display categories as “Knives,” “Pots,” “Mixers,” etc.

After saving changes, the custom admin panel now looks like this:

Django also lets you visualize, create, edit, and delete each record in your database model. Each field can be edited in the same view as where the object is visualized:

Any changes you made are automatically persisted to your project’s underlying SQLite database.

Creating a product category report

Now let’s do something more interesting with the category data we're managing. Instead of seeing the Django admin panel as an end goal, we can approach it as a useful tool that’s integrated with other functionalities in our project.

Let's start by writing a simple report in Python to list the product categories of our Kitchen Shop.

To create a report, we first have to understand the view concept.

A view function is a Python function that takes a web request and returns a web response. In our case, the response will be the HTML of our report web page.

As a web framework, Django generates HTML dynamically using HTML templates. A template contains the static HTML and some specific syntax describing how dynamic content will be inserted. Django comes with a proprietary template system — Django Template Language (DTL) — that we'll use in this article.

Let’s create the /KitchenShop/custom/templates folder. Then create a base.html file inside the /KitchenShop/custom/templates/ folder with the following content:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>INVENTORY REPORT</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" />
</head>
<body>
    <div class="container">
        <main role="main" class="pb-3">
            {% block content %}{% endblock %}
        </main>
    </div>
</body>
</html>

Notice how the code above defines the {% block content %} and {% endblock %} special tags that must be filled by the inherited template.

Now, create a report.html file inside the /KitchenShop/custom/templates/ folder with the following content to override the base template:

{% extends "base.html" %}
{% block title %}Inventory Report{% endblock %}
{% block content %}
    <div class="row mt-4">
        <div class="col col-md-8 offset-md-2 alert alert-primary text-black text-center">
            <span class="h3">INVENTORY REPORT</span>
        </div>
    </div>
    {% for category in Categories %}
    <div class="row">
        <div class="col col-md-8 offset-md-2">
            <div class="row flex-row align-items-center border-bottom">
                <span class="col col-10"><em>{{ category.description }}</em></span>
            </div>
        </div>
    </div>
    {% endfor %}
{% endblock %}

Before we move on, let’s explain some of the special tags used in the report.html file:

  • {% extends “base.html” %} — This tag tells the template engine that this template “extends” the base.html template. It must be rendered within the context of the base.html template.
  • {% block title %} — This tag overrides the default page title.
  • {% block content %} — This tag overrides the main content block.
  • {% for category in Categories %} — This tag loops over each item in the Categories array to display a list of categories.

Outside of the template, we still have to define a view. By convention, views are defined in the views.py file in your project or application directory. Add the following code to the views.py file in the KitchenShop/KitchenShop/custom/ folder:

from django.http import HttpResponse
from django.template import loader
from custom.models import Category
def index(request):
    categories = Category.objects.order_by('id')
    template = loader.get_template('report.html')
    context = {
        'Categories': categories
    }
    return HttpResponse(template.render(context, request))

You can access your SQLite database (or any other database configured in your Django project) by using the QuerySet API. A QuerySet is a logical representation of a query expression that you can build using Python language to execute later against a database. QuerySets are powerful and can satisfy simple and complex scenarios. You use the order_by function to order the resulting queryset from the Category objects.

The get_template function loads the template file with the report.html name and returns a Template object. You don’t need to mention the base.html template because it loads automatically.

Next, you must enable your Django website to find your report when the user navigates to the http://127.0.0.1:8000/custom/report/ URL. Let’s modify the KitchenShop/custom/urls.py file in our custom app to map the /report URL path (if the KitchenShop/custom/urls.py file does not exist, you will need to create it):

from django.urls import path

from . import views

urlpatterns = [
    path('report/', views.index),
]

Then modify the KitchenShop/KitchenShop/urls.py file in our custom app to map the /custom url path:

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

admin.site.site_header = "🍴 Kitchen Shop Administration 🥣"
admin.site.site_title = "Kitchen Shop Admin"

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

Now run the app once more and navigate to the report URL (http://127.0.0.1:8000/custom/report/):

python manage.py runserver

Intermediate to advanced Django admin elements: Adding products and inventories to the model

Now let's start making our Django admin application more advanced. Instead of working with just one model class, we’ll introduce products and inventories related to the Category model. Here’s a diagram of the models we’re trying to build:

The relationship between categories and products fits the one-to-many pattern, while inventories and products fit the one-to-one pattern. This means that multiple products can be in the same category, while a product can have only one inventory.

To translate this new reality to our project, let's modify the file KitchenShop/custom/models.py and register the new Product and Inventory model classes:

from django.db import models
from django.core.validators import MaxValueValidator, MinValueValidator
class Category(models.Model):
    description = models.TextField()

    class Meta:
        verbose_name_plural = "Categories"
    def __str__(self):
        return f"{self.description}"

class Product(models.Model):
    description = models.TextField()
    category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='products')
    class Meta:
        verbose_name_plural = "Products"
    def __str__(self):
        return f"{self.description}"
  
class Inventory(models.Model):
    product = models.OneToOneField(
        Product,
        on_delete=models.CASCADE,
        primary_key=True,
    )
    quantity = models.PositiveSmallIntegerField(validators=[MinValueValidator(0), MaxValueValidator(1000000)])
    unit_price = models.DecimalField(max_digits=12, decimal_places=2, validators=[MinValueValidator(0.0), MaxValueValidator(1000000)])
    class Meta:
        verbose_name_plural = "Inventories"
    def __str__(self):
        product_description = Product.objects.filter(id=self.product.id)[0].description
        return f"{product_description}, qty: {self.quantity}, unit price: ${self.unit_price:2}"

Some aspects of the new code are worth noting:

  • The ForeignKey function provides a many-to-one relation. It defines a foreign key where the category_id attribute references the id attribute of the Category model class.
  • The OneToOneField function works similarly to a ForeignKey, except it always carries a unique constraint — the reverse relation always returns the object pointed to rather than a list.
  • The DecimalField and the PositiveSmallIntegerField declare the unit_price and the quantity fields. They also define their type, size, number of decimal places, and valid value ranges.

To register the new model classes, open the KitchenShop/custom/admin.py file and use the same method as we used for the Category class as an example:

from django.contrib import admin
from custom.models import Category, Product, Inventory
@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
    pass
@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
    pass
@admin.register(Inventory)
class InventoryAdmin(admin.ModelAdmin):
    pass

Now let's create two new files named sample_products.json and sample_inventories.json in KitchenShop/custom/. Give them the following content, representing product categories:

KitchenShop/custom/sample_products.json

[
    { "pk": 1,  "model": "custom.Product", "fields": { "category": 1,  "description": "Mixing bowl" } },
    { "pk": 2,  "model": "custom.Product", "fields": { "category": 2,  "description": "Cheese grater" } },
    { "pk": 3,  "model": "custom.Product", "fields": { "category": 3,  "description": "Microwave oven" } },
    { "pk": 4,  "model": "custom.Product", "fields": { "category": 4,  "description": "Bread knife" } },
    { "pk": 5,  "model": "custom.Product", "fields": { "category": 4,  "description": "Chef's knife" } },
    { "pk": 6,  "model": "custom.Product", "fields": { "category": 4,  "description": "Paring knife" } },
    { "pk": 7,  "model": "custom.Product", "fields": { "category": 4,  "description": "Pizza cutter" } },
    { "pk": 8,  "model": "custom.Product", "fields": { "category": 6,  "description": "Balloon whisk" } },
    { "pk": 9,  "model": "custom.Product", "fields": { "category": 6,  "description": "Blender" } },
    { "pk": 10, "model": "custom.Product", "fields": { "category": 7,  "description": "Bottle opener" } },
    { "pk": 11, "model": "custom.Product", "fields": { "category": 7,  "description": "Can opener" } },
    { "pk": 12, "model": "custom.Product", "fields": { "category": 8,  "description": "Saucepan" } },
    { "pk": 13, "model": "custom.Product", "fields": { "category": 8,  "description": "Frying pan" } },
    { "pk": 14, "model": "custom.Product", "fields": { "category": 9,  "description": "Soup pot" } },
    { "pk": 15, "model": "custom.Product", "fields": { "category": 10, "description": "Ladle" } },
    { "pk": 16, "model": "custom.Product", "fields": { "category": 11, "description": "Kitchen shear" } },
    { "pk": 17, "model": "custom.Product", "fields": { "category": 12, "description": "Silicone spatula" } },
    { "pk": 18, "model": "custom.Product", "fields": { "category": 13, "description": "Sieve" } },
    { "pk": 19, "model": "custom.Product", "fields": { "category": 13, "description": "Colander" } }
]

KitchenShop/custom/sample_inventories.json

[
    { "pk": 1, "model": "custom.Inventory", "fields": { "product": 1, "quantity": 28, "unit_price": 6.2 } },
    { "pk": 2, "model": "custom.Inventory", "fields": { "product": 2, "quantity": 23, "unit_price": 12.96 } },
    { "pk": 3, "model": "custom.Inventory", "fields": { "product": 3, "quantity": 4, "unit_price": 5.23 } },
    { "pk": 4, "model": "custom.Inventory", "fields": { "product": 4, "quantity": 4, "unit_price": 12.44 } },
    { "pk": 5, "model": "custom.Inventory", "fields": { "product": 5, "quantity": 9, "unit_price": 5.43 } },
    { "pk": 6, "model": "custom.Inventory", "fields": { "product": 6, "quantity": 23, "unit_price": 10.27 } },
    { "pk": 7, "model": "custom.Inventory", "fields": { "product": 7, "quantity": 10, "unit_price": 6.93 } },
    { "pk": 8, "model": "custom.Inventory", "fields": { "product": 8, "quantity": 17, "unit_price": 6.51 } },
    { "pk": 9, "model": "custom.Inventory", "fields": { "product": 9, "quantity": 19, "unit_price": 11.4 } },
    { "pk": 10, "model": "custom.Inventory", "fields": { "product": 10, "quantity": 27, "unit_price": 10.09 } },
    { "pk": 11, "model": "custom.Inventory", "fields": { "product": 11, "quantity": 9, "unit_price": 14.55 } },
    { "pk": 12, "model": "custom.Inventory", "fields": { "product": 12, "quantity": 6, "unit_price": 6.17 } },
    { "pk": 13, "model": "custom.Inventory", "fields": { "product": 13, "quantity": 13, "unit_price": 8.26 } },
    { "pk": 14, "model": "custom.Inventory", "fields": { "product": 14, "quantity": 27, "unit_price": 9.76 } },
    { "pk": 15, "model": "custom.Inventory", "fields": { "product": 15, "quantity": 12, "unit_price": 7.79 } },
    { "pk": 16, "model": "custom.Inventory", "fields": { "product": 16, "quantity": 21, "unit_price": 11.15 } },
    { "pk": 17, "model": "custom.Inventory", "fields": { "product": 17, "quantity": 10, "unit_price": 10.48 } },
    { "pk": 18, "model": "custom.Inventory", "fields": { "product": 18, "quantity": 24, "unit_price": 12.53 } },
    { "pk": 19, "model": "custom.Inventory", "fields": { "product": 19, "quantity": 3, "unit_price": 5.93 } }
]

Then run the makemigrations command to calculate the differences between your current database models and include the Product and Inventory models:

python manage.py makemigrations

Now let's execute the following command line to migrate the Product and Inventory tables in your SQLite database:

python manage.py migrate

Now that our database is prepared, we can run the loaddata command to read data from the fixture sample_products.json and sample_inventories.json and import them into the database:

python manage.py loaddata custom/sample_products.json

Installed 19 object(s) from 1 fixture(s)

python manage.py loaddata custom/sample_inventories.json

Installed 19 object(s) from 1 fixture(s)

Finally, let's rerun the Django project. Note how Django can manage the new Product and Inventory models:

python manage.py runserver

Click Products to see a list of your products:

When you click Inventories, you can see how each inventory is displayed using the product name, unit price, and quantity, as defined in the __str__ function in the Inventory model class:

When you click one of the inventories, Django will display the product’s description, quantity, and unit_price fields. Note how Django admin recognizes the Product as a field with a one-to-one relationship, referencing the Product model. Instead of a plain text box, Django displays a drop-down list where you can pick one of the products to create the relationship:

Finishing the inventory report

Let's get back to our report. The first time around, we took the report data directly from the Category table. This time, let's take it from the Inventory table.

First, modify the KitchenShop/KitchenShop/custom/views.py file with these changes:

from django.http import HttpResponse
from django.template import loader
from custom.models import Inventory
def index(request):
    inventories = Inventory.objects.select_related('product').order_by('product')
    template = loader.get_template('report.html')
    context = {
        'inventories': inventories
    }
    return HttpResponse(template.render(context, request))

Note how select_related returns a QuerySet that will follow the foreign-key relationship between inventories and products, selecting additional related-object data when it executes the query.

Now, let’s modify the report.html report template file so that the data for categories, products, and inventories is structured in this order:

{% extends "base.html" %}
{% block title %}Inventory Report{% endblock %}
{% block content %}
<div class="row mt-4">
    <div class="col col-md-8 offset-md-2 alert alert-primary text-black text-center">
        <span class="h3">INVENTORY REPORT</span>
    </div>
</div>
{% regroup inventories by product as product_list %}
{% regroup product_list by grouper.category as category_list %}
{% for category in category_list %}
<div class="row">
    <div class="col col-md-8 offset-md-2">
        <div class="row flex-row align-items-center border-bottom">
            <span class="col col-10"><em>{{ category.grouper.description }}</em></span>
                <div class="row">
                    <div class="col col-md-8 offset-md-2">
                        <div class="row bg-light text-white border-bottom border-dark">
                            <span class="col col-4 text-dark">Product</span>
                            <span class="col col-4 text-dark text-end">Unit Price ($)</span>
                            <span class="col col-4 text-dark text-end">Quantity</span>
                        </div>
                        {% for product in category.list %}
                        <div class="row flex-row align-items-center border-bottom">
                            {% for inventory in product.list %}
                            <span class="col col-4">{{ inventory.product.description }}</span>
                            <span class="col col-4 text-end">{{ inventory.unit_price }}</span>
                            <span class="col col-4 text-end">{{ inventory.quantity }}</span>
                            {% endfor %}
                        </div>
                        {% endfor %}
                    </div>
                </div>
            </div>
        </div>
    </div>
    {% endfor %}
{% endblock %}

Now run the Django project again to see your beautiful inventory report 😍

python manage.py runserver

http://127.0.0.1:8000/custom/report/

That's how you can build a simple admin panel using Django admin! While Django is a popular choice for building simple models, building out some of the more complex functionalities can be arduous. Django admin lacks sufficient permission management and is challenging to use when it comes to creating complicated fieldsets. If you're looking to spin up a simple admin site, Django admin could be a great fit for your use case. You can also check out other internal tooling platforms like Airplane and Active Admin for building admin dashboards.

Build admin panels quickly with Airplane

Airplane is a developer platform for quickly building internal tools where you can transform scripts and queries into applications in minutes. With Airplane you can build tasks and automate runbooks for simple operations as well as for complex, multi-step workflows. These can be things like restarting microservices, user management workflows, customer onboarding, admin operations, and on-call support. You can find more examples here.

Airplane also provides permissions, audit logs, approval flows, schedules, and more out of the box so all you have to focus on is writing the business logic for your tasks.

You can check out How to build an admin panel in under 10 minutes and other guides on the Airplane blog. Sign up for a free Airplane account to get started!

Share this article:

Subscribe to new blog posts from Airplane:

Leveraging feature flags to boost productivity

Leveraging feature flags to boost productivity

Jun 23, 2022
Madhura Kumar
In this article we'll discuss six ways to leverage feature flags to boost productivity and walk through how to build a lightweight feature flag in under 5 minutes. What
12 useful Python scripts for developers

12 useful Python scripts for developers

Jun 8, 2022
Madhura Kumar
This article will cover twelve Python scripts that were selected for their general utility, ease of use, and positive impact on your workload. They range in complexity from easy to intermediate and focus on text processing and file management.
go to home