CRUD Operations In Django

This is my 7th article of the Django series. We'll talk about different methodologies for performing CRUD operations in Djnago

CRUD is the abbreviation used for four major operations performed on a datacenter. These operations usually include the following,

  1. Create
  2. Read
  3. Update
  4. Delete

Every programmer has to deal with these operations at some point. For example, you are developing a social networking website you need to provide the users a way to create their account and read their profile related data. What if they forgot their user name or password? So you surely need to let them update it. Last but not least they don’t want to be active on your site anymore so delete option is required as you surely wouldn’t want your database to be full of inactive users' records. Not only the social networking, but most probably you would need to implement these operations in all of your websites, to some point, at some level.

Let’s make our lives easy and talk about it in this article; the 7th in my Django series. I’m assuming that by far you have pretty good understanding of the Django application. If you have any confusion then refer to the previous articles of the series here.

If you’re wondering why we don't perform the CRUD operations using the admin panel; about which we have talked about earlier in the previous articles; that’s the hardcoded way and is only possible while building. Once you have deployed your site, users will not appear on your doorstep to request an account or update a password. You need to provide users a way to do all that for themselves.

Another way could be by using simple forms but it's a tedious and repetitive part of the job of a developer. You would have to implement a lot of validations; even prefilled fields and so on. Sounds like a nightmare to me not to mention it is an old-fashioned traditional way of doing things. Let’s see what Django has for us in the house.

CRUD using Function-Based Views

Django cares about its ease of use and so is there to help us with its simplified implementation for CRUD operations using Function Based views. These views are easy to read and pretty straightforward. A simple example will help us better understand it.

Let’s say we want to make our site a platform where a user can add their favorite movies. Nothing complex, we’ll just let user add, edit and delete the movies. Let’s get started.

Create a new project by executing the following command.

  1. django-admin startproject CRUD
  2.     
  3. # CRUD is the name of project  

 And execute the following command to start app name CRUD_FBVs.

  1. python manage.py startapp CRUD_FBVs  

Don’t forget to add your new app to the Installed app. Append CRUD/settings.py as follow,

  1. INSTALLED_APPS = [  
  2. 'django.contrib.admin',  
  3. 'django.contrib.auth',  
  4. 'django.contrib.contenttypes',  
  5. 'django.contrib.sessions',  
  6. 'django.contrib.messages',  
  7. 'django.contrib.staticfiles',  
  8. 'CRUD_FBVs',  
  9. ]  

 Let’s start by creating our movie model. Open CRUD_FBVs/models.py and add the following code to it.

  1. from django.db import models  
  2. from django.urls import reverse  
  3. from django.contrib.auth.models import User  
  4.   
  5.   
  6. class Movies(models.Model):  
  7.   
  8. '''''by specifying user as Foreign Key, we're using the builtin User model of django.'''  
  9.   
  10.    user = models.ForeignKey(User, on_delete=models.CASCADE)  
  11.    title = models.CharField(max_length=200)  
  12.    genre = models.CharField(max_length=200)  
  13.   
  14.    def __unicode__(self):  
  15.      return self.title  
  16.   
  17.    def get_absolute_url(self):  
  18.      return reverse('CRUD_FBVs:movies_edit', kwargs={'pk'self.pk})  

 We simply defined the model Movies by defining two fields, title and genre. We also defined user as the Foreign Key so that we’ll be able to use the built-in user model. By defining __unicode__() method, Django will call it when it needs to render an object in a context where a string representation is needed. get_absolute_url() method will tell Django how to calculate the canonical URL for an object. In other words, this method will return a string that can be used to refer to the object over HTTP.

Now apply the migrations as follows:

  1. python manage.py makemigrations  

 and

  1. python manage.py migrate  

 Let’s register our model to admin interface. Modify CRUD_FBVs/admin.py file as follow,

  1. from django.contrib import admin  
  2. from .models import Movies  
  3.    
  4.    
  5.  admin.site.register(Movies)  

 We also need to create a simple form to perform the CRUD operations. Create a new python file inside your app and name it forms.py. Append the following code to it.

  1. from django import forms  
  2. from .models import Movies  
  3.    
  4.    
  5. class MoviesForm(forms.ModelForm):  
  6.    
  7.      class Meta:  
  8.          model = Movies  
  9.          fields = ['title''genre']  

 Function Based views uses decorators to achieve the special functionality. Let’s see how that works. Edit views.py file as follow:

  1. from django.contrib.auth.decorators import login_required  
  2. from django.shortcuts import render, get_object_or_404, redirect  
  3. from .forms import MoviesForm  
  4. from CRUD_FBVs.models import Movies  
  5.   
  6.   
  7. @login_required  
  8. def movies_list(request):  
  9.      if request.user.is_superuser:  
  10.          movies = Movies.objects.all()  
  11.      else:  
  12.          movies = Movies.objects.filter(user=request.user)  
  13.      return render(request, 'movies_list.html', {  
  14.          'object_list': movies  
  15.      })  
  16.   
  17.   
  18. @login_required  
  19. def movies_create(request):  
  20.      form = MoviesForm(request.POST or None)  
  21.    
  22.      if form.is_valid():  
  23.          movies = form.save(commit=False)  
  24.          movies.user = request.user  
  25.          movies.save()  
  26.          return redirect('CRUD_FBVs:movies_list')  
  27.      return render(request, 'movies_form.html', {'form': form})  
  28.   
  29.   
  30. @login_required  
  31. def movies_update(request, pk):  
  32.      if request.user.is_superuser:  
  33.          movies = get_object_or_404(Movies, pk=pk)  
  34.      else:  
  35.          movies = get_object_or_404(Movies, pk=pk, user=request.user)  
  36.      form = MoviesForm(request.POST or None, instance=movies)  
  37.      if form.is_valid():  
  38.          form.save()  
  39.          return redirect('CRUD_FBVs:movies_list')  
  40.      return render(request, 'movies_form.html', {'form': form})  
  41.   
  42.   
  43. @login_required  
  44. def movies_delete(request, pk):  
  45.      if request.user.is_superuser:  
  46.          movies = get_object_or_404(Movies, pk=pk)  
  47.      else:  
  48.          movies = get_object_or_404(Movies, pk=pk, user=request.user)  
  49.      if request.method == 'POST':  
  50.          movies.delete()  
  51.          return redirect('CRUD_FBVs:movies_list')  
  52.      return render(request, 'confirm_delete.html', {'object': movies})  

We imported @login_required decorator which will limit the functionality of the CRUD operations to the logged in user. Remaining code is pretty simple and self-explanatory. You might have noticed the templates mentioned in the above code which we haven’t created yet. Let’s do that. Create templates folder in your app and create the HTML files as follow,

  1. # movie_list.html  

  2.  <h1>CRUD Function Based View Example</h1>  
  3.  <ul>  
  4.      {% for movies in object_list %}  
  5.      <li>{{ movies.title }}  {{ movies.genre }}  
  6.      <a href="{% url 'CRUD_FBVs:movies_edit' movies.id %}">edit</a>  
  7.      <a href="{% url 'CRUD_FBVs:movies_delete' movies.id %}">delete</a>  
  8.      </li>  
  9.      {% endfor %}  
  10.  </ul>  
  11.    
  12.  <a href="{% url 'CRUD_FBVs:movies_new' %}">Add</a>  

#movies_form.html

  1. # movies_form.html  
  2.    
  3. <h1>CRUD Function Based View Example</h1>  
  4.  <form method="post">  
  5.      {% csrf_token %}  
  6.      {{ form.as_p }}  
  7.      <input type="submit" value="Submit" />  
  8.  </form>  

 #movies_delete.html

  1. #confirm_delete.html  
  2.   
  3.  <h1>CRUD Function Based View Example</h1>  
  4.  <form method="post">  
  5.     {% csrf_token %}  
  6.      Are you sure you want to delete "{{ object }}" ?  
  7.      <input type="submit" value="Submit" />  
  8.  </form>  

 Now is the time to specify the URLs. Add the following code to urls.py:

  1. from django.conf.urls import url  
  2.  from django.contrib import admin  
  3.  from CRUD_FBVs import views  
  4.    
  5.  app_name = 'CRUD_FBVs'  
  6.    
  7.  urlpatterns = [  
  8.      url('admin/', admin.site.urls),  
  9.      url(r'^$', views.movies_list, name='movies_list'),  
  10.      url(r'^new$', views.movies_create, name='movies_new'),  
  11.      url(r'^edit/(?P<pk>\d+)$', views.movies_update, name='movies_edit'),  
  12.      url(r'^delete/(?P<pk>\d+)$', views.movies_delete, name='movies_delete'),  
  13.  ]  

That’s it. Now your app is ready to be up and running. Start your server by either executing the following command or directly by running it from your IDE.

  1. python manage.py runserver   

You can create superuser and use that to test the functionality or feel free to extend it.

Is there any con?

Function based views are easy to read and simple to implement but there’s also a downside to it. They are hard to customize or extend the functionality. Also they don’t allow the code reuse in true sense so repetitiveness still exists. To deal with all these issues, Class Based Views were introduced.

CRUD using Class Based Views

CRUD operations can be implemented in no time using CBVs, one of the biggest advantages being that if model evolves, changes would be automatically reflected in CBVs if done properly. Thus you can save tens of lines of code by implementing CBVs. They are easily extendable and also allow code reuse. Moreover, Django has built-in generic CBVs which make the life of a developer easy. Let’s implement them and see how the things turn out to be.

We are not going to just dump whatever we have done so far so keep calm. We just need to change our views and urls file as follow.

  1. from django.views.generic import ListView  
  2.  from django.views.generic.edit import CreateView, UpdateView, DeleteView  
  3.  from django.urls import reverse_lazy  
  4.    
  5.  from CRUD_CBVs.models import Movies  
  6.    
  7.    
  8.  class MoviesList(ListView):  
  9.      model = Movies  
  10.    
  11.    
  12.  class MoviesCreate(CreateView):  
  13.      model = Movies  
  14.      fields = ['title''genre']  
  15.      success_url = reverse_lazy('CRUD_CBVs: movies_list')  
  16.    
  17.    
  18.  class MoviesUpdate(UpdateView):  
  19.      model = Movies  
  20.      fields = ['title''genre']  
  21.      success_url = reverse_lazy('CRUD_CBVs: movies_list')  
  22.    
  23.    
  24.  class MoviesDelete(DeleteView):  
  25.      model = Movies  
  26.      success_url = reverse_lazy('CRUD_CBVs: movies_list')  

We imported the generic Create, Update and Delete Views from Django which are pretty much performing all the CRUD operations. Now let’s edit urls.py file as follows:

  1. from django.conf.urls import url  
  2.  from django.contrib import admin  
  3.  from CRUD_CBVs import views  
  4.    
  5.  app_name = 'CRUD_CBVs'  
  6.    
  7.  urlpatterns = [  
  8.      url('admin/', admin.site.urls),  
  9.      url('', views.MoviesList.as_view(), name='movies_list'),  
  10.      url(r'^new$', views.MoviesCreate.as_view(), name='movies_new'),  
  11.      url(r'^edit/(?P<pk>\d+)$', views.MoviesUpdate.as_view(), name='movies_edit'),  
  12.      url(r'^delete/(?P<pk>\d+)$', views.MoviesDelete.as_view(), name='movies_delete'),  
  13.  ]  

That’s all for the changes. Now your app can perform all the CRUD operations easily.

Is there any con?

You might have noticed that they are not as readable as Function Based views, mostly because there’s implicit code flow and they apply the Object Oriented technique. One more thing to mention, Class Based Views does not exist to replace Function Based Views. If you’re learning or maybe designing some tutorials for newbies, you might prefer Function Based Views due to their readability. And if you’re deploying your site or working professionally, you might prefer Class Based Views. They both are there for you, it’s up to you to choose which suits your situation.

To Readers

The complete repo of the above example exists on github at my profile. You can download it here.  Feel free to experiment with it. You can also contribute to the repo if you want. Happy coding you all. J