Django – #11 – Pierwsza aplikacja – Formularz

Wprowadzenie.

Jednym z podstawowych elementów stron internetowych są formularze, które dają możliwość przesyłania danych do serwera. W tym wpisie przedstawię i wyjaśnię w jaki sposób stworzyć formularz i jak go obsłużyć w Django. 

Zakres artykułu.

  • Tworzenie aplikacji Django – Modyfikacja modelu
  • Tworzenie aplikacji Django – Tworzenie formularza

Tworzenie aplikacji Django – Modyfikacja modelu

Załóżmy, że na naszej stronie z recenzjami mamy stworzyć mechanizm umożliwiający ocenianie obejrzanych filmów przez osoby wchodzące na naszą stronę. W tym celu dodajmy nowy model UserRating tak jak poniżej.

class UserRating(models.Model):
    rate = models.DecimalField(max_digits=4, decimal_places=2, default=0)
    number_of_ratings = models.IntegerField(default=0)
    last_rate_date = models.DateField(auto_now=True)

    movie = models.ForeignKey(WatchedMovies, on_delete=models.CASCADE)

    def __str__(self):
        return self.movie

Po stworzeniu modelu przeprowadźmy migrację do bazy danych. Dla przypomnienia są to polecenia python3 manage.py makemigrations myFirstApp i python3 manage.py migrate

Następnie zarejestrujmy model w panelu administratora, wbudowywując model UserRating w model WatchedMovies

from django.contrib import admin

# Register your models here.

from .models import MovieGenres, MovieActors, WatchedMovies, MovieReview, UserRating

class MovieReviewInline(admin.TabularInline):
    model = MovieReview

class UserRatingInline(admin.TabularInline):
    model = UserRating
    extra = 1

class WatchedMoviesAdmin(admin.ModelAdmin):
    inlines = [ MovieReviewInline,  UserRatingInline]

admin.site.register(MovieGenres)
admin.site.register(MovieActors)
admin.site.register(WatchedMovies, WatchedMoviesAdmin)

W klasie UserRatingInline dodałem jeszcze zmienną extra = 1 co oznacza, że w panelu administratora będzie pokazywał się dodatkowo tylko jeden rekord dla UseRating.

Tworzenie aplikacji Django – Tworzenie formularza

Gdy już mamy przygotowaną bazę danych możemy przejść do właściwej części wpisu. W pierwszej kolejności zmodyfikujemy nasze widoki w pliku views.py, gdzie w widoku detail prześlemy do szablonu nowe dane z bazy danych. Dodatkowo stworzymy nowy widok rate, który będzie odpowiedzialny za obliczenia, zapisanie zmian oraz przekierowanie na stronę ze szczegółami. Po modyfikacjach plik views.py wygląda następująco. 

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

from .models import WatchedMovies, UserRating

def error_404_my_view(request, exception):
	return render(request, 'myFirstApp/error404.html')

def index(request):
	movies = WatchedMovies.objects.order_by('title')
	context = {
		'movies': movies,
	}
	return render(request, 'myFirstApp/index.html', context) 

def myNextPage(request):
	return HttpResponse("To jest moja następna strona!")

def detail(request, watchedMovies_id):
	movie = get_object_or_404(WatchedMovies, pk=watchedMovies_id)
	userRatings = UserRating.objects.filter(movie=watchedMovies_id)
	context = {
		'movie': movie,
		'userRatings': userRatings,
	}
	return render(request, 'myFirstApp/detail.html', context)

def rate(request, watchedMovies_id):
	movie = get_object_or_404(WatchedMovies, pk=watchedMovies_id)
	userRatings = UserRating.objects.filter(movie=watchedMovies_id)
	ur = userRatings[0]
	ur.rate = str((float(ur.rate) * ur.number_of_ratings + float(request.POST['movie_rating_radio']))/(ur.number_of_ratings + 1))
	ur.number_of_ratings = ur.number_of_ratings + 1
	ur.save()
	return HttpResponseRedirect(reverse('myFirstApp:myDetail', args=(watchedMovies_id,)))

Omówię teraz nowo użyte elementy.

1) userRatings = UserRating.objects.filter(movie=watchedMovies_id)
W tym wierszu tworzymy obiekt userRatings klasy QuerySet, który będzie przechowywał dane z modelu UserRating dla obejrzanego filmu z id watchedMovies_id.

2) ur = userRatings[0]
Tworzymy obiekt, który będzie odwoływał się do rekordu zerowego. W obiekcie userRatings jest i tak zawarty tylko jeden rekord z bazy danych, lecz jak byśmy chcieli w przyszłości odróżnić jak głosują na przykład użytkownicy zalogowani od niezalogowanych lub utworzyć specjalne grupy osób oceniających to bez problemu możemy dodać kolejne rekordy i przypisać je do tego samego filmu.  

3) ur.save()
Jest to metoda, która zapisuje wprowadzone zmiany w bazie danych.

4) request.POST[’movie_rating_radio']
W taki sposób odwołujemy się do zmiennych przesłanych z formularza. Nazwa movie_rating_radio odnosi się do nazwy elementu z formularza. Przykład wygląda następująco name=”movie_rating_radio”

5) return HttpResponseRedirect(reverse(’myFirstApp:myDetail', args=(watchedMovies_id,)))
W tym wierszu mamy dwa nowe wyrażenia. Piersze to klasa HttpResponseRedirect którą trzeba zaimportować z django.http oraz drugie wyrażenie to funkcja reverse, którą importujemy z django.urls. Klasa HttpResponseRedirect zwraca kod przekierowania 302, natomiast argument stanowi adres URL, na który będziemy przekierowywani. Stosowanie przekierowywania zamiast zwykłej odpowiedzi nie jest koniecznością, lecz jest dobrą praktyką tworzenia stron. W argumencie HttpResponseRedirect zastosowaliśmy funkcję reverse, która umożliwia nam umieszczanie zautomatyzowanych ścieżki na podstawie nazwy, tak jak było to w przypadku szablonów. Funkcja revesre przyjmuje także argumenty, które następnie możemy wykorzystać w widoku.

Przejdźmy teraz do zmapowania nowo dodanego widoku rate w pliku urls.py naszej aplikacji.

from django.urls import path

from . import views

app_name = 'myFirstApp'

urlpatterns = [
    path('', views.index, name='index'),
    path('myPage/', views.myNextPage, name='myNextPage'),
    path('<int:watchedMovies_id>/details/', views.detail, name='myDetail'),
    path('<int:watchedMovies_id>/rate/', views.rate, name='myRate'),
    ]

Budowa kolejnej ścieżki nie wymaga większego omawiania ponieważ jest analogiczna do ścieżki dla szczegółów. 

W kolejnym kroku zmodyfikujemy szablon detail.html, tak aby pokazywał średnią ocenę użytkowników, liczbę oddanych głosów oraz datę ostatniego oddania głosu. Następnie dodamy formularz, przy pomocy którego będziemy oddawali nasze głosy (na tą chwilę pominiemy zablokowanie możliwości oddawania kilku głosów przez tego samego użytkownika). Zmodyfikowany plik detail.html wygląda następująco.  

{% extends "myFirstApp/base.html" %}

{% block titlepage %}
    Moja strona
{% endblock %}

{% block mybody %}
    <h1> Szczegóły dla filmu  "{{ movie.title }}"</h1>
    {% if movie %}
        <ul>
            <li>Gatunek - {{ movie.genre }}</li>
            <li>Data premiery - {{ movie.release_date }}</li>
            <li>Moja ocena - {{ movie.rate }}</li>
            </br><h3>Obsada</h3>
            {% for star in movie.stars.all %}
                <li>{{ star }}</li>
            {% endfor %}
        </ul>
        </br><h3>Oceny użytkowników</h3>
        <ul>
            <li>Średnia ocen - {{ userRatings.0.rate }}</li>
            <li>Liczba ocen - {{ userRatings.0.number_of_ratings }}</li>
            <li>Data ostatniej oceny - {{ userRatings.0.last_rate_date }}</li>
        </ul>
        <form action="{% url 'myFirstApp:myRate' movie.id %}" method="post">
            {% csrf_token %}
            <h3>Oceń film</h3>
            <input type="radio" id="rate_1" name="movie_rating_radio" value="1">
            <label for="rate_1">1</label></br>
            <input type="radio" id="rate_2" name="movie_rating_radio" value="2">
            <label for="rate_2">2</label></br>
            <input type="radio" id="rate_3" name="movie_rating_radio" value="3">
            <label for="rate_3">3</label></br>
            <input type="radio" id="rate_4" name="movie_rating_radio" value="4">
            <label for="rate_4">4</label></br>
            <input type="radio" id="rate_5" name="movie_rating_radio" value="5">
            <label for="rate_5">5</label></br>
            <input type="submit" value="Oceń">
        </form>
    {% else %}
        <p>Brak filmów</p>
    {% endif %}
{% endblock %}

Przy tym kodzie ponownie na chwilę się zatrzymajmy ponieważ warto omówić kilka zagadnień. 

1) {{ userRatings.0.rate }}
Powyższe wyrażenie pokazuje w jaki sposób uzyskać dostęp do kolejnych rekordów. Jak pamiętamy dla plików .py dostęp uzyskujemy poprzez zastosowanie nawiasów kwadratowych userRatings[0] natomiast w przypadku szablonów, należy użyć kropki po której wpisujemy interesujący nas indeks. Iterować taki obiekt możemy też na przykład przy pomocy pętli for {% for star in movie.stars.all %}.

2) {% csrf_token %}
Tag ten ma za zadanie chronić nas przed tak zwanym atakiem cross-site request forgeries i należy go używkać w każdym formularzu, gdzie stosujemy metodę POST do przesyłania danych.

 

Na koniec przetestujmy jak działa nasza aplikacja. W pierwszej kolejności w panelu administratora dodajmy do obecnych filmów ocenę oraz ustawmy, że zagłosował jeden użytkownik. Następnie przejdźmy do strony ze szczegółami jednego z filmów. W moim przypadku jest to adres http://127.0.0.1:8000/myFirstApp/3/details/. Strona na chwilę obecną wygląda następująco.

W celu prostego przetestowania strony ustawiłem początkową ocenę na 5 oraz że zagłosował jednen użytkownik. Następnie oddam głos na 1 co spowoduje, że powinienem dostać średną ocen 3, następnie oddam głos na 3, tak by wynik z średniej ocen się nie zmienił, a jedynie żeby uległa zmianie liczba oddanych głosów. Na koniec oddam głos na 5 co powinno dać mi wynik (5+1+3+5)/4 = 3,5.

Autor artykułu
Dominik Bednarski

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *