Class based Views

So far we have coded views as a function. But most of the advanced projects have class based views inhertiting from

django.views.generic

In urls.py, make sure to call {class_name}.as_view()

Advantages

Organization of code related to specific HTTP methods (GET, POST, etc.) can be addressed by separate methods instead of conditional branching.

Object oriented techniques such as mixins (multiple inheritance) can be used to factor code into reusable components.

Look at Documentation and list of available views for more info

Simple class based view

In views.py, instead of using index function, this class can be used

from django.views.generic imoprt View

    class CBView(View):
        def get(self,request):
            #contents as under index(request)
            return render(request,'index.html',{'text':'Simple CBV'})

In urls.py

path("",views.CBView.as_view()),

Template View

This class is built over simple view. This class automatically renders, thereby eliminating the need for render function

In views.py

#URL : templateview/
    #Template view
    from django.views.generic import TemplateView

    class IndexView(TemplateView):

        template_name = 'index.html'

        #Injecting content
        def get_context_data(self,**kwargs):
            # **kwargs are the other key word args
            context = super().get_context_data(**kwargs)
            context['text'] = "Template view!"
            return context

List View

This class is built over template view. This class takes in a model and automatically creates context dict

In models.py

# Create your models here.
    class School(models.Model):
        name = models.CharField(max_length=256)
        principal = models.CharField(max_length=256)
        location = models.CharField(max_length=256)

        def __str__(self):
            return self.name #This is printed when used as Foreign key

    class Student(models.Model):
        name = models.CharField(max_length=256)
        age = models.PositiveIntegerField()
        #use of related name is seen in list view
        school = models.ForeignKey(School,on_delete=models.CASCADE,related_name='students')

        def __str__(self):
            return self.name

In views.py, instead of getting list of entries as

list = models.{class_name}.objects.all()

we just use the ListView class

#List view
    #URL : listview/
    from django.views.generic import ListView
    from . import models

    #Lists all the school
    class SchoolListView(ListView):
        model = models.School
        #This class takes in the model class name, lower cases it and creates default context object and html files

        template_name = 'index.html'
        # in absense of template_name, this class will look into 'basic_app/school_list.html'

        context_object_name = 'schools'
        # in absence of context name, it will be 'school_list'

        def get_context_data(self,**kwargs):
            context = super().get_context_data(**kwargs)
            context['text'] = "List view!"
            return context

In index.html (called from url : listview/)

{%  if schools  %}
      <h2>List of schools : </h2>
      <ol>
        {%  for s in schools  %}
          <!-- id is an autogenetrated primary key -->
         <li><a href="">, </a></li>
         <!-- the link becomes /listview/{id}-->
         <!-- if href="/", link becomes /{id} -->
         <!-- the link should match with the detailsView in urls.py -->

         <!-- 'students' is a related name in foreign key -->
         {%  for student in s.students.all  %}
           <p>,  years old</p>
         {%  endfor  %}

        {%  endfor  %}
      </ol>
    {%  endif  %}

Detail View

This view creates a separate URL based on primary key for each entry in model. This looks same as ListView, except for URL mappings and naming conventions

In views.py

#Lists the details of school, like students
    #Each entery in the model gets a separate URL
    class SchoolDetailView(DetailView):
        model = models.School

        template_name = 'index.html'
        # in absense of template_name, this class will look into 'basic_app/school_detail.html'

        context_object_name = 'school_detail'
        # in absence of context name, it will be 'school'

        def get_context_data(self,**kwargs):
            context = super().get_context_data(**kwargs)
            context['text'] = "Detail view!"
            return context

In urls.py

path('listview/<int:pk>/',views.SchoolDetailView.as_view(),name='details'),
#pk stands for primary key

In index.html (renders separate for each entry under URL 'listview/{id}'

{%  if school_detail  %}
      <h2>Schools details: </h2>
      <p>Name : </p>
      <p>Principal : </p>
      <p>Location : </p>
      <h3>Students : </h3>
      <!-- 'students' is a related name in foreign key -->
      {%  for student in school_detail.students.all  %}
        <p>,  years old</p>
      {%  endfor  %}
    {%  endif  %}

Create View

Creates a HTML form with fields that can be filled and added to database

In views.py

#CreateView
    #URL : createview/
    from django.views.generic import CreateView
    #Creates a form to add entry into model
    class SchoolCreateView(CreateView):
        model = models.School
        #fields are mandatory
        fields = ('name','principal','location')
        template_name = 'index.html'
        #by default, looks for basic_app/school_form.html
        # context variable = form

This requires get_absolute_url() method to be implemented in model class. This gives the URL to redirect upon submission of form. 'action' attribute in form element does not work.

Instead of get_absolute_url, one can also specify success_url attribute in the CBV

In models.py under the class School

#This needs to be implemented to use CreateView
    def get_absolute_url(self):
        return reverse("details",kwargs={'pk':self.pk})
        # return "/listview/"

Update View

Each entry gets a separate URL with form fields to update. This also requires get_absolute_url() to be implemented / success_url to be defined

In views.py

#UpdateView
    from django.views.generic import UpdateView

    #Each entry gets a separate URL
    class SchoolUpdateView(UpdateView):
        model = models.School
        fields = ('name','principal')
        # if  is used in html, it wont be rendered

        template_name= 'index.html'
        #by default, looks for basic_app/school_form.html
        # context variable = form

        #success_ur = reverse_lazy('index)

In urls.py

path('update/<int:pk>/',views.SchoolUpdateView.as_view(),name='update'),

In index.html

{%  if form  %}
      {%  if not form.instance.pk  %}
        <!-- when entered from SchoolCreateView -->
        <h1>Create school</h1>
      {%  else  %}
        <!-- when entered from SchoolUpdateView -->
        <h1>Update school</h1>
      {%  endif  %}
      <form method="POST">
        {%  csrf_token  %}
        
        <input type="submit" class='btn btn-primary' value="Submit">
      </form>
    {%  endif  %}

Delete View

Each entry has a separate URL

In views.py

#DeleteView
    from django.views.generic import DeleteView
    from django.urls import reverse_lazy

    #Each entry gets a separate URL
    class SchoolDeleteView(DeleteView):
        model = models.School
        #reverse_lazy redirects only upon success
        success_url = reverse_lazy("list")
        template_name= 'school_confirm_delete.html'
        #basic_app/school_confirm_delete.html is the default html file
        context_object_name = 'school_delete'
        #default is school

In urls.py

path('delete/<int:pk>/',views.SchoolDeleteView.as_view(),name='delete'),

In school_confirm_delete.html

<h1>Delete ?</h1>
    <form method="post">
        {%  csrf_token  %}
        <input type="submit" class="btn btn-danger" value="Delete">
        <a class="btn btn-warning" href="{%  url 'details' pk=school_delete.pk  %}">Cancel</a>
    </form>