Sunday, November 3, 2019

PollOpinion site in Django-5 ( Making other pages for the PollSite)

It's time to create the other required views for the polls app. Open the polls\views.py file and add the following:

from django.shortcuts import render

# Create your views here.

def index(request):
    """The home page for Learning Log"""
    return render(request, 'polls/index.html')
   
def detail(request, question_id):
    return HttpResponse("You're looking at question %s." % question_id)
   
def results(request, question_id):
    response = "You're looking at the results of question %s."
    return HttpResponse(response % question_id)

def vote(request, question_id):
    return HttpResponse("You're voting on question %s." % question_id)



As we have created new views, the same should be registered with the polls.url, open polls/url.py and add the following:

from django.urls import path

from . import views

app_name = 'polls'
urlpatterns = [
    # Home page
    path('', views.index, name='index'),   
    # ex: /polls/5/
    path('<int:question_id>/', views.detail, name='detail'),
    # ex: /polls/5/results/
    path('<int:question_id>/results/', views.results, name='results'),
    # ex: /polls/5/vote/
    path('<int:question_id>/vote/', views.vote, name='vote'),
   
    ]


When a page is requested from your website – say, “/polls/5/”, Django will load the mysite.urls Python module because it’s pointed to by the ROOT_URLCONF setting. It finds the variable named urlpatterns and traverses the patterns in order. After finding the match at 'polls/', it strips off the matching text ("polls/") and sends the remaining text – "5/" – to the ‘polls.urls’ URLconf for further processing. There it matches '<int:question_id>/', resulting in a call to the detail() view as shown below:

detail(request=<HttpRequest object>, question_id=5)

The question_id=5 part comes from <int:question_id>. Using angle brackets “captures” part of the URL and sends it as a keyword argument to the view function. The :question_id> part of the string defines the name that will be used to identify the matched pattern, and the <int: part is a converter that determines what patterns should match this part of the URL path.

Usually a  view is responsible for returning an HttpResponse object containing the content for the requested page, or raising an exception such as Http404 and user defined implementation as it can read records from a database, use a template system such as Django’s – or a third-party Python template system, generate a PDF file, output XML, create a ZIP file on the fly, anything we want, using whatever Python libraries we want.

Now let's modify our index() function in the polls/views.py so that it displays the latest 5 poll questions in the system, separated by commas, according to publication date. See the following code:

def index(request):
"""The home page for Learning Log"""
"""return render(request, 'polls/index.html')"""
latest_question_list = Question.objects.order_by('-pub_date')[:5]
        output = ', '.join([q.question_text for q in latest_question_list])
        return HttpResponse(output)

Now if we open the http://127.0.0.1:8000/ page, it's blank.

Before we move ahead, let's focus a bit on the page design part as at the current stage a page's design is hard coded in the view, which isn't the best way as every time we wish to change design we have to change code at lot of places. Now we'll use Django’s template system to separate the design from Python by creating a template that the view can use. Let's modify our polls/templates/polls/index.html file and change it's code as shown below:

{% if latest_question_list %}
    <ul>
    {% for question in latest_question_list %}
        <li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
    {% endfor %}
    </ul>
{% else %}
    <p>No polls are available.</p>
{% endif %}

Next we update our index view in polls/views.py to use the template:

from django.shortcuts import render
from .models import Question
from django.http import HttpResponse
from django.template import loader

# Create your views here.

def index(request):

latest_question_list = Question.objects.order_by('-pub_date')[:5]
template = loader.get_template('polls/index.html')
context = {'latest_question_list': latest_question_list,
}
return HttpResponse(template.render(context, request))

There is a shortcut in Django which makes loading and returning response easier using the render(). Let's use this method in the code shown above and modify the def index(request) method:

def index(request):

latest_question_list = Question.objects.order_by('-pub_date')[:5]
template = loader.get_template('polls/index.html')
context = {'latest_question_list': latest_question_list,
}
        return render(request, 'polls/index.html', context)

The render() function takes the request object as its first argument, a template name as its second argument and a dictionary as its optional third argument. It returns an HttpResponse object of the given template rendered with the given context.

Now if we open the http://127.0.0.1:8000/ page it shows page displaying "No polls are available." The code loads the template called polls/index.html and passes it a context. The context is a dictionary mapping template variable names to Python objects. Since we didn't add any Poll question the page displays the  "No polls are available."

Next we'll modify the detail() in the views which  displays the question text for a given poll. See the modified method below:

def detail(request, question_id):

try:
        question = Question.objects.get(pk=question_id)
    except Question.DoesNotExist:
        raise Http404("Question does not exist")
    return render(request, 'polls/detail.html', {'question': question})

As you may have noticed the view raises the Http404 exception if a question with the requested ID doesn’t exist. Make sure to add from django.http import Http404 along with other import statements. We can use a helper function get_object_or_404() instead of automatically catching the ObjectDoesNotExist exceptions at a higher level. See the modified detail() method:

def detail(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, 'polls/detail.html', {'question': question})

The get_object_or_404() function takes a Django model as its first argument and an arbitrary number of keyword arguments, which it passes to the get() function of the model’s manager. It raises Http404 if the object doesn’t exist.

Make sure to add from django.shortcuts import get_object_or_404, render along with other import statements.

It's time now to modify polls/templates/polls/detail.html file as shown below:

<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
    <li>{{ choice.choice_text }}</li>
{% endfor %}
</ul>

The template system uses dot-lookup syntax to access variable attributes. In the example of {{ question.question_text }}, first Django does a dictionary lookup on the object question. Failing that, it tries an attribute lookup – which works, in this case. If attribute lookup had failed, it would’ve tried a list-index lookup.

Method-calling happens in the {% for %} loop: question.choice_set.all is interpreted as the Python code question.choice_set.all(), which returns an iterable of Choice objects and is suitable for use in the {% for %} tag.

Our project has just one app, polls but in real Django projects, there might more. How does Django differentiate the URL names between them? For example, the polls app has a detail view, and so might an app on the same project that is for a blog. How does one make it so that Django knows which app view to create for a url when using the {% url %} template tag?

The answer is to add namespaces to your URLconf. In the polls/urls.py file, go ahead and add an app_name to set the application namespace:

from django.urls import path

from . import views

app_name = 'polls'
urlpatterns = [
# Home page
    path('', views.index, name='index'), 
    # ex: /polls/5/
    path('<int:question_id>/', views.detail, name='detail'),
    # ex: /polls/5/results/
    path('<int:question_id>/results/', views.results, name='results'),
    # ex: /polls/5/vote/
    path('<int:question_id>/vote/', views.vote, name='vote'),
 
    ]

Another simplification we can do is by removing hardcoded URLs in templates. When we wrote the link to a question in the polls/index.html template, the link was partially hardcoded like this:

<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>

The problem with this hardcoded, tightly-coupled approach is that it becomes challenging to change URLs on projects with a lot of templates. However, since we defined the name argument in the path() functions in the polls.urls module, we can remove a reliance on specific URL paths defined in our url configurations by using the {% url %} template tag:

<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>

The way this works is by looking up the URL definition as specified in the polls.urls module. We can see exactly where the URL name of ‘detail’ is defined below:
...
# the 'name' value as called by the {% url %} template tag
path('<int:question_id>/', views.detail, name='detail'),
...

Now we'll change our polls/templates/polls/index.html template from:


<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>

to point at the namespaced detail view:

<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>

Now go to http://127.0.0.1:8000/admin/ and add one question: I've added Most popular programming language? 

If we open http://127.0.0.1:8000/ we should see the following on our page
In the next post we shall implement the choice functionality to be used with the poll question. Till we meet next keep practicing and learning Python as Python is easy to learn!
Share:

0 comments:

Post a Comment