11. Formulare

“Django provides a range of tools and libraries to help you build forms to accept input from site visitors, and then process and respond to the input.”

Working with forms | Django documentation

Nachdem die Anmeldung im Frontend fertig ist kann das Formular zum Anlegen von Lesezeichen folgen.

11.1. Add URLs for the forms

Zuerst musst du die URLconf der App marcador in mysite/marcador/urls.py um zwei URLs erweitern:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
from django.conf.urls import url


urlpatterns = [
    url(r'^user/(?P<username>[-\w]+)/$', 'marcador.views.bookmark_user',
        name='marcador_bookmark_user'),
    url(r'^create/$', 'marcador.views.bookmark_create',
        name='marcador_bookmark_create'),
    url(r'^edit/(?P<pk>\d+)/$', 'marcador.views.bookmark_edit',
        name='marcador_bookmark_edit'),
    url(r'^$', 'marcador.views.bookmark_list', name='marcador_bookmark_list'),
]

Die Erste, ^create/$, ist rein statisch und führt dazu, dass die entsprechende View über den Pfad /create/ erreichbar ist.

Bei ^edit/(?P<pk>\d+)/$ gibt es einen variablen Anteil, mit dem dieses mal der Primärschlüssel (Primary Key, abgekürzt mit PK) eines Lesezeichens herausgefiltert wird. Der PK wird von Django automatisch jedem Model hinzugefügt, damit jeder Eintrag eindeutig referenziert werden kann. Der Pfad /edit/1/ führt also beispielsweise zum Lesezeichen mit dem PK 1.

Zwischen den statischen Teilen edit/ am Anfang und / am Ende steht die Gruppe (?P<pk>\d+). Sie trifft durch \d+ auf beliebig viele Zahlen zu und speichert sie in der Variable pk, die dann wieder in der View zur Verfügung steht.

11.2. Add the Form

Als nächstes fügst du das Formular hinzu, dazu legst du die Datei mysite/marcador/forms.py an:

1
2
3
4
5
6
7
8
9
from django.forms import ModelForm

from .models import Bookmark


class BookmarkForm(ModelForm):
    class Meta:
        model = Bookmark
        exclude = ('date_created', 'date_updated', 'owner')

Die Formular-Klasse BookmarkForm bezeichnet man auch als ModelForm, da sie von django.forms.ModelForm erbt. ModelForms erzeugen automatisch Formularfelder für jedes Feld im ihnen zugeordneten Model. Die Zuordnung des Models erfolgt über die innere Klasse Meta, die jedes ModelForm besitzen muss.

For security reasons the value owner mustn’t be editable through the form because otherwise it would be possible to foist a bookmark on another user. Therefore owner is added to the list of excluded fields. The same is done with the fields date_created and date_updated because they are set automatically.

11.3. Add the Views

Nun kannst du zwei neue Views erstellen, die das eben erstellte BookmarkForm benutzen. Die Erste dient dazu, neue Lesezeichen zu erzeugen:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from django.shortcuts import get_object_or_404, redirect, render

from .forms import BookmarkForm
from .models import Bookmark


def bookmark_list(request):
    bookmarks = Bookmark.public.all()
    context = {'bookmarks': bookmarks}
    return render(request, 'marcador/bookmark_list.html', context)


def bookmark_user(request, username):
    user = get_object_or_404(User, username=username)
    if request.user == user:
        bookmarks = user.bookmarks.all()
    else:
        bookmarks = Bookmark.public.filter(owner__username=username)
    context = {'bookmarks': bookmarks, 'owner': user}
    return render(request, 'marcador/bookmark_user.html', context)


@login_required
def bookmark_create(request):
    if request.method == 'POST':
        form = BookmarkForm(data=request.POST)
        if form.is_valid():
            bookmark = form.save(commit=False)
            bookmark.owner = request.user
            bookmark.save()
            form.save_m2m()
            return redirect('marcador_bookmark_user',
                username=request.user.username)
    else:
        form = BookmarkForm()
    context = {'form': form, 'create': True}
    return render(request, 'marcador/form.html', context)

Der Decorator @login_required sorgt dafür, dass die View nur erreicht werden kann, wenn man angemeldet ist. Ansonsten wird man automatisch zur Login-Seite weitergeleitet.

The first step is to check if the request was done via POST. If yes, the form is initialized with the POST data.

The form gets validated by using form.is_valid(). If the validation was successful, the new bookmark can be saved with form.save(commit=False). The argument commit=False prevents the ModelForm from saving the bookmark, it just returns the model instance prepared with the validated data. Now the current user is added as the owner and the bookmark is saved. After that form.save_m2m() is called to create the relationships to the Tag models. Finally the user gets redirected to his or her bookmark list.

Falls die Validierung nicht erfolgreich war, oder falls es sich um einen GET-Request handelt, wird das Formular an das Template übergeben, wo es inklusive eventueller Fehlermeldungen gerendert werden kann.

Two values are passed to the template as context variables. The dictionary key form contains the instance of BookmarkForm. create is a boolean value and is used to determine if the template is used to create or edit a bookmark because it is used in both views.

Die zweite View dient dem Editieren von Lesezeichen:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from django.core.exceptions import PermissionDenied
from django.shortcuts import get_object_or_404, redirect, render

from .forms import BookmarkForm
from .models import Bookmark


def bookmark_list(request):
    bookmarks = Bookmark.public.all()
    context = {'bookmarks': bookmarks}
    return render(request, 'marcador/bookmark_list.html', context)


def bookmark_user(request, username):
    user = get_object_or_404(User, username=username)
    if request.user == user:
        bookmarks = user.bookmarks.all()
    else:
        bookmarks = Bookmark.public.filter(owner__username=username)
    context = {'bookmarks': bookmarks, 'owner': user}
    return render(request, 'marcador/bookmark_user.html', context)


@login_required
def bookmark_create(request):
    if request.method == 'POST':
        form = BookmarkForm(data=request.POST)
        if form.is_valid():
            bookmark = form.save(commit=False)
            bookmark.owner = request.user
            bookmark.save()
            form.save_m2m()
            return redirect('marcador_bookmark_user',
                username=request.user.username)
    else:
        form = BookmarkForm()
    context = {'form': form, 'create': True}
    return render(request, 'marcador/form.html', context)


@login_required
def bookmark_edit(request, pk):
    bookmark = get_object_or_404(Bookmark, pk=pk)
    if bookmark.owner != request.user and not request.user.is_superuser:
        raise PermissionDenied
    if request.method == 'POST':
        form = BookmarkForm(instance=bookmark, data=request.POST)
        if form.is_valid():
            form.save()
            return redirect('marcador_bookmark_user',
                username=request.user.username)
    else:
        form = BookmarkForm(instance=bookmark)
    context = {'form': form, 'create': False}
    return render(request, 'marcador/form.html', context)

Hier wird mit Hilfe des Primary Key pk das entsprechende Lesezeichen aus der Datenbank geholt oder der HTTP Status Code 404 zurückgegeben, wenn kein Lesezeichen mit diesem PK existiert. Ist der aktuelle Benutzer weder der Besitzer des Lesezeichens (bookmark.owner != request.user), noch Mitglied des Administratorenteams (not request.user.is_superuser), wird ihm der Zugriff verweigert und der HTTP Status Code 403 zurückgegeben.

Ansonsten ist der Ablauf wie in bookmark_create, nur dass bei der Initialisierung des Formulars die Instanz des Lesezeichens übergeben wird (instance=bookmark). Dadurch sind alle Formularfelder mit den alten Werten vorausgefüllt. Sind POST-Daten vorhanden, dann überschreiben diese die Werte aus der bookmark-Instanz.

In addition the key create in the context dictionary is set to False because this view is only used to edit existing bookmarks.

11.4. Add the Templates

Jetzt fügst du das Template für das Formular in mysite/marcador/templates/marcador/form.html hinzu:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
{% extends "base.html" %}
{% load crispy_forms_tags %}

{% block title %}
  {% if create %}Create{% else %}Edit{% endif %} bookmark
{% endblock %}

{% block heading %}
  <h2>
    {% if create %}
      Create bookmark
    {% else %}
      Edit bookmark
    {% endif %}
  </h2>
{% endblock %}

{% block content %}
  {% if create %}
    {% url "marcador_bookmark_create" as action_url %}
  {% else %}
    {% url "marcador_bookmark_edit" pk=form.instance.pk as action_url %}
  {% endif %}
  <form action="{{ action_url }}" method="post" accept-charset="utf-8">
    {{ form|crispy }}
    {% csrf_token %}
    <p><input type="submit" class="btn btn-default" value="Save"></p>
  </form>
{% endblock %}

The variable create is used in the title and heading blocks to display different text depending on whether the template is used to create or edit a bookmark. The URL for the form’s action attribute is also set according to the value of create and assigned to action_url. Note that the current bookmark fetched from the database has been assigned to form.instance and can be used to create the edit URL for this bookmark.

Now you can add a link to create new bookmarks to the file mysite/templates/toggle_login.html. The corresponding line is highlighted:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
{% if user.is_authenticated %}
<ul class="nav navbar-nav navbar-right">
  <li><a href="{% url "marcador_bookmark_create" %}">Create bookmark</a></li>
  <li class="dropdown">
    <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
        aria-expanded="false">{{ user.username }} <span class="caret"></span></a>
    <ul class="dropdown-menu" role="menu">
      <li><a href="{% url "marcador_bookmark_user" user.username %}">
          My bookmarks</a></li>
      <li><a href="{% url "mysite_logout" %}">Logout</a></li>
    </ul>
  </li>
</ul>
{% else %}

The last step is to extend the template mysite/marcador/templates/marcador/bookmark.html at the end with the following lines:

15
16
17
18
19
20
21
<br>by <a href="{% url "marcador_bookmark_user" bookmark.owner.username %}">
    {{ bookmark.owner.username }}</a>
{{ bookmark.date_created|timesince }} ago
{% if bookmark.owner == user or user.is_superuser %}
  <br><a class="btn btn-default btn-xs" role="button"
      href="{% url "marcador_bookmark_edit" bookmark.pk %}">Edit bookmark</a>
{% endif %}

So wird jedem angemeldeten Benutzer unter seinen eigenen Lesezeichen der Button zum Bearbeiten angezeigt.

11.5. Test the form

Now load the main page and click on the link to create a new bookmark. You should see this form:

Create Bookmark Form

It displays all fields we have configured in the form. Tags can’t be added, but you can build a second form to do this.

If you click on one the links to edit a bookmark you will see this edit form:

Edit Bookmark Form