Istanbul/Turkey

22- Django CRUD Operation

CRUD refers to the four basic operations a software application should be able to perform – Create, Read, Update, and Delete. For our MovieApp project, site admin would need such a functionality where he can easily see, update and delete movies. First I am adding a new menu item (Manage Movies) on the Navbar. This menu item is only available for superusers.

base.html

                  {% if user.is_authenticated and user.is_superuser%}
                  <li><a class="dropdown-item text-white" href="/{% url 'addmovie' %}">Add Movie</a></li>
                  <li><a class="dropdown-item text-white" href="/{% url 'addactor' %}">Add Actor</a></li>
                  <li><a class="dropdown-item text-white" href="/{% url 'managemovies' %}">Manage Movies</a></li>
                  {% endif %}
                  <li><hr class="dropdown-divider"></li>

 

Create a path in urls.py

path('managemovies', views.managemovies, name='managemovies'),

 

 Create a view to list movies in views.py

def managemovies(request):
    movies = Movie.objects.all()
    return render (request, 'managemovies.html', {'movies':movies})

 

Create a template named managemovies.html. 

I will copy a simple table from bootstrap.com and modify it to list my movie data in it like this.  Thanks to this,site admin will be able to publish and unpublish movies with a single click. 

{% extends 'base.html' %}

{% block title %}
    <title>Manage Movies</title>
{% endblock title %}

{% block content%}
<table class="table table-striped table-bordered">
  <thead>
    <tr>
      <th scope="col">#</th>
      <th scope="col">Title</th>
      <th scope="col">Description</th>
      <th scope="col">Gender</th>
      <th scope="col">Duration</th>
      <th scope="col">Resolution</th>
      <th scope="col">Photo</th>
      <th scope="col">Publish</th>
    </tr>
  </thead>
  <tbody>
  {%for m in movies%}
    <tr>
      <th scope="row">{{m.id}}</th>
      <td>{{m.title}}</td>
      <td>{{m.description}}</td>
      <td>{{m.gender}}</td>
      <td>{{m.duration}}</td>
      <td>{{m.resolution}}</td>
      <td>{{m.photo}}</td>
      <td>Take Online</td>
    </tr>
{%endfor block%}
  </tbody>
</table>
{% endblock content%}

 

That is how Manage Movies page looks for now.

 

Now I will modify my template and add some icons for edit, delete and status (Published-Unpublished). Because I use static link, I needed to add {%load static%} at the top of my template. 

{% extends 'base.html' %}
{%load static%}

{% block title %}
    <title>Manage Movies</title>
{% endblock title %}

{% block content%}
<table class="table table-striped table-bordered">
  <thead>
    <tr>
      <th scope="col">Title</th>
      <th scope="col">Description</th>
      <th scope="col">Edit</th>
      <th scope="col">Delete</th>
      <th scope="col">Status</th>
    </tr>
  </thead>
  <tbody>
  {%for m in movies%}
    <tr>
      <td>{{m.title}}</td>
      <td>{{m.description}}</td>
      <td align="center" valign="middle"> <img src="/{% static 'images/edit.png' %}"> </td>
      <td align="center" valign="middle"> <img src="/{% static 'images/delete.png' %}"> </td>
      <td align="center" valign="middle"> <img src="/{% static 'images/published.png' %}"> </td>
    </tr>
{%endfor block%}
  </tbody>
</table>
{% endblock content%}

 

 

Delete Functionality:

I need to add a url for the delete icon on managemovies.html

  {%for m in movies%}
    <tr>
      <td>{{m.title}}</td>
      <td>{{m.description}}</td>
      <td align="center" valign="middle"> <img src="/{% static 'images/edit.png' %}"> </td>
      <td align="center" valign="middle"> <a href="/{% url 'deletemovie' m.id %}"> <img src="/{% static 'images/delete.png' %}"></a> </td>
      <td align="center" valign="middle"> <img src="/{% static 'images/published.png' %}"> </td>
    </tr>
{%endfor block%}

 

Then create a path in urls.py

path('deletemovie/<id>', views.deletemovie, name='deletemovie'),

 

Create delete view in views.py

Because I will be working on filesystem, I need to import os

movie obj is created first, movie.photo is actually partial path that is stored in the database. To get full path, I add .path to that. So it returns the full path.

Because movie.delete() only deletes db record, I needed to use os.remove to delete actual files on the file system. 

import os

def deletemovie(request, id):
    movie = Movie.objects.get(pk=id)
    photoPath = movie.photo.path #get the full path of the photofile on filesystem
    moviePath = movie.moviefile.path #get the full path oft he moviefile on filesystem
    movie.delete() #delete db record
    filesToDelete = [photoPath, moviePath] #I can not use multiple os.remove() in a single view, therefore I created a list
    for file in filesToDelete:
        os.remove(file)
    messages.success(request, "Movie is Removed", extra_tags='green')
    return redirect('managemovies')

 

 

 

Edit Functionality:

Add url for edit icon on the template managemovies.html

  {%for m in movies%}
    <tr>
      <td>{{m.title}}</td>
      <td>{{m.description}}</td>
      <td align="center" valign="middle"> <a href="/{% url 'editmovie' m.id %}"> <img src="/{% static 'images/edit.png' %}"></a> </td>
      <td align="center" valign="middle"> <a href="/{% url 'deletemovie' m.id %}"> <img src="/{% static 'images/delete.png' %}"></a> </td>
      <td align="center" valign="middle"> <img src="/{% static 'images/published.png' %}"> </td>
    </tr>
{%endfor block%}

 

Add a path for Editing on urls.py

path('editmovie/<id>', views.editmovie, name='editmovie'),

 

I need a form in which I display existing data and post the new data after the site admin enters updated data. forms.py

class EditMovieForm(forms.ModelForm):
    #Gender Choices are defined here
    GENDER_CHOICES = (
        ('Drama', "Drama"),
        ('War', "War"),
        ('Romance', "Romance"),
        ('Mind Blowing', "Mind Blowing")
    )
    #Resolution Choices are defined here
    RESOLUTION_CHOICES =(('480p', "480p"),('720p', "720p"),('1080p', "1080p"))

    title = forms.CharField(widget=forms.TextInput(attrs={'class':'form-control'}))
    gender = forms.ChoiceField(choices=GENDER_CHOICES)
    #For description we used Textarea and specify its size
    description = forms.CharField(widget=forms.Textarea(attrs={'rows':4, 'cols':110,}))
    moviefile = forms.FileField()
    photo = forms.FileField()
    duration = forms.FloatField(widget=NumberInput(attrs={'class':'form-control'}))
    resolution = forms.ChoiceField(choices=RESOLUTION_CHOICES, widget=forms.RadioSelect())
    imdbID = forms.CharField(widget=forms.TextInput(attrs={'class':'form-control'}))
    class Meta:
        model=Movie
        exclude = ['numberOfViews', 'users', 'date_added', 'movieapp_score']
        fields = ['title', 'description', 'gender', 'moviefile', 'photo', 'duration', 'resolution','imdbID']

 

 

Create a view  named editmovie in views.py

Don't forget to import our new form at the top of views.py. If you want to delete the existing moviefile or photo file on the filesystem, you can use the same logic that we have done while creating the view for delete functionality above.

def editmovie(request, id):
    movie = Movie.objects.get(pk=id)
    if request.method == 'POST':
        form = EditMovieForm(request.POST, request.FILES, instance=movie)
        if form.is_valid():
            form.save()
            messages.success(request, "Movie is Updated", extra_tags='green')
            return redirect('managemovies')
    else:
        form = EditMovieForm(instance=movie)
    return render(request, 'editmovie.html', {'form':form})

 

 

We need a template for editmovie. Create a new template editmovie.html

{% extends 'base.html' %}
{% load crispy_forms_tags %}

{% block title %}
<title>Edit Movie</title>
{% endblock title%}

{% block content %}
    {% if user.is_authenticated and user.is_superuser%}
    <p class="text-warning">Welcome {{ user.username }} </p>
    <div class="row justify-content-center">
        <div class="col-10">
            <div class="card">
                <div class="card-body">
				    <div class="col-md-10 offset-md-2">
                        <h2 class="text-center">Edit Movies</h2>
                        <!--enctype="multipart/form-data" is needed here, 
                        because we have multiple filefields in a single form.-->
                        <form method="POST" enctype="multipart/form-data" >
                        {% csrf_token %}
                        {% if form.errors %}
                            <div class="alert alert-danger alert-dismissible fade show" role="alert">
                                <strong>Your Form has errors</strong>
                                <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
                            </div>
                        {% endif %}
                        
                        <!--crispy fields stars First Row-->
                        <div class="row g-4">
                            <div class="col-sm-4">
                            {{ form.title | as_crispy_field }}

                            </div>
                            <div class="col-sm">
                            {{ form.gender | as_crispy_field }}
                            </div>
                            <div class="col-sm">
                            {{ form.duration | as_crispy_field }}
                            </div>
                        </div>
                        <!--Second Row-->
                        <div class="row g-2">
                            <div class="col-sm">
                            {{ form.moviefile | as_crispy_field }}
                            </div>
                            <div class="col-sm">
                            {{ form.photo | as_crispy_field }}
                            </div>
                        </div>
                        <!--Third Row-->
                        <div class="row g-2">
                            <div class="col-sm">
                            {{ form.resolution | as_crispy_field }}
                            </div>
                            <div class="col-sm">
                            {{ form.imdbID | as_crispy_field }}
                            </div>
                        </div>
                        <!--Fourth Row-->
                        <div class="row g-1">
                            <div class="col-sm">
                            {{ form.description | as_crispy_field }}
                            </div>
                        </div>
                        <!--crispy fields ends-->

                        <input type="submit" value="Update" class="btn btn-secondary mb-2">
                        </form>
					</div>                  
                </div>
            </div>
        </div>
    </div>
    {% else %}
    <div class="position-absolute top-50 start-50 translate-middle">
    <h1>You do not have access to this page!<h1>
    </div>
    
    {% endif%}
    {% endblock content%}

 

After the site admin clicks edit button on managemovies page, he gets a form in which he can see the existing data and update it.

 

 

 

Publish/Unpublish Functionality:

Site Admin might need a functionality to publish or unpublish movies without deleting them. So, they will stay on database but regular users will not be able to see them on moviesapp page. For this, I need to add a new boolean field on Moviemodel (status). models.py

class Movie(models.Model):
    title= models.CharField(max_length=100)
    gender= models.CharField(max_length=20)
    description= models.CharField(max_length=255, blank=True, null=True)
    moviefile= models.FileField(upload_to='movie', blank=True, null=True)
    photo= models.FileField(upload_to='photo',blank=True, null=True)
    publishDate= models.DateField(blank=True, null=True)
    imdbID= models.CharField(max_length=10, blank=True, null=True)
    movieapp_score= models.FloatField(blank=True, null=True)
    date_added= models.DateField(auto_now=True)
    numberOfViews= models.IntegerField(default=0)
    duration= models.FloatField(blank=True, null=True)
    resolution= models.CharField(max_length=5)
    status = models.BooleanField(default=False)
    users = models.ManyToManyField(MyCustomUser, through='UserComment')
    usermovielist = models.ManyToManyField(MyCustomUser, related_name="usermovielist")
    
    def __str__(self):
        return self.title

 

Run migration commands 

python3 manage.py makemigrations
python3 manage.py migrate

 

 

Modify the template. I am going to use if-elsestatement. If status is True, it will display published icon if not it will display unpublished icon on the template. managemovies.html

 

  <tbody>

  {%for m in movies%}
    <tr>
      <td>{{m.title}}</td>
      <td>{{m.description}}</td>
      <td align="center" valign="middle"> <a href="/{% url 'editmovie' m.id %}"> <img src="/{% static 'images/edit.png' %}"></a> </td>
      <td align="center" valign="middle"> <a href="/{% url 'deletemovie' m.id %}"> <img src="/{% static 'images/delete.png' %}"></a> </td>
    {%if m.status %}
      <td align="center" valign="middle"> <a href="/{% url 'unpublish' m.id %}"> <img src="/{% static 'images/published.png' %}"></a> </td>
    {% else %}
      <td align="center" valign="middle"> <a href="/{% url 'publish' m.id %}"> <img src="/{% static 'images/unpublished.png' %}"></a>  </td>
    {% endif %}
    </tr>
  {% endfor %}

  </tbody>

 

Create 2 paths in urls.py

    path('publish/<id>', views.publish, name='publish'),
    path('unpublish/<id>', views.unpublish, name='unpublish'),

 

 

Create 2 view functions

views.py

def publish(request, id):
    movie = Movie.objects.get(pk=id)
    movie.status = True
    movie.save()
    return redirect('managemovies')

def unpublish(request, id):
    movie = Movie.objects.get(pk=id)
    movie.status = False
    movie.save()
    return redirect('managemovies')

 

With a single click, site admin can publish and unpublish the movies but one last thing...

 

 

We aneed to modify moviesapp.html in order to allow users to see only the movies on which the status value is True.

            {% for m in movies%}
                {% if m.status%}
                <div class="col-2">
                            
                            <a href="/{% url 'watchmovie' m.id %}">
                            <p class="text-light">{{m.title}}</p>
                            <img src="/{{ MEDIA_URL }}{{m.photo}}"  width="200" height="300" class="rounded">
                            </a>
                            <a href="/{% url 'addtolist' m.id %}" class="text-white">Add To List</a>
                            <hr>
                </div>
                {% endif %}
            {%endfor block%} 

 

 

  • Hits: 318