# Τεκμηρίωση του Django - 1.10.7

# Γράφοντας το πρώτο σας Django app, μέρος 3

Ο οδηγός αυτός ξεκινά εκεί που τελειώνει ο οδηγός 2. Συνεχίζουμε με την εφαρμογή μας (σκοπός μας είναι φτιάξουμε ένα σύστημα ψηφοφορίας όπου θα γράφουμε τις ερωτήσεις και τι πιθανές επιλογές και ο κόσμος θα μπορεί να ψηφίζει). Αυτή τη φορά θα επικεντρωθούμε στην προβολή της ιστοσελίδας μας στο ευρύ κοινό, στα “views”.

## Επισκόπηση

Ένα view είναι ένας “τύπος” μιας ιστοσελίδας (Web page) σε ένα Django application το οποίο, σε γενικές γραμμές, εξυπηρετεί μια συγκεκριμένη συνάρτηση και έχει ένα συγκεκριμένο template. Για παράδειγμα, ένα blog application, μπορεί να περιέχει τα ακόλουθα views:

- Blog homepage – προβάλει τα μερικά από τα τελευταία entries (καταχωρήσεις).

- Σελίδα “λεπτομερειών” μιας entry (καταχώρησης) – μια permalink σελίδα για ένα συγκεκριμένο entry.

- Σελίδα archive ανά ημερολογιακό χρόνο – προβάλει όλους τους μήνες που έχουν καταχωρήσεις για το δοθέντα έτος.

- Σελίδα archive ανά ημερολογιακό μήνα – προβάλει όλες τις μέρες που έχουν καταχωρήσεις για το δοθέντα μήνα.

- Σελίδα archive ανά ημερολογιακή μέρα – προβάλει όλα τα entries μια συγκεκριμένη μέρα.

- Προσθήκη σχολίου – χειρίζεται τα σχόλια πάνω στα posts για ένα συγκεκριμένο entry.

Στη δική μας εφαρμογή, θα έχουμε τα ακόλουθα τέσσερα views:

- Αρχική σελίδα (“index”) ερωτήσεων – προβάλει τις τελευταίες ερωτήσεις.

- Σελίδα “λεπτομερειών” μιας ερώτησης – προβάλει μια ερώτηση, χωρίς αποτελέσματα, αλλά με μια φόρμα (η οποία θα περιέχει τις επιλογές της ερώτησης) προς ψήφιση.

- Σελίδα “αποτελεσμάτων” μιας ερώτησης – προβάλει τα αποτελέσματα μιας συγκεκριμένης ερώτησης.

- Προσθήκη ψήφου – χειρίζεται τη ψήφο μιας συγκεκριμένης επιλογής μιας συγκεκριμένης ερώτησης.

Στο Django, οι ιστοσελίδες όπως επίσης και άλλα περιεχόμενα εξυπηρετούνται από τα views. Κάθε view αντιπροσωπεύεται από μια απλή Python συνάρτηση (ή μέθοδο, στην περίπτωση των class-based views). Το Django θα διαλέξει το view ανάλογα το URL το οποίο ζητήθηκε (πιο συγκεκριμένα, το μέρος του URL μετά το domain name).

Ίσως μέχρι τώρα έχετε δει στην διαδικτυακή σας περιήγηση μερικές ομορφιές όπως “ME2/Sites/dirmod.asp?sid=&type=gen&mod=Core+Pages&gid=A6CD4967199A42D9B65B1B”. Ίσως θα σας χαροποιήσει το γεγονός ότι το Django χρησιμοποιεί (και το κάνει) πολύ πιο κομψά URL patterns εν συγκρίσει με αυτά.

Ένα URL pattern (μοτίβο) είναι απλά μια γενική φόρμα ενός URL – για παράδειγμα: `/newsarchive/<year>/<month>/`.

Για να μεταβούμε από ένα URL σε ένα view, το Django χρησιμοποιεί κάτι το οποίο είναι γνωστό ως ‘URLconfs’. Ένα URLconf αντιστοιχεί URL μοτίβα (τα οποία περιγράφονται με regular expressions) σε views. Αυτά τα είδαμε περιληπτικά στον Οδηγό 1.

Ο συγκεκριμένος οδηγός προσφέρει βασικές οδηγίες στη χρήση των URLconfs. Επίσης, περισσότερες πληροφορίες μπορείτε να βρείτε στο module `django.urls`.

## Γράφοντας περισσότερα views

Ας προσθέσουμε τώρα μερικά views παραπάνω, στο αρχείο `polls/views.py`. Αυτά τα views είναι ελαφρώς διαφορετικά, επειδή παίρνουν και όρισμα (argument):

polls/views.py

    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)

Συνδέστε τα καινούργια αυτά views με το αρχείο `polls.urls` προσθέτοντας τον παρακάτω κώδικα στη κλήση της συνάρτησης `url()`:

polls/urls.py

    from django.conf.urls import url

    from . import views

    urlpatterns = [
        # ex: /polls/
        url(r'^$', views.index, name='index'),
        # ex: /polls/5/
        url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
        # ex: /polls/5/results/
        url(r'^(?P<question_id>[0-9]+)/results/$', views.results, name='results'),
        # ex: /polls/5/vote/
        url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
    ]

Ρίξτε μια ματιά με τον browser σας στη σελίδα, “/polls/34/”. Με το που πατήσετε enter, το GET request που κάνατε για αυτό το URL θα κάνει trigger το Django project σας, θα γίνει σκανάρισμα των URLs, θα ταιριάξει το δεύτερο URL pattern το οποίο με τη σειρά του θα καλέσει τη μέθοδο `detail()` και η τελευταία θα προβάλει το μήνυμα μαζί με το ID που εισήχθη στο URL (το response το οποίο επιστρέφει η `detail` συνάρτηση). Τώρα δοκιμάστε το “/polls/34/results/” και το “/polls/34/vote/” – αυτά τα URLs θα εμφανίσουν μια σελίδα placeholder για τα αποτελέσματα και τις ψηφοφορίες αντίστοιχα.

Όπως είπαμε και πιο πάνω όταν κάποιος κάνει request μια σελίδα από το website σας – ας πούμε την “/polls/34/”, τότε το Django θα φορτώσει το `mysite.urls` Python module επειδή δείχνει σε αυτό η ρύθμιση `ROOT_URLCONF`. Βρίσκει μέσα μια μεταβλητή με το όνομα `urlpatterns` και βλέπει-σκανάρει μία-μία τις regular expressions με τη σειρά που είναι δηλωμένες. Αφού βρει αυτή που ταιριάζει στο `'^polls/'`, κόβει το string που ταίριαξε (`"polls/"`) και στέλνει το υπόλοιπο – `"34/"` – στο ‘polls.urls’ URLconf για περαιτέρω επεξεργασία. Εκεί, ταιριάζει το `r'^(?P<question_id>[0-9]+)/$'`, με αποτέλεσμα να κληθεί το `detail()` view ως εξής:

    detail(request=<HttpRequest object>, question_id='34')

Το κομμάτι `question_id='34'` προκύπτει από το `(?P<question_id>[0-9]+)`. Χρησιμοποιώντας παρενθέσεις γύρω από ένα μοτίβο, “αιχμαλωτίζουμε” το κείμενο (text) που ταίριαξε (βάση του μοτίβου) και το στέλνουμε ως όρισμα (argument) στη συνάρτηση view (π.χ `detail`). Το `?P<question_id>` ορίζει το όνομα που θα χρησιμοποιηθεί για να αναγνωρίσουμε το κείμενο που ταίριαξε. Τέλος, το `[0-9]+` είναι μια regular expression η οποία ταιριάζει ακολουθίες ψηφίων (π.χ ένας αριθμός).

Επειδή, στην ουσία, τα URL patterns είναι regular expressions, στην πραγματικότητα δεν υπάρχουν όρια για το τι μπορεί να επιτευχθεί με αυτά. Επίσης δεν υπάρχει λόγος να προσθέσετε άσχημες URL καταλήξεις όπως `.html` – εκτός και αν το θέλετε. Αν ναι, τότε θα κάνετε κάτι σαν:

    url(r'^polls/latest\.html$', views.index),

Αλλά μην το κάνετε. Είναι ανόητο και μη επαγγελματικό.

## Γράφοντας views οι οποίες κάνουν κάτι

Κάθε view είναι υπεύθυνο στο να κάνει ένα από δύο πράγματα: να επιστρέφει ένα `HttpResponse` object το οποίο περιέχει το περιεχόμενο για την σελίδα που ζητήθηκε ή να κάνει raise κάποιο exception όπως το `Http404`. Τα υπόλοιπα εξαρτώνται από εσάς.

Το view σας μπορεί να διαβάσει records από την βάση δεδομένων ή όχι. Μπορεί να χρησιμοποιήσει ένα template system όπως αυτό του Django – ή κάποιο τρίτο Python template system – ή όχι. Μπορεί να παράγει κάποιο PDF αρχείο, να εξάγει XML, να δημιουργεί κάποιο ZIP αρχείο, να εξάγει JSON δεδομένα, βασικά μπορεί να κάνει τα πάντα χρησιμοποιώντας οποιεσδήποτε βιβλιοθήκες Python επιθυμείτε.

Το μόνο που απαιτείται από το Django είναι ένα `HttpResponse` ή μια exception.

Επειδή είναι βολικό, θα χρησιμοποιήσουμε το database API του Django, το οποίο καλύψαμε στον Οδηγό 2. Παρακάτω βλέπετε ένα καινούργιο view με το όνομα `index()`, το οποίο εμφανίζει τις τελευταίες 5 ερωτήσεις (που υπάρχουν στην βάση δεδομένων), διαχωρισμένες με κόμμα, βάση της ημερομηνίας που καταχωρήθηκαν (δημιουργήθηκαν):

polls/views.py

    from django.http import HttpResponse

    from .models import Question

    def index(request):
        latest_question_list = Question.objects.order_by('-pub_date')[:5]
        output = ', '.join([q.question_text for q in latest_question_list])
        return HttpResponse(output)

    # Leave the rest of the views (detail, results, vote) unchanged

Υπάρχει ένα πρόβλημα εδώ, όμως: ο σχεδιασμός της σελίδας (design) είναι γραμμένος στο view. Αν χρειαστεί να αλλάξετε τον τρόπο εμφάνιση της σελίδας σας, θα χρειαστεί να επεξεργαστείτε αυτόν τον Python κώδικα. Ας κάνουμε κάτι καλύτερο. Ας χρησιμοποιήσουμε το template system του Django για να διαχωρίσουμε το design από τον Python κώδικα δημιουργώντας ένα template που το view μπορεί να χρησιμοποιήσει.

Για αρχή, δημιουργήστε ένα φάκελο με το όνομα `templates` κάτω από τον φάκελο `polls`. Το Django θα κοιτάξει για templates εκεί.

Η ρύθμιση `TEMPLATES` του project σας περιγράφει πως το Django θα φορτώνει και θα κάνει render τα templates. Η προεπιλεγμένη ρύθμιση στο αρχείο settings παραμετροποιεί το `DjangoTemplates` backend του οποίου η ρύθμιση `APP_DIRS` είναι ρυθμισμένη στο `True`. Έτσι, το `DjangoTemplates` ψάχνει για ένα υπό-φάκελο “templates” σε κάθε εφαρμογή (app) που είναι περασμένη στη ρύθμιση `INSTALLED_APPS`.

Μέσα στο φάκελο `templates` που μόλις δημιουργήσατε, δημιουργήστε άλλο έναν φάκελο με το όνομα `polls` και μέσα σε αυτό το φάκελο ένα HTML αρχείο με το όνομα `index.html`. Με άλλα λόγια, το template σας θα πρέπει να βρίσκεται στο `polls/templates/polls/index.html`. Λόγω του μηχανισμού λειτουργίας του φορτωτή template (template loader) `app_directories` όπως αναφέρθηκε παραπάνω, μπορείτε να αναφερθείτε σε αυτό το template μέσα στο Django ως `polls/index.html`.

Template namespacing

Ίσως θα αναρωτηθήκατε γιατί βάλαμε το template μέσα σε υπό-φάκελο (polls) και όχι μέσα στο φάκελο `polls/templates`. Αν το κάναμε έτσι, τότε δεν θα ήταν και τόσο καλή ιδέα. Το Django θα ψάξει και θα βρει το πρώτο template βάση ονόματος. Αν, λοιπόν, εσείς έχετε ένα άλλο template με το ίδιο όνομα σε μία διαφορετική εφαρμογή, τότε το Django δεν θα είναι σε θέση να ξεχωρίσει ποια από τα δύο να χρησιμοποιήσει. Πρέπει καθοδηγήσουμε το Django στην επιλογή του template που θέλουμε και ο ευκολότερος τρόπος είναι να χρησιμοποιήσουμε την τεχνική namespace. Αυτό επιτυγχάνεται βάζοντας τα templates μέσα σε έναν υπό-φάκελο ο οποίος θα έχει το ίδιο όνομα με το όνομα της εφαρμογής.

Βάλτε τον ακόλουθο κώδικα στο template:

polls/templates/polls/index.html

    {% 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 %}

Τώρα, ας ανανεώσουμε το `index` view μέσα στο αρχείο `polls/views.py` ούτως ώστε να χρησιμοποιήσει το νέο μας template:

polls/views.py

    from django.http import HttpResponse
    from django.template import loader

    from .models import Question

    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))

Αυτός ο κώδικας φορτώνει το template με το όνομα `polls/index.html` και περνάει σε αυτό ένα context. Το context είναι ένα dictionary το οποίο αντιστοιχεί template variable ονόματα σε Python objects.

Πηγαίνετε στη σελίδα με το browser σας στο “/polls/” και θα πρέπει να δείτε μια λίστα (με κουκίδες) η οποία περιέχει την ερώτηση “What’s up” που φτιάξαμε στον Οδηγό 2. Το link δείχνει στην σελίδα με τις λεπτομέρειες της ερώτησης.

### Η συντόμευση: `render()`

Είναι πολύ συνηθισμένο να φορτώνεται ένα template, να γεμίζεται ένα context και να επιστρέφεται ένα `HttpResponse` object του οποίου το αποτέλεσμα θα είναι το rendered template. Το Django παρέχει μια συντόμευση για όλα αυτά. Ορίστε πάλι το `index()` view, γραμμένο από την αρχή:

polls/views.py

    from django.shortcuts import render

    from .models import Question

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

Σημειώστε ότι, αφού χρησιμοποιήσουμε την `render()` για όλες τις views, δεν χρειάζεται πλέον να κάνουμε import τα `loader` και `HttpResponse`. Παραταύτα, θα χρειαστεί να κρατήσετε το `HttpResponse` αν έχετε ακόμη τις μεθόδους για το``detail``, `results` και `vote` view.

Η συνάρτηση `render()` παίρνει ως πρώτο όρισμα το request object, ως δεύτερο το όνομα του template και ένα dictionary ως τρίτο προαιρετικό. Επιστρέφει ένα `HttpResponse` object του δηλωθέν template το οποίο template έχει γίνει rendered βάση του δοθέν context.

## Raising σφάλμα 404

Σε αυτό το σημείο θα πειράξουμε το view που αφορά στις λεπτομέρειες μιας ερώτησης – τη σελίδα, δηλαδή, η οποία εμφανίζει την ερώτηση για μια συγκεκριμένη ψηφοφορία. Αυτό είναι το view:

polls/views.py

    from django.http import Http404
    from django.shortcuts import render

    from .models import Question
    # ...
    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})

Το νέο concept εδώ είναι: Το view κάνει raise την exception `Http404` αν η ερώτηση με το συγκεκριμένο ID δεν υπάρχει (στη βάση δεδομένων).

Θα συζητήσουμε τι μπορείτε να βάλετε μέσα στο template με το όνομα `polls/detail.html` λίγο αργότερα, αλλά τώρα αν βιάζεστε να δείτε πως θα φαίνεται γράψτε τα ακόλουθα στο αρχείο `polls/detail.html`:

polls/templates/polls/detail.html

    {{ question }}

Αυτό θα σας δώσει μια ιδέα.

### Η συντόμευση: `get_object_or_404()`

Είναι συνηθισμένο να χρησιμοποιείται η μέθοδος `get()` η οποία να κάνει raise το exception `Http404` αν το object δεν υπάρχει. Το Django σας παρέχει μια συντόμευση και γι’ αυτό. Παρακάτω φαίνεται το `detail()` view, γραμμένο από την αρχή (πάλι):

polls/views.py

    from django.shortcuts import get_object_or_404, render

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

Η συνάρτηση `get_object_or_404()` παίρνει ως πρώτο όρισμα ένα Django model και ένα αυθαίρετο αριθμό από arguments, τα οποία τα δίνει στην μέθοδο `get()` του manager του μοντέλου (model manager) που περάστηκε ως πρώτο όρισμα. Η συνάρτηση αυτή κάνει raise το exception `Http404` αν το object δεν υπάρχει.

Φιλοσοφία

Γιατί όμως χρησιμοποιούμε την βοηθητική συνάρτηση (helper function) `get_object_or_404()` αντί να κάνουμε αυτόματα το catch του exception `ObjectDoesNotExist` σε ένα υψηλότερο επίπεδο; Επίσης, γιατί να βάλουμε το model API να κάνει raise το exception `Http404` αντί του exception `ObjectDoesNotExist`;

Η απάντηση είναι ίδια και στις δύο ερωτήσεις. Επειδή αυτό θα δεσμεύσει το επίπεδο του model με το επίπεδο του view. Ένας από τους πρωταρχικούς στόχους του Django είναι να διατηρηθεί η ανεξαρτητοποίηση και η μη σύνδεση μεταξύ οντοτήτων (το λεγόμενο loose coupling). Μερικά ελεγχόμενα couplings παρουσιάζονται στο module `django.shortcuts`.

Υπάρχει επίσης η συνάρτηση `get_list_or_404()`, η οποία λειτουργεί πανομοιότυπα με την `get_object_or_404()` – με τη μόνη διαφορά ότι χρησιμοποιεί την μέθοδο `filter()` αντί της `get()`. Η `get_list_or_404()` κάνει raise την exception `Http404` αν η λίστα είναι κενή (άδεια).

## Χρησιμοποιώντας ένα template system

Πίσω στο `detail()` view της εφαρμογής μας. Έχοντας το context variable με το όνομα `question`, το template `polls/detail.html` θα δείχνει κάπως έτσι:

polls/templates/polls/detail.html

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

Το template system χρησιμοποιεί την σύνταξη της τελείας για να ψάχνει μέσα στα variable attributes. Στο παράδειγμα του `{{ question.question_text }}`, το Django θα ψάξει πρώτα μέσα στο dictionary του object `question`. Αν δεν βρει τίποτα (δεν υπάρχει, δηλαδή, το key `question_text``στο υποτιθέμενο dictionary), προσπαθεί να βρει το ``question_text` σαν attribute του object – το οποίο στη συγκεκριμένη περίπτωση ισχύει. Αν όμως δεν υπήρχε, τότε θα έψαχνε σε μια λίστα να το βρει.

Ο τρόπος για να καλείτε μεθόδους φαίνεται στο βρόγχο επανάληψης `{% for %}`: το `question.choice_set.all` μεταφράζεται ως Python κώδικας `question.choice_set.all()`, το οποίο επιστρέφει ένα iterable από `Choice` objects και είναι βολικό προς χρήση (στην παρούσα) με τον βρόγχο επανάληψης `{% for %}` (το οποίο αναφέρεται και ως `template tag`).

Δείτε το άρθρο templates για περισσότερα σχετικά με τα templates.

## Αφαίρεση των hardcoded URLs στα templates

Θυμηθείτε ότι πριν, δηλώσαμε στην HTML το link για την ερώτηση στο template `polls/index.html` ως:

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

Το πρόβλημα με τα hardcoded URLs (αλλά και γενικά με την hardcoded προσέγγιση), είναι ότι από ένα σημείο και μετά γίνεται δύσκολο (αν όχι ακατόρθωτο) να αλλάξετε όλα τα URLs στα projects σας τα οποία χρησιμοποιούν πολλά templates. Ωστόσο, εφόσον έχετε ονοματήσει τα URLs σας μέσα στη εκάστοτε συνάρτηση `url()` στο αρχείο `polls.urls`, μπορείτε να αναφέρεστε σε αυτά χρησιμοποιώντας το template tag `{% url %}`:

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

Ο τρόπος με τον οποίο λειτουργεί αυτό το template tag είναι κοιτώντας τα URLs που έχετε ορίσει στο module `polls.urls`. Μπορείτε να δείτε ακριβώς σε ποιο URL έχει οριστεί το όνομα ‘detail’ παρακάτω:

    ...
    # the 'name' value as called by the {% url %} template tag
    url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
    ...

Αν θέλετε να αλλάξετε το URL αυτού του view σε κάτι άλλο (ίσως στο `polls/specifics/12/`), τότε αντί να ψάχνετε όλα τα templates που είχατε δηλώσει το παλιό URL προκειμένου να το αντικαταστήσετε με το καινούργιο, πολύ απλά πειράζετε ένα αρχείο (`polls/urls.py`) και όλα τα υπόλοιπα ανανεώνονται αυτόματα, χάρη στο template tag `{% url %}`:

    ...
    # added the word 'specifics'
    url(r'^specifics/(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
    ...

## Χρησιμοποιώντας namespacing στα ονόματα των URL

Το project του οδηγού αυτού έχει μόνο μια εφαρμογή, την `polls`. Σε πραγματικά Django projects, ίσως να έχετε πέντε, δέκα, είκοσι ή περισσότερα apps. Πως μπορεί να ξέρει και να διαφοροποιεί το Django τα ονόματα των URLs ανάμεσα σε τόσα apps; Για παράδειγμα, η εφαρμογή `polls` έχει ένα `detail` view και ίσως ένα άλλο app (στο ίδιο project) να έχει το ίδιο όνομα για ένα δικό του view. Όταν το Django συναντήσει το template tag `{% url 'detail' %}` πως θα ξέρει σε ποιο view να αναφερθεί;

Η απάντηση έρχεται με την προσθήκη namespaces στο URLconf. Στο αρχείο `polls/urls.py`, προσθέστε μια μεταβλητή `app_name` ούτως ώστε να θέσετε το namespace για αυτό το app:

polls/urls.py

    from django.conf.urls import url

    from . import views

    app_name = 'polls'
    urlpatterns = [
        url(r'^$', views.index, name='index'),
        url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
        url(r'^(?P<question_id>[0-9]+)/results/$', views.results, name='results'),
        url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
    ]

Τώρα, πηγαίνετε και αλλάξτε το αρχείο ``polls/index.html``από:

polls/templates/polls/index.html

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

στο:

polls/templates/polls/index.html

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

Όταν αισθανθείτε άνετα με τη συγγραφή των views, διαβάστε το τέταρτο μέρος αυτού του οδηγού για να μάθετε το πώς να διαχειρίζεστε απλές φόρμες και τα Django generic views.