Django: Create, Update, Delete rodinių klasės - robotautas/kursas GitHub Wiki
Šioje paskaitoje prisiminsime ListView, DetailView. Ir papildomai išmoksime kurti class-based view'sus CreateView, UpdateView, DeleteView.
Vartotojo laukas is_staff = True reiškia, kad jis gali prisijungti prie admin svetainės. Mūsų atveju, tai nurodys ir tai, kad šis vartotojas yra darbuotojas/administratorius. Prisijungęs (ne per admin) jis galės administruoti knygų egzempliorius: visus juos peržiūrėti. Taip pat kurti, redaguoti, trinti per svetainę egzempliorius.
ListView jau žinome. Jį papildome įtraukdami paveldimą UserPassesTestMixin klasę. Ji į rodinį praleis tik tuo atveju, jei vartotojas atitinka tam tikrus reikalavimus (tam skirtas test_func metodas, kurį reikia perrašyti, grąžina True):
from django.views import generic
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
class BookInstanceListView(LoginRequiredMixin, UserPassesTestMixin, generic.ListView):
model = BookInstance
context_object_name = "instances"
template_name = "instances.html"
def test_func(self):
return self.request.user.is_staff
Sukuriame path, faile library/urls:
path("instances/", views.BookInstanceListView.as_view(), name="instances"),
Sukuriame html, faile instances.html:
{% extends "base.html" %}
{% block "title" %}Knygų egzemplioriai{% endblock%}
{% block "content" %}
{% for instance in instances %}
<p>{{ instance.book }} - {{ instance.uuid }}
<span class="{% if instance.is_overdue %}text-danger{% endif %}">({{ instance.due_back }}, {{ instance.get_status_display }})</span>
</p>
{% endfor%}
{% endblock%}
Į svetainės meniu įdedame Egzempliorių administravimo nuorodą, kuri matoma tik darbuotojui. base.html:
{% if user.is_staff %}
<li class="nav-item">
<a class="nav-link" href="{% url 'instances' %}">Administravimas</a>
</li>
{% endif %}
Padarome, kad paspaudus ant egzemplioriaus, užeitų į jo išsamų aprašymą:
Papildome views.py:
from django.views import generic
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
class BookInstanceDetailView(LoginRequiredMixin, UserPassesTestMixin, generic.DetailView):
model = BookInstance
context_object_name = "instance"
template_name = "instance.html"
def test_func(self):
return self.request.user.is_staff
Sukuriame path, faile library/urls:
path("instances/<int:pk>", views.BookInstanceDetailView.as_view(), name="instance"),
Sukuriame html. Jei objekto pavadinimas (context_object_name) nenurodytas, jo laukus pasiekiame per "object.". Faile instance.html:
{% extends "base.html" %}
{% block "title" %}Knygų egzempliorius{% endblock%}
{% block "content" %}
<h1 class="display-5">Knyga "{{ instance.book.title }}"</h1>
<p><strong>UUID: </strong>{{ instance.uuid }}</p>
<p><strong>Skaitytojas: </strong>
<img class="rounded-circle account-img" style="width: 30px" src="{{ instance.reader.profile.photo.url }}">
{{ instance.reader }}
</p>
<p><strong>Bus prieinama: </strong><span class="{% if instance.is_overdue %}text-danger{% endif %}">{{ instance.due_back }}</span></p>
<p><strong>Būsena: </strong>{{ instance.get_status_display }}</p>
{% endblock%}
Dedame nuorodą į vieno knygos egzemplioriaus rodinį faile instances.html:
{% extends "base.html" %}
{% block "title" %}Knygų egzemplioriai{% endblock%}
{% block "content" %}
{% for instance in instances %}
<a href="{% url 'instance' instance.pk %}">
<p>{{ instance.book }} - {{ instance.uuid }}
<span class="{% if instance.is_overdue %}text-danger{% endif %}">({{ instance.due_back }}, {{ instance.get_status_display }})</span>
</p>
</a>
{% endfor%}
{% endblock%}
Dabar padarysime, kad prisijungęs darbuotojas galėtų pridėti (sukurti) naują knygos egzempliorių:
Iš pradžių sukuriame formą forms.py:
from .models import BookInstance
class InstanceCreateUpdateForm(forms.ModelForm):
class Meta:
model = BookInstance
fields = ['book', 'reader', 'due_back', 'status']
widgets = {'due_back': forms.DateInput(attrs={'type': 'date'})}
Atkreipkite dėmesį, kaip nustatyta, kad kad formoje, įvedant datą, būtų matomas kalendorius (laikrodis), kad vartotojui būtų patogiau įvesti datą. Jei lauke norite nustatyti DateTime lauką, tai yra ir datą ir laiką, DateInput klasėje input_type reikšmę pakeiskite iš 'date' į "datetime-local".
Taip pat svarbu, kad tiek modelio kūrimui, tiek redagavimui galima naudoti tą pačią formą ir tą patį html šabloną.
Sukuriame naują view'są views.py:
from .forms import InstanceCreateUpdateForm
from django.urls import reverse_lazy
class BookInstanceCreateView(LoginRequiredMixin, UserPassesTestMixin, generic.CreateView):
model = BookInstance
template_name = "instance_form.html"
form_class = InstanceCreateUpdateForm
# fields = ['book', 'reader', 'due_back', 'status']
success_url = reverse_lazy('instances')
def test_func(self):
return self.request.user.is_staff
Beje, jei formoje nebūtų datos pasirinkimo, galėtume atskiros formos forms.py ir nekurti, o tiesiog nurodyti fields = parametrą views'e (dabar - užkomentauotas).
Sukuriame path, faile library/urls:
path("instances/create", views.BookInstanceCreateView.as_view(), name="instance_create"),
Sukuriame html, faile instance_form.html:
{% extends "base.html" %}
{% block "title" %}Egzemplioriaus kūrimas/redagavimas{% endblock%}
{% block "content" %}
<div class="content-section">
<form method="POST">
{% csrf_token %}
{% load crispy_forms_tags %}
<legend class="border-bottom mb-4">Knygos Egzemplioriaus kūrimas/redagavimas</legend>
{{ form | crispy }}
<div class="form-group">
<button class="btn btn-outline-info" type="submit">Išsaugoti</button>
</div>
</form>
</div>
{% endblock%}
Įdedame mygtuką į instances.html:
<a class="btn btn-secondary btn-sm mt-1 mb-1" href="{% url 'instance_create' %}">Naujas egzempliorius</a>
PAPILDOMAI: Jei sukūrimo metu modeliui reikėtų priskirti prisijungusį vartotoją, reikės perrašyti ir form_valid funkciją, pvz:
views.py:
def form_valid(self, form):
form.instance.user = self.request.user
form.save()
return super().form_valid(form)
PAPILDOMAI: Jei CreateView view'e prireiktų į formą įdėti kintamąjį (id lauką) iš URL adreso, galime padaryti pavyzdžiui taip:
views.py:
def form_valid(self, form):
form.instance.reader = User.objects.get(pk=self.kwargs['pk'])
return super().form_valid(form)
Išsamiame egzemplioriaus aprašyme įdėsime mygtuką, kuris leis paredaguoti egzempliorių.
Iš pradžių aprašome rodinį faile views.py:
class BookInstanceUpdateView(LoginRequiredMixin, UserPassesTestMixin, generic.UpdateView):
model = BookInstance
template_name = "instance_form.html"
form_class = InstanceCreateUpdateForm
# fields = ['book', 'reader', 'due_back', 'status']
# success_url = reverse_lazy('instances')
def get_success_url(self):
return reverse("instance", kwargs={"pk": self.object.pk})
def test_func(self):
return self.request.user.is_staff
Kaip minėta aukščiau, atskiros formos ir html šablono nenaudosime. Panaudosime tuos pačius iš CreateView.
Sukuriame path, faile library/urls:
path("instances/<int:pk>/update", views.BookInstanceUpdateView.as_view(), name="instance_update"),
Įdedame atnaujinimo nuorodą į failą instance.html:
<div>
<a class="btn btn-secondary btn-sm mt-1 mb-1" href="{% url 'instance_update' instance.pk %}">Redaguoti</a>
</div>
PAPILDOMAI: Jei reikia, kad modelį leistų atnaujinti tik to modelio savininkui, galima test_func() perrašyti pavyzdžiui taip:
def test_func(self):
return self.get_object().user == self.request.user
PAPILDOMAI: Pavyzdžiui, jei paredagavus užsakymo eilutę (prekę, kiekius), norėtume grįžti į tą patį užsakymą (ne į eilutę), tuomet į views.py rodinį reikėtų įdėti:
def get_success_url(self):
return reverse("order", kwargs={"pk": self.get_object().order.pk})
Galiausiai padarome, kad darbuotojas (ne per admin) galėtų ištrinti knygos egzempliorių.
Naujas views'as views.py:
class BookInstanceDeleteView(LoginRequiredMixin, UserPassesTestMixin, generic.DeleteView):
model = BookInstance
template_name = "instance_delete.html"
context_object_name = "instance"
success_url = reverse_lazy('instances')
def test_func(self):
return self.request.user.is_staff
Sukuriame path, faile library/urls:
path("instances/<int:pk>/delete", views.BookInstanceDeleteView.as_view(), name="instance_delete"),
Sukuriame failą instance_delete.html:
{% extends "base.html" %}
{% block "title" %}Egzemplioriaus trynimas{% endblock %}
{% block "content" %}
<div class="content-section">
<form method="POST">
{% csrf_token %}
{% load crispy_forms_tags %}
<fieldset class="form-group">
<legend class="border-bottom mb-4">Ištrinti egzempliorių</legend>
<h2>Ar tikrai norite ištrinti knygos egzempliorių {{ instance.uuid }} ("{{ instance.book.title }}")?</h2>
<div class="form-group">
<button class="btn btn-outline-danger" type="submit">Taip, ištrinti</button>
<a class="btn btn-outline-secondary" href="{% url 'instance' instance.pk %}">Atšaukti</a>
</div>
</fieldset>
</form>
</div>
{% endblock %}
Įdedame trinimo nuorodą prie redagavimo mygtuko į failą instance.html:
<div>
<a class="btn btn-secondary btn-sm mt-1 mb-1" href="{% url 'instance_update' instance.pk %}">Redaguoti</a>
<a class="btn btn-danger btn-sm mt-1 mb-1" href="{% url 'instance_delete' instance.pk %}">Ištrinti</a>
</div>
Į templates galime įdėti papildomus šablonus, kurie bus rodomi esant 403, 404, 500 klaidoms. Pavyzdžiui, kai į rodinį bandys prisijungti vartotojas, kuris nepraėjo test_func() funkcijos:
403.html
{% extends "base.html" %}
{% block "title" %}403 - Forbidden{% endblock %}
{% block "content" %}
<div class="container mt-5 text-center">
<h1 class="display-4 text-danger">403 - Forbidden</h1>
<p>You don't have permission to see this page.</p>
<a href="{% url 'login' %}" class="btn btn-primary">Login</a>
</div>
{% endblock %}
404.html
{% extends "base.html" %}
{% block "title" %}404 - Page Not Found{% endblock %}
{% block "content" %}
<div class="container mt-5 text-center">
<h1 class="display-4 text-danger">404 - Page Not Found</h1>
<p>The page you are looking for does not exist.</p>
<a href="{% url 'login' %}" class="btn btn-primary">Login</a>
</div>
{% endblock %}
500.html
{% extends "base.html" %}
{% block "title" %}500 - Server Error{% endblock %}
{% block "content" %}
<div class="container mt-5 text-center">
<h1 class="display-4 text-danger">500 - Server Error</h1>
<p>An unexpected server error occurred. Please try again later.</p>
<a href="{% url 'login' %}" class="btn btn-primary">Login</a>
</div>
{% endblock %}
Tęsti kurti Django užduotį – Autoservisas:
- Jei reikia, perdaryti vartotojo užsakymų puslapius į ListView ir DetailView klases.
- Padaryti, kad prisijungęs vartotojas galėtų kurti naujus užsakymus (be eilučių, tik pasirinkęs automobilį ir terminą). Panaudoti CreateView
- Padaryti, kad prisijungęs vartotojas galėtų redaguoti savo užsakymus. Panaudoti UpdateView
- Padaryti, kad prisijungęs vartotojas galėtų ištrinti savo užsakymus. Panaudoti DeleteView
- Padaryti, kuriant arba redaguojant užsakymą, datą ir laiką leistų pasirinkti kalendoriuje/laikrodyje (datetimepicker)
- Padaryti, kad vartotojas galėtų savo užsakyme įvesti eilutes (pasirinktas paslaugas ir kiekius). Papildomai - padaryti, kad šias eilutes galima būtų redaguoti/ištrinti.