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.