Istanbul/Turkey

13-Creating a Custom Form with Crispy Forms

So far, We have used Django Admin Panel to add new movies records. In this post, I will create my own form which can be accessed only by the super users in order to add new movie records. While we are doing this,we will also learn about Django-crispy-forms. Django-crispy-forms is a Python package that lets you easily build, customize and reuse forms using your favorite CSS framework without having to take care of annoying details form template code. 

Make sure you activate the virtual environment before installing crispy forms. Install django-crispy-forms on terminal by using the following commands:

pip3 install django-crispy-forms
pip3 install crispy-bootstrap5

 

In settings.py,

Add crispy_forms and crispy_bootstrap5 in INSTALLED_APPS.

Then add CRISPY_TEMPLATE_PACK = 'bootstrap5' 

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'moviesapp',
    'crispy_forms',
    'crispy_bootstrap5',
]

CRISPY_TEMPLATE_PACK = 'bootstrap5'

 

Creating the Form:

First I need to create my custom form for adding movies. Open forms.py and import the model Movie.

forms.py

from .models import MyCustomUser, Movie

class AddMovieForm(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()
    publishDate = forms.DateField(widget=MyDateInput(attrs={'class':'form-control'}))
    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
        #the excluded fields will not be filled with the form
        exclude = ['numberOfViews', 'users', 'date_added', 'movieapp_score']
        fields = ('title', 'gender', 'description', 'moviefile', 'photo', 'publishDate', 'duration', 'resolution', 'imdbID')

 

Create the view:

views.py

Import AddMovieForm in views.py

from moviesapp.forms import MyCustomUserChangeForm, MyCustomUserCreationForm, MyCustomUserPasswordChangeForm, AddMovieForm

def addmovie(request):
    if request.method=='POST':
        form = AddMovieForm(request.POST, request.FILES)
        if form.is_valid():
            form.save()
            messages.success(request, "Movie Added", extra_tags='green')
            return redirect('addmovie')
    else:
        form = AddMovieForm()
    context = {'form':form}
    return render(request, 'addmovie.html', context)

 

Create a url in urls.py

path('addmovie/', views.addmovie, name='addmovie'),

 

 

 

In our templates we need add load crispy_forms_tags after extending to our base.html. Create a new template named addmovie.html

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

 

And instead of form.as_p,we will be use form|crispy

{{ form|crispy }}

 

Create a new template named addmovie.html. Only Django super users can see this page thanks to the if statement in our template. Other logged on users will see a message  that states "You do not have access to this page"

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

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

{% block content %}
    {% if user.is_authenticated and user.is_superuser%}
        
    <div class="row justify-content-center">
        <div class="col-6">
            <div class="card">
                <div class="card-body">
				    <div class="col-md-6 offset-md-3">
                        <h2 class="text-center">Add New 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" action="{% url 'addmovie' %}">
                        {% 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 %}
                        {{ form|crispy }}
                        <input type="submit" value="Add" 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%}

 

 

Let's check how our form looks at the moment. There are asterisk symbols on the form. To remove those we need to modify the css style in our base.html file.

 

<!--Crispy Form Asteriks Removal Starts, add this in your base.html right after {% endblock title %}-->
    <style type="text/css">
      .asteriskField{
      display: none;
      }
      </style>
      <!--Crispy Form Asterisk Ends-->

 

 Each field on ouform has its own row, that causes our form to be long. We can play with the style of the form to have a nicer looking form. Open addmovie.html template. Remove  {{ form|crispy }} and add crispy forms fields like below.

I use bootstrap 5 here, The form has 4 rows. In each row I define thenumber of column, number of grids and size. For example the first row has 4 grids. The first column size is 4 (col-sm-4) and other fields are equally sized because I just used col-sm for them.

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

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

{% block content %}
    {% if user.is_authenticated and user.is_superuser%}
        
    <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">Add New 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" action="{% url 'addmovie' %}">
                        {% 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.publishDate | 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="Add" 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%}

 

 and now how it looks:

 

 

On the Navbar, I want to give a link to this form which can only be seen by superusers.  If user is superuser, then he will see this link.

base.html

              <li class="nav-item dropdown">
                <a class="nav-link dropdown-toggle text-white" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
                  {{user.username}}'s Profile
                </a>
                <ul class="dropdown-menu" style="background-color: #86aac4;">
                  <li><a class="dropdown-item text-white" href="#">My List</a></li>
                  <li><a class="dropdown-item text-white" href="/{% url 'editprofile' %}">My Account</a></li>
                  {% if user.is_authenticated and user.is_superuser%}
                  <li><a class="dropdown-item text-white" href="/{% url 'addmovie' %}">Add Movie</a></li>
                  {% endif %}
                  <li><hr class="dropdown-divider"></li>
                  <li><a class="dropdown-item text-white" href="/{% url 'logoutuser' %}">Log Out</a></li>
                </ul>
              </li>

 

If the logged on user is not a superuser. Even if he manually types the correct url to reach this form, he will see nothing but the message below thanks to the if-else statement in addmovie.html template.

  • Hits: 350