Django CRUD (Create, Retrieve, Update, Delete)

UPDATE 1: The main example use Class Based Views, I also added Function Based Views version of the same functionality.

UPDATE 2: I added a new example application with user access, user must login and each user has his own list.
Sample Code: You can download a sample application from

UPDATE 3: You might be interested in Django CRUD Parent/Child Edition

In this post I briefly cover the step needed to create a CRUD app in Django, the steps we will need are:

  • Create an App
  • Create the Model
  • Create the Admin Interface (optional)
  • Create the View
  • Define the URLs (i.e. URL to View mapping)
  • Create the Templates

Create new App

From the Django project directory we will create the new app called “servers” to store our servers information:

 ./ startapp servers

We will also need to register the new app in our Django project, add the app ‘server’ to the INSTALLED_APPS in your django_proj_name/


Create the Model

The model file would be servers/

from django.db import models
from django.core.urlresolvers import reverse

class Server(models.Model):
    name = models.CharField(max_length=200)
    ip = models.GenericIPAddressField()
    order = models.IntegerField()

    def __unicode__(self):

    def get_absolute_url(self):
        return reverse('server_edit', kwargs={'pk':})

After defining the model you need to provision it to the database:

./ makemigrations
./ migrate

To create the table for the new model.

Admin Interface (optional)

Django will give you free CRUD interface from the admin site, just define the file servers/ as:

from django.contrib import admin
from servers.models import Server

The Views

We will use Django Class-based views to crete our app pages, the file servers/ would look like:

from django.http import HttpResponse
from django.views.generic import TemplateView,ListView
from django.views.generic.edit import CreateView, UpdateView, DeleteView
from django.core.urlresolvers import reverse_lazy

from servers.models import Server

class ServerList(ListView):
    model = Server

class ServerCreate(CreateView):
    model = Server
    success_url = reverse_lazy('server_list')
    fields = ['name', 'ip', 'order']

class ServerUpdate(UpdateView):
    model = Server
    success_url = reverse_lazy('server_list')
    fields = ['name', 'ip', 'order']

class ServerDelete(DeleteView):
    model = Server
    success_url = reverse_lazy('server_list')

Define the URLs

We need to define app URLs in the file __servers/

from django.conf.urls import patterns, url

from servers import views

urlpatterns = patterns('',
  url(r'^$', views.ServerList.as_view(), name='server_list'),
  url(r'^new$', views.ServerCreate.as_view(), name='server_new'),
  url(r'^edit/(?P<pk>\d+)$', views.ServerUpdate.as_view(), name='server_edit'),
  url(r'^delete/(?P<pk>\d+)$', views.ServerDelete.as_view(), name='server_delete'),

This URLs wouldn’t work unless you include the servers/ in the main URLs file django_proj_name/

urlpatterns = patterns('',
  url(r'^servers/', include('servers.urls')),


templates/servers/server_form.html This file will be used by Edit and Update views:

<form method="post">{% csrf_token %}
    {{ form.as_p }}
    <input type="submit" value="Submit" />

templates/servers/server_list.html This file will be used by the ListView:

    {% for server in object_list %}
    <li>{{ }}  :  
    <a href="{% url "server_edit" %}">{{ server.ip }}</a>
    <a href="{% url "server_delete" %}">delete</a>
    {% endfor %}

<a href="{% url "server_new" %}">New</a>

templates/servers/server_confirm_delete.html This file will be used by DeleteView:

<form method="post">{% csrf_token %}
    Are you sure you want to delete "{{ object }}" ?
    <input type="submit" value="Submit" />

Function Based View Version

The example above uses Class Based Views (or CBV for short) to implement the views, what I will cover now is how to implement the same functionality but with Function Based Views i.e. using functions instead of classes, we will be using the same templates:


from django.shortcuts import render, redirect, get_object_or_404
from django.forms import ModelForm

from servers.models import Server

class ServerForm(ModelForm):
    class Meta:
        model = Server
        fields = ['name', 'ip', 'order']

def server_list(request, template_name='servers/server_list.html'):
    servers = Server.objects.all()
    data = {}
    data['object_list'] = servers
    return render(request, template_name, data)

def server_create(request, template_name='servers/server_form.html'):
    form = ServerForm(request.POST or None)
    if form.is_valid():
        return redirect('server_list')
    return render(request, template_name, {'form':form})

def server_update(request, pk, template_name='servers/server_form.html'):
    server = get_object_or_404(Server, pk=pk)
    form = ServerForm(request.POST or None, instance=server)
    if form.is_valid():
        return redirect('server_list')
    return render(request, template_name, {'form':form})

def server_delete(request, pk, template_name='servers/server_confirm_delete.html'):
    server = get_object_or_404(Server, pk=pk)    
    if request.method=='POST':
        return redirect('server_list')
    return render(request, template_name, {'object':server})


from django.conf.urls import patterns, url

from servers import views

urlpatterns = patterns('',
  url(r'^$', views.server_list, name='server_list'),
  url(r'^new$', views.server_create, name='server_new'),
  url(r'^edit/(?P<pk>\d+)$', views.server_update, name='server_edit'),
  url(r'^delete/(?P<pk>\d+)$', views.server_delete, name='server_delete'),

p.s. personally I prefer using FBV over CBV, the article Django’s CBVs were a mistake explains why.

64 thoughts on “Django CRUD (Create, Retrieve, Update, Delete)”

  1. Thanks for tutorial! I’ve a question regarding ClassBased views. How django understands which template to use. For example in case of Update, how it knows that server_form.html located in templates/servers/server_form.html is the right template…

  2. @Gio Django will use the value of “template_name” attribute as a template file, and the default value would depend on the class you would inherit from, e.g.:

    ListView => {app_name}/{model_name}_list.htm
    CreateView => {app_name}/{model_name}_form.htm
    UpdateView => {app_name}/{model_name}_form.htm
    DeleteView => {app_name}/{model_name}_confirm_delete.htm

    You can also override the suffix only using the attribute “template_name_suffix”, for example if you would like to have different template for “CreateView” and “UpdateView” you would change “template_name_suffix” from the default “_form” to “_create_form” and “_update_form” respectively.

  3. Thanks @Rayed. I was getting TemplateNotFound on …/templates/auth/user_form.html and it is obvious, cause I did not had that auth directory. If I create auth an put inside user_form.html everything woks perfectly, but my template directory is another …/templates/profile/…, how can I indicate my desired one?

  4. @Rayed To manage users I will use Django admin, but I’m trying to allow registered users edit their profiles, which I already did. But I have one issue… I’ve extended user registration with extra fields, it means that extended fields are located in a separate table ind DB. Now when I’m retrieving records from User model only default fields are selected. Now thinking how I can take other fields also.. for example: first_name, last_name, username, email, address.
    Why you asked? Am I doing something wrong? I’m new in Django.

  5. @Gio Not sure how you can do it using CBV but with FBV you would use a view similar to “server_update” with two forms instead of one.

  6. Nicely captured detail on simple but fundamental model actions. I particularly appreciate the parallel implementations of the class based views and function based views. I personally also prefer the function based views. Sure, it’s more verbose but I think it makes for clear, intuitive code.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.