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

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

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

## Γράφοντα μια απλή φόρμα

Ας αναβαθμίσουμε το HTML template (“polls/detail.html”), από τον προηγούμενο οδηγό, στο οποίο εμφανίζουμε τις λεπτομέρειες της ψηφοφορίας. Αυτή τη φορά θα προσθέσουμε ένα HTML `<form>` element:

polls/templates/polls/detail.html

    <h1>{{ question.question_text }}</h1>

    {% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}

    <form action="{% url 'polls:vote' question.id %}" method="post">
    {% csrf_token %}
    {% for choice in question.choice_set.all %}
        <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}" />
        <label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br />
    {% endfor %}
    <input type="submit" value="Vote" />
    </form>

Εδώ συμβαίνουν τα εξής:

- Το παραπάνω template εμφανίζει ένα radio button για κάθε επιλογή ερώτησης. Η τιμή (`value`) κάθε radio button είναι το ID αυτού. Το όνομα (`name`) σε όλα τα radio buttons είναι `"choice"`. Αυτό σημαίνει ότι, όταν κάποιος επιλέγει κάποια από τις πιθανές απαντήσεις και κάνει submit τη φόρμα, τότε θα σταλούν (στον server) τα εξής δεδομένα με τη μέθοδο POST (POST data): `choice=#` όπου # είναι το ID της επιλεγμένης απάντησης (στέλνεται, δηλαδή, το name του input μαζί με το value του ίδιου σε μια μορφή key=value). Αυτό είναι το βασικό concept των HTML forms.

- Θέτουμε το `action` της φόρμας σε `{% url 'polls:vote' question.id %}` και θέτουμε επίσης και τη μέθοδο που θα σταλεί στον server (`method="post"`). Είναι πολύ σημαντικό να χρησιμοποιήσουμε τη μέθοδο POST (`method="post"`) και όχι τη GET (`method="get"`) γιατί όταν κάνουμε submit τη συγκεκριμένη φόρμα σκοπεύουμε να αλλάξουμε τα data στο server (πιο συγκεκριμένα στην database). Οποτεδήποτε δημιουργείτε φόρμες που αλλάζουν (δημιουργούν νέα data - Create, αλλάζουν ήδη υπάρχοντα data - Update ή διαγράφουν data - Delete) τα δεδομένα στον server, τότε καλό είναι να χρησιμοποιείτε τη μέθοδο POST (`method="post"`). Αυτή η συμβουλή δεν είναι συγκεκριμένη για το Django. Είνα απλά μια πολύ καλή πρακτική στο Web development.

- Το `forloop.counter` δείχνει τον αριθμό που ο βρόγχος επανάληψης `for` έχει τρέξει. Αυτό το κάνουμε για να λάβει κάθε `input` element μοναδικό id.

- Εφόσον δημιουργούμε μια POST φόρμα (η οποία επιδρά στο server αλλάζοντας τα δεδομένα), θα πρέπει να ανησυχούμε για τα Cross Site Request Forgeries. Ευτυχώς, δεν θα πρέπει να ανησυχείτε πολύ, γιατί το Django έρχεται με ένα πανεύκολο σύστημα προστασίας από το CSRF. Εν ολίγοις, όλες οι POST φόρμες των οποίων τα actions αφορούν εσωτερικά URLs θα πρέπει να χρησιμοποιούν το template tag :ttag:`{% csrf_token %}<csrf_token>.

Ας δημιουργήσουμε τώρα ένα Django view το οποίο θα χειριστεί τα δεδομένα τα οποία έγιναν submit από τη φόρμα. Θυμηθείτε ότι στον Οδηγό 3, δημιουργήσαμε ένα URLconf, το οποίο περιλαμβάνει τη γραμμή:

polls/urls.py

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

Δημιουργήσαμε επίσης μια πρόχειρη υλοποίηση της συνάρτησης (view) `vote()`. Ας αναβαθμίσουμε και αυτό το view για να χειριστεί τα data. Προσθέστε τα ακόλουθα στο αρχείο `polls/views.py`:

polls/views.py

    from django.shortcuts import get_object_or_404, render
    from django.http import HttpResponseRedirect, HttpResponse
    from django.urls import reverse

    from .models import Choice, Question
    # ...
    def vote(request, question_id):
        question = get_object_or_404(Question, pk=question_id)
        try:
            selected_choice = question.choice_set.get(pk=request.POST['choice'])
        except (KeyError, Choice.DoesNotExist):
            # Redisplay the question voting form.
            return render(request, 'polls/detail.html', {
                'question': question,
                'error_message': "You didn't select a choice.",
            })
        else:
            selected_choice.votes += 1
            selected_choice.save()
            # Always return an HttpResponseRedirect after successfully dealing
            # with POST data. This prevents data from being posted twice if a
            # user hits the Back button.
            return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))

Ο παραπάνω κώδικας περιλαμβάνει μερικά πράγματα τα οποία δεν έχουμε καλύψει μέχρι τώρα στον οδηγό:

- Το object `request.POST` έχει τη μορφή ενός dictionary το οποίο σας επιτρέπει να έχετε πρόσβαση στα submitted data μέσω του ονόματος του key (όπως ακριβώς λειτουργεί ένα dictionary στην Python). Στην περίπτωση μας, το `request.POST['choice']` επιστρέφει το ID της επιλεγμένης απάντησης, υπό τη μορφή ενός string. Όλες οι τιμές μέσα το object `request.POST` έχουν τη μορφή string.

  Σημειώστε ότι το Django παρέχει επίσης ανάλογη πρόσβαση στα GET data (αν χρησιμοποιηθεί το `method="get"`) μέσω του object `request.GET` – αλλά στο συγκεκριμένο παράδειγμα χρησιμοποιούμε αποκλειστικά την `request.POST`, για να σιγουρευτούμε ότι τα data θα αλλάξουν (στη βάση δεδομένων) μόνο από κάποιο POST call (και ότι τυχόν από κάπου αλλού).

- Το object `request.POST['choice']` θα κάνει raise το exception `KeyError` (όπως και ένα κοινό Python dictionary) αν το key `choice` δεν βρεθεί (δεν υπάρχει, δηλαδή, στα POST data). Ο παραπάνω κώδικας κάνει handle το exception αυτό με το να επανεμφανίσει τη φόρμα ψηφοφορίας με ένα ανάλογο μήνυμα σφάλματος (error message).

- Αφού αυξήσουμε το πλήθος των ψήφων (κατά ένα, κάθε φορά), ο κώδικας επιστρέφει ένα object `HttpResponseRedirect` παρά ένα συνηθισμένο `HttpResponse`. Το object `HttpResponseRedirect` αρχικοποιείται (initializes) με ένα όρισμα (argument): το URL στο οποίο θα ανακατευθυνθεί ο χρήστης (δείτε τις ακόλουθες δύο παραγράφους σχετικά με το πως χτίζουμε το URL σε αυτή την περίπτωση).

  Όπως προαναφέραμε θα πρέπει πάντα το view να επιστρέφει ένα object `HttpResponseRedirect` μετά από επιτυχή χειρισμό δεδομένων που έγιναν submit με τη μέθοδο POST. Για ακόμη μια φορά, αυτή η συμβουλή δεν αφορά αποκλειστικά το Django, είναι απλώς καλή πρακτική για το Web development.

- Χρησιμοποιούμε τη συνάρτηση `reverse()` στον constructor της κλάσης `HttpResponseRedirect`. Η συνάρτηση αυτή βοηθά στο να αποφύγουμε να γράφουμε ολόκληρα τα URLs (hardcode) μέσα στη συνάρτηση view (ή οπουδήποτε αλλού σε Python κώδικα). Σαν ορίσματα παίρνει το όνομα του URL (π.χ αυτό που έχουμε ορίσει ως name μέσα στη συνάρτηση `url()` στο αρχείο `polls/urls.py`, με πρόθεμα το όνομα του application για τυχόν namespacing) και τυχόν arguments (είτε positional είτε named) τα οποία έχουν περαστεί μέσα στο URL (αυτά που “αιχμαλωτίστηκαν” λόγω των regular expressions). Στη δικιά μας περίπτωση, χρησιμοποιώντας το URLconf που δημιουργήσαμε στον Οδηγό 3, η κλήση της συνάρτησης `reverse()` θα επιστρέψει ένα string όπως το παρακάτω:

      '/polls/3/results/'

  όπου `3` είναι η τιμή του `question.id`. Αυτό το URL θα καλέσει το view με το όνομα `'results'` για να εμφανίσει τη σελίδα (το rendered template, δηλαδή, `polls/detail.html`). Όταν λέμε rendered template ας εξηγήσουμε τι εννοούμε. Είναι πολύ απλό. Μέσα σε ένα template (συνήθως μια HTML σελίδα), συνήθως βάζουμε περιεχόμενο το οποίο αναμένουμε να “γεμίσει” με μια τιμή από κάπου (συνήθως από το view που κάλεσε αυτό το template). Είδαμε κάτι τέτοιο στον Οδηγό 3 όπου στο template `polls/detail.html` βάλαμε την έκφραση {{ choice.choice_text }} μέσα σε ένα `<li></li>` HTML element. Αυτή η τιμή θα “γεμίσει” από το context που θα περάσει το view στο template (μέσω της `render()` φυσικά). Το context δεν είναι τίποτε άλλο παρά ένα dictionary όπου τα keys ονομάζονται context variables. Υπολογίστηκαν κάποιες τιμές στο view και με κάποιον τρόπο θα πρέπει να περαστούν στο template. Όταν το template γεμίσει με τις απαραίτητες τιμές τότε έχει γίνει rendered.

Όπως αναφέρθηκε στον Οδηγό 3, το `request` είναι ένα object της κλάσης `HttpRequest`. Για περισσότερες πληροφορίες σχετικά με τα objects της κλάσης `HttpRequest`, δείτε το άρθρο εγχειρίδιο του request και response.

Όταν κάποιος ψηφίσει στην ερώτηση, τότε το view `vote()` θα ανακατευθύνει το χρήστη στη σελίδα των αποτελεσμάτων για αυτή την ερώτηση. Ας γράψουμε αυτό το view:

polls/views.py

    from django.shortcuts import get_object_or_404, render

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

Αυτό το view είναι σχεδόν πανομοιότυπο με το `detail()` από τον Οδηγό 3. Η μόνη διαφορά είναι το όνομα του template. Θα φτιάξουμε αυτό τον πλεονασμό αργότερα. Μας αρέσει να ακολουθάμε την τακτική DRY (don’t repeat yourself).

Τώρα, δημιουργήστε το template με το όνομα `polls/results.html`:

polls/templates/polls/results.html

    <h1>{{ question.question_text }}</h1>

    <ul>
    {% for choice in question.choice_set.all %}
        <li>{{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li>
    {% endfor %}
    </ul>

    <a href="{% url 'polls:detail' question.id %}">Vote again?</a>

Μεταβείτε στη σελίδα `/polls/1/` και ψηφίστε. Κάθε φορά που ψηφίζετε θα βλέπετε τη σελίδα με τα αποτελέσματα της ερώτησης. Αν κάνετε submit τη φόρμα δίχως να επιλέξετε κάποια απάντηση θα δείτε ένα μήνυμα σφάλματος.

Σημείωση

Ο κώδικας του view `vote()` έχει ένα μικρό προβληματάκι. Πρώτα ρωτάει την database (μέσω της `get()`) για την ύπαρξη του `choice` με το συγκεκριμένο ID και αν βρεθεί επιστρέφει το ανάλογο object που είναι instance της `polls.models.Choice` class. Το object αυτό το ονομάζουμε `selected_choice`. Κατόπιν υπολογίζουμε τη νέα τιμή του `votes` και μετά αποθηκεύουμε τη νέα τιμή (αυξημένη κατά ένα) πίσω στην βάση δεδομένων. Εδώ όμως δημιουργείται πρόβλημα. Αν δύο χρήστες προσπαθήσουν να ψηφίσουν στην ίδια ερώτηση ακριβώς την ίδια χρονική στιγμή, τότε δεν θα δουλέψει όπως πρέπει: Η ίδια τιμή, ας πούμε 42, θα είναι η αρχική (`votes`) και για τους δύο χρήστες. Τότε και στους δύο, η νέα τιμή 43 θα εμφανιστεί ως αποτέλεσμα, αλλά η τιμή 44 θα έπρεπε να είναι η αναμενόμενη (που δεν είναι).

Αυτό ονομάζεται race condition. Αν ενδιαφέρεστε μπορείτε να διαβάσετε την αναφορά στο Avoiding race conditions using F() για να μάθετε πως μπορείτε να αποφύγετε τέτοιες (ακραίες) καταστάσεις.

## Χρήση των generic views: Ο λιγότερος κώδικας είναι καλύτερος

Το view `detail()` (από τον Οδηγό 3) και το view `results()` είναι πολύ απλά και όπως αναφέρθηκε παραπάνω είναι πλεονασμός. Το `index()` view, το οποίο εμφανίζει μια λίστα των πέντε πρώτων ερωτήσεων είναι εξίσου παρόμοιο.

Αυτά τα views αναπαριστούν μια κοινή πρακτική γενικά στο Web development: φέρνουν data από την database ανάλογα τις παραμέτρους που εισήχθησαν στο URL, φορτώνουν ένα template και επιστρέφουν ένα rendered template. Επειδή αυτό είναι τόσο κοινότυπο, το Django προσφέρει μια συντόμευση, η οποία ονομάζεται σύστημα “generic views”.

Τα generic views απλοποιούν αυτές τις τόσο συνηθισμένες τακτικές σε βαθμό που δεν χρειάζεται να γράψετε καθόλου Python κώδικα για να φτιάξετε ένα app.

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

1. Μετατροπή των URLconf.

2. Διαγραφή παλιού κώδικα, αχρησιμοποίητα views.

3. Εισαγωγή σε νέου είδους views, βασισμένα πάνω στα generic views του Django.

Συνεχίστε να διαβάζετε για λεπτομέρειες.

Γιατί αυτή η σύγχυση με τον κώδικα;

Σε γενικές γραμμές, όταν γράφετε ένα Django app, θα καταλάβετε από την αρχή αν θα πρέπει να χρησιμοποιήσετε τα generic views (για την επίλυση του προβλήματος σας), παρά να φτάσετε στο τέλος και μετά να ξαναγράψετε τον κώδικα (όπως κάναμε εμείς τώρα). Εδώ ακολουθήσαμε αυτή την τακτική προκειμένου να σας δείξουμε πως γράφετε ένα view “με το δύσκολο τρόπο” προκειμένου να επικεντρωθείτε σε βασικά concepts.

Θα πρέπει να γνωρίζετε βασικά μαθηματικά προτού χρησιμοποιήσετε το κομπιουτεράκι.

### Βελτιώση του URLconf

Ανοίξτε πρώτα το URLconf αρχείο `polls/urls.py` και αλλάξτε το σε:

polls/urls.py

    from django.conf.urls import url

    from . import views

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

Σημειώστε ότι τα ονόματα των τιμών που “αιχμαλωτίζονται” (μέσα στα regexes) στο δεύτερο και τρίτο URL μοτίβο, έχουν αλλάξει από `<question_id>` σε `<pk>`.

### Βελτίωση των views

Επόμενο βήμα είναι να αφαιρέσουμε τα παλιά (πλέον) `index`, `detail` και `results` views και να χρησιμοποιήσουμε τα generic views του Django. Για να γίνει αυτό, ανοίξτε το αρχείο `polls/views.py` και αλλάξτε το σε:

polls/views.py

    from django.shortcuts import get_object_or_404, render
    from django.http import HttpResponseRedirect
    from django.urls import reverse
    from django.views import generic

    from .models import Choice, Question

    class IndexView(generic.ListView):
        template_name = 'polls/index.html'
        context_object_name = 'latest_question_list'

        def get_queryset(self):
            """Return the last five published questions."""
            return Question.objects.order_by('-pub_date')[:5]

    class DetailView(generic.DetailView):
        model = Question
        template_name = 'polls/detail.html'

    class ResultsView(generic.DetailView):
        model = Question
        template_name = 'polls/results.html'

    def vote(request, question_id):
        ... # same as above, no changes needed.

Χρησιμοποιούμε δύο generic views: Το `ListView` και το `DetailView`. Αντίστοιχα, αυτά τα δύο views απλοποιούν τα concepts της “προβολής μια λίστας από objects” και της “προβολής μιας σελίδας λεπτομερειών για ένα συγκεκριμένου τύπου object.”

- Κάθε generic view χρειάζεται να γνωρίζει το μοντέλο με το οποίο θα συνεργαστεί. Αυτό επιτυγχάνεται με το attribute `model`.

- Το `DetailView` generic view περιμένει μια τιμή ενός primary key (ID, αν θέλετε) η οποία έχει “αιχμαλωτιστεί” στο URL υπό το όνομα `"pk"`. Αυτός είναι ο λόγος που μετονομάσαμε το `question_id` σε `pk`.

Από προεπιλογή, το generic view `DetailView` χρησιμοποιεί ένα template με το όνομα `<appname>/<modelname>_detail.html`. Στην περίπτωση μας θα χρησιμοποιήσει το template με το όνομα `"polls/question_detail.html"`. Μπορούμε να παρακάμψουμε (override) αυτή τη συμπεριφορά, θέτοντας την τιμή του attribute `template_name` σε μια δική μας. Ορίζουμε, επίσης, ένα δικό μας `template_name` και για το view `results` – αυτό εξασφαλίζει ότι το results view και το detail view θα έχουν διαφορετική εμφάνιση όταν γίνουν rendered, παρά το γεγονός ότι και τα δύο είναι τύπου `DetailView`.

Ομοίως, το generic view `ListView` χρησιμοποιεί, εξ ορισμού, ένα template με το όνομα `<appname>/<modelname>_list.html`. Παρακάμπτουμε, και εδώ, τη συμπεριφορά αυτή θέτοντας τη τιμή του attribute `template_name` στο string `"polls/index.html"`, το οποίο έχουμε ήδη υλοποιήσει.

Στα προηγούμενα μέρη του οδηγού, προμηθεύαμε τα templates με τα context variables `question` και `latest_question_list`. Για το `DetailView` η μεταβλητή (variable) `question` παρέχεται αυτόματα στο template – έτυχε, γιατί χρησιμοποιούμε ίδιο όνομα για το μοντέλο μας (`Question`) και το Django γνωρίζει πως να καθορίσει ένα κατάλληλο όνομα για το context variable. Ωστόσο, όσον αφορά το ListView, το αυτοματοποιημένο όνομα που παράγεται για το context variable είναι `question_list`. Για να το παρακάμψουμε αυτό χρησιμοποιούμε το attribute `context_object_name`, δηλώνοντας έτσι ότι θέλουμε να χρησιμοποιήσουμε το `latest_question_list` αντί του άλλου. Μια εναλλακτική προσέγγιση είναι να αλλάξετε τα templates σας και να αντικαταστήσετε τα `latest_question_list` με `question_list` και να μην θέσετε καθόλου το attribute `context_object_name` μέσα στο ListView. Αλλά είναι πιο εύκολο να πείτε στο Django να χρησιμοποιήσει την μεταβλητή που θέλετε παρά να μπείτε στη διαδικασία να αλλάζετε όλα τα templates σας.

Τρέξτε τον server και παίξτε με τη νέα σας εφαρμογή που είναι βασισμένη στα generic views.

Για όλες τις λεπτομέρειες πάνω στα generic views, δείτε το άρθρο εγχειρίδιο (documentation) των generic views.

Όταν είστε εξοικειωμένοι με τις φόρμες και τα generic views διαβάστε το πέμπτο μέρος αυτού του οδηγού για να μάθετε πως να κάνετε τεστ (test) στην εφαρμογή σας.