Django: Create, Update, Delete rodinių klasės - robotautas/kursas GitHub Wiki

Create, Update, Delete rodinių klasės

Š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 klasė

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 %}

DetailView klasė

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%}

CreateView klasė

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)    

UpdateView klasė

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})

DeleteView klasė

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>

403, 404, 500 klaidų šablonai

Į 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 %}

Užduotis

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)

Papildoma užduotis

  • 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.

Atsakymas

⚠️ **GitHub.com Fallback** ⚠️