Django Calling Function in Template |chapter 8

Hi, if you have been following this series then congratulations you have made it to chapter 8. In this post, we are going to learn about and use Django Calling View Function in Template, What that means is that we will have a function in views.py that does something when a button or link is clicked on the templates.

We will also:

  • Add a decent navbar
  • Hide the ticket create form till a button is clicked
  • Enable support team users to create tickets as well.
  • Support team to View all tickets.
  • Mark a ticket as done.

If you are new please check my other posts in this series and overall consider subscribing to my newsletter.

Before we begin you must have gotten an error  ”TypeError: ‘AnonymousUser’ object is not iterable”on the previous post if you log out and log in or signup as a new client. This happened because we were referring our new logged in user to the client page and trying to load tickets created by the user. Well, this user has not logged in automatically after being created so django did not know which tickets to load.

Quick fix, lets login user automatically after they signup. on views.py, import the following:

from django.contrib.auth import login

Then update the client view to include login(self.request, user)  so that it looks like this:

class clientsSignUp(CreateView):
    model = User
    form_class = ClientSignUpForm
    template_name = 'registration/signup_form.html'

    def get_context_data(self, **kwargs):
        kwargs['user_type'] = 'client'
        return super().get_context_data(**kwargs)

    def form_valid(self, form):
        user = form.save()
        login(self.request, user)
        return redirect('client_home')

That should solve the problem:

New navigation bar:

I don’t know about you but the current navigation is not cool at all. Because this I not front-end tutorial, I’ll just go to w3schools.com and pick one navigation from there. I use bootstrap4 a lot. Follow this step with me. (I assume you know a bit of HTML, CSS and Bootstrap)

  • Go here and choose the navbar that you like. I choose the ”collapsing navigation’.
  • Click the try yourself a button and it will open a new page that you can see and edit the whole code and a preview.django bootstrap
  • I will copy the nav section only.
  • And paste it on base.html just after body.
  • Edit it as you want. I edited mine to be as follows:
    <nav class="navbar navbar-expand-md bg-dark navbar-dark">
        <a class="navbar-brand" href="#">Sticketing</a>
        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#collapsibleNavbar">
            <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="collapsibleNavbar">
            <ul class="navbar-nav">
                {% if request.user.is_authenticated %}
                <li class="nav-item">
    
                    <a class="nav-link" href="{% url 'logout'%}">{{request.user}} logout?</a>
    
    
                </li>{% else %}
                <li class="nav-item">
                    <a class="nav-link" href="{% url 'login'%}">Login</a>
                </li>
                <li class="nav-item">
                    <a class="nav-link" href="{% url 'home'%}">Sign up</a>
                </li>
                {% endif %}
            </ul>
        </div>
    </nav>

    And now I have my cool navigation that collapses on small devices.bootstrap4 nav in django

Hide form on the client page.

Next is to hide the form when the user does not need it. To achieve this, I will do the same thing we did with the navbar. this time I will take the basic collapse:

<button data-toggle="collapse" data-target="#demo">Collapsible</button>

<div id="demo" class="collapse">
Lorem ipsum dolor text....
</div>

I’ll paste that on my client page and replace ‘Lorem ipsum dolor text …’ with my form. Here is my final client/home.html 

{% extends 'base.html' %}
{% load static %}
{% block content %}
<!-- new -->
{% load crispy_forms_tags %}

<div class="container">
    <button data-toggle="collapse" class="btn btn-block btn-primary" data-target="#demo">Creat New Ticket</button>

    <div id="demo" class="collapse">
        <div class="container">
            <div class="row">
                <div class="col-md-3"></div>
                <div class="col-md-6">
                    <h5 class="text-center">Create a new Ticket</h5>
                    <!-- new -->
                    <form method="post" novalidate>
                        {% csrf_token %}
                        <input type="hidden" name="next" value="{{ next }}">
                        {{ form|crispy }}
                        <br>
                        <button type="submit" class="btn btn-success">Submit</button>
                    </form>


                </div>
                <div class="col-md-3"></div>
            </div>
            <!-- new -->

        </div>
    </div>

    <div class="container">
        <br>
        <ul class="list-group">
            {% for ticket in my_tickets %}

            <li class="list-group-item d-flex mb-1  list-group-item-dark justify-content-between align-items-center">
                <a href="{% url 'ticket_detail' id=ticket.pk %}">TK{{ticket.id}}</a>
                <span class="badge badge-primary badge-pill">{{ticket.status}}</span>
            </li>

            {% endfor %}
        </ul>

        <br><br>
    </div>
</div>
{% endblock content%}

Nice. My output is:

Enable Support Team to Create Tickets

Django Calling View Function in Template will happen here but first, let us enable clients to open a ticket just in case the client has contacted them directly. This could be when clients computer and the phone has no internet at all.

This will just be the same with client form.

views.py

def support(request):
    tickets = Ticket.objects.all()

    if request.method == "POST":
        form = TicketCreateForm(request.POST)
        if form.is_valid():
            TicketAdd = form.save(commit=False)
            TicketAdd.opened_by = request.user
            TicketAdd.done_by = request.user  #new
            TicketAdd.status = 'inprogress'  #new
            TicketAdd.save()
            return redirect('support_home')  #redirect to support page.

    form = TicketCreateForm()
    context = {
        'form': form,
        'tickets': tickets,
    }

    return render(request, 'support/home.html', context)

Line 9 and 10 we are simply populating fields; done_by and status. Why? You guessed it right, whenever a member of support staff adds a new ticket they are in charge of it. Hence the status too.

support/home.html:

I don’t concentrate much on HTML because its another series I’ll write another day.

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

{% block content %}
<div class="container">
    <div class="card">
        <div class="card-body">

            <div class="container">
                <!-- create new ticket  -->
                <button data-toggle="collapse" class="btn btn-block btn-primary" data-target="#demo">Creat New
                    Ticket</button>

                <div id="demo" class="collapse">
                    <div class="container">
                        <div class="row">
                            <div class="col-md-3"></div>
                            <div class="col-md-6">
                                <h5 class="text-center">Create a new Ticket</h5>
                                <!-- new -->
                                <form method="post" novalidate>
                                    {% csrf_token %}
                                    <input type="hidden" name="next" value="{{ next }}">
                                    {{ form|crispy }}
                                    <br>
                                    <button type="submit" class="btn btn-success">Submit</button>
                                </form>


                            </div>
                            <div class="col-md-3"></div>
                        </div>
                        <!-- new -->

                    </div>
                </div>

                <!-- end of create new ticket  -->
                <br>
                <h3>Open and Pending Tickets </h3><br>
                <table class="table table-striped">
                    <thead>
                        <tr>
                            <th>Ticket No.</th>
                            <th>Opened By:</th>
                            <th>Date</th>
                            <th>Actions</th>
                        </tr>
                    </thead>
                    <tbody>
                        {% for ticket in tickets%}
                        <tr>
                            <td>{{ticket.id}}</td>
                            <td>{{ticket.opened_by}}</td>
                            <td>{{ticket.opened_on}}</td>
                            <td>Take | View</td>
                        </tr>
                        {%endfor%}

                    </tbody>
                </table>
            </div>
        </div>
    </div>
</div>
{% endblock content %}

It should now look like this;

Note: you won’t be able to create a ticket from here. Auto-login support.

sticketing

Enable Support To Take Tickets

Our main aim here is to allow staff to take a ticket they want to work on. That is if the client opened the ticket than done_by field will be populated with the current user. Then change the status to inprogress

To achieve this, we will do the following:

  1. Create a Django view function that takes pk of the ticket, queries the ticket, alters the fields and saves the update. It also redirects users back to support home page
  2. Create a URL that will pass pk to this view. This pk comes from templates.
  3. Call the function from template passing the pk of the post in question.

Views.py:

def takejob(request, tk):
    ticket = get_object_or_404(Ticket, pk=tk)
    #tk is ticket selected
    ticket.done_by = request.user
    ticket.status = 'inprogress'
    ticket.save()  #saving the update
    return redirect('support_home')  #redirecting to support page

1: we are creating the function and passing request and variable tk which will have the id of the ticket

2. we get the specific ticket from the database or 404 if not found. you can see we are filtering ticket with primary key equal to tk.

4-5: we are updating the fields we want to update.

6. We save the update.

7. Take the user back to support home page.

I don’t think this is the best way to do it. It will make many requests and if the user was doing something on the page it will go after a refresh. Learn about Django ajax by simpleisbetterthancomplex

urls.py

Just add the new URL.

path('take/job<int:tk>', views.takejob, name = 'take_job'),

We had discussed this kind of URL in the previous chapter.

support/home.html:

Here is what my table looks like:

<table class="table table-striped">
                   <thead>
                       <tr>
                           <th>Ticket No.</th>
                           <th>Status:</th>
                           <th>Opened By:</th>
                           <th>Date</th>
                           <th>Done by:</th>
                           <th>Actions</th>
                       </tr>
                   </thead>
                   <tbody>
                       {% for ticket in tickets%}
                       <tr>
                           <td>{{ticket.id}}</td>
                           <td>{{ticket.status}}</td>
                           <td>{{ticket.opened_by}}</td>
                           <td>{{ticket.opened_on}}</td>
                           <td>{{ticket.done_by}}</td>
                           <!-- change the line bellow -->
                           <td> <a href="{% url 'take_job' tk=ticket.pk %}"> Take</a> |
                               <a href="{% url 'ticket_detail' id=ticket.pk %} ">View</a></td>
                       </tr>
                       {%endfor%}

                   </tbody>
               </table>

Up to now if you try to take job, you will get an error:

NB: ensure you, auto-login support users, too.

Tried to update field ticketing.Ticket.done_by with a model instance, <SimpleLazyObject: <User: cecewydi>>. Use a value compatible with CharField.

 

Now, this error expects us to update our model field from Carfield to ForeignKey.

Alter the done_by fields:

done_by = models.ForeignKey(User, on_delete=models.PROTECT,null = True, blank = True, related_name = 'my_tickets')

makemigrations and migrate

 

You might get errors, especially if you tried taking a job. If that is the case you can delete db.sqlite3 and migrations folder

And we are good to go.

Mark Ticket as Done.

Views.py

def complete(request, tk):
    ticket = get_object_or_404(Ticket, pk=tk)
    ticket.status = 'closed'
    ticket.save()

    return redirect('support_home')

urls.py

from django.urls import path
from . import views

urlpatterns = [
    path('', views.home, name='home'),
    path('clients/home', views.clients, name='client_home'),
    path('support/home', views.support, name='support_home'),
    path('director/home', views.director, name='director_home'),
    path('ticket/detail<int:id>', views.ticket_details, name='ticket_detail'),
    path('take/job<int:tk>', views.takejob, name='take_job'),
    path('complete/job<int:tk>', views.complete, name='complete_job'),
]

support/home.html:

Update the table body

<tbody>
                       {% for ticket in tickets%}
                       <tr>
                           <td>{{ticket.id}}</td>
                           <td>{{ticket.status}}</td>
                           <td>{{ticket.opened_by}}</td>
                           <td>{{ticket.opened_on}}</td>
                           <td>{{ticket.done_by}}</td>
                           <!-- change the line bellow -->
                           <td> <a href="{% url 'take_job' tk=ticket.pk %}"> Take</a> |
                               <a href="{% url 'ticket_detail' id=ticket.pk %} ">View</a>|
                               <a href="{% url 'complete_job' tk=ticket.pk %} ">Mark as Complete</a></td>
                       </tr>
                       {%endfor%}

                   </tbody>

We are done with for this post. That is how Django Calling View Function in Template works.

If you get any problems or question you can reach out to me.

Get the code from github

You may join my whatsapp group

or Contact me personally info@omborisymons.com

.

 

 

 

close

Leave a Comment

error

Enjoy this blog? Please spread the word :)