Menggunakan mixin dengan tampilan berdasarkan-kelas¶
Hati-hati
Ini adalah sebuah topik lanjut. Sebuah pengetahuan dari Django's class-based views disarankan sebelum menjelajahi teknik-teknis ini.
Tampilan berdasarkan-kelas siap-pakai Django menyediakan banyak fungsionalitas, tetapi beberapa dari itu anda mungkin ingin menggunakannya terpisah. Sebagai contoh, anda mungkin ingin menulis sebuah tampilan yang membangun cetakan untuk membuat tanggapan HTTP, tetapi anda tidak dapat menggunakan TemplateView
; mungkin anda butuh membangun cetakan hanya pada POST
, dengan GET
melakukan sesuatu lain seluruhnya. Selagi anda dapat menggunakan TemplateResponse
langsung, ini akan kemungkinan hasil di kode ganda.
Untuk alasan ini, Django juga menyediakan sejumlah mixin yang menyediakan fungsionalitas diskrit lebih. Pembangunan cetakan, sebagai contoh, dienkapsulasi di TemplateResponseMixin
. Dokumentasi acuan Django mengandung full documentation of all the mixins.
Tanggapan koteks dan cetakan¶
Dua mixin pusat disediakan yang membantu dalam antarmuka tetap untuk bekerja dengan cetakan di tampilan berdasarkan-kelas.
TemplateResponseMixin
Setiap tampilan siap pakai yang mengembalikan
TemplateResponse
akan memanggil metoderender_to_response()
yangTemplateResponseMixin
sediakan. Kebanyakan dari waktu ini akan dipanggil untuk anda (sebagai contoh, itu dipanggil oleh metodeget()
diterapkan oleh keduaTemplateView
andDetailView
); demikian pula, itu tidak mungkin anda butuh menimpa itu, meskipun jika anda ingin tanggapan anda mengembalikan sesuatu tidak dibangun melalui cetakan Django kemudian anda akan ingin melakukan itu. Sebagai contoh dari ini, lihat JSONResponseMixin example.render_to_response()
itself callsget_template_names()
, which by default will look uptemplate_name
on the class-based view; two other mixins (SingleObjectTemplateResponseMixin
andMultipleObjectTemplateResponseMixin
) override this to provide more flexible defaults when dealing with actual objects.ContextMixin
Every built in view which needs context data, such as for rendering a template (including
TemplateResponseMixin
above), should callget_context_data()
passing any data they want to ensure is in there as keyword arguments.get_context_data()
returns a dictionary; inContextMixin
it returns its keyword arguments, but it is common to override this to add more members to the dictionary. You can also use theextra_context
attribute.
Membangun tampilan berdasarkan-kelas umum Django¶
Mari kita lihat bagaimana tampilan berdasarkan-kelas umum Django adalah membangun dari mixin disediakan fungsionalitas diskrit. Kami akan mempertimbangkan DetailView
, yang membangun tampilan "detail" dari sebuah obyek, dan ListView
, yang akan membangun sebuah daftar dari obyek, khususnya dari queryset, dan pilihannya memberi nomor mereka. Ini akan memperkenalkan kita untuk empat mixin yang diantara mereka menyediakan fungsionalitas berguna dengan antara obyek Django tunggal, atau banyak obyek.
Ada juga mixin terlibat di tampilan sunting umum (FormView
, dan tampilan model-tertentu CreateView
, UpdateView
dan DeleteView
), dan di tampilan umum berdasarkan-tanggal. Ini dicakupi di mixin reference documentation.
DetailView
: bekerja dengan obyek tunggal Django¶
Untuk menunjukkan rincian dari sebuah obyek, kami dasarnya butuh melakukan dua hal: kami butuh mencari obyek dan kemudian kami butuh membuat TemplateResponse
dengan cetakan yang cocok, dan obyek itu sebagai konteks.
Untuk mendapatkan obyek, DetailView
bergantung pada SingleObjectMixin
, yang menyediakan sebuah metode get_object()
yang mencari tahu berdasarkan obyek pada URL dari permintaan (itu sepertinya untuk argumen kata kunci pk
dan slug
seperti dinyatakan di URLConf, dan mencari obyek antara dari atribut model
pada tampilan, atau atribut queryset
jika itu disediakan). SingleObjectMixin
juga menimpa get_context_data()
, yang digunakan terhadap semua tampilan siap-pakai Django untuk memasok data konteks untuk membangun cetakan.
Untuk kemudian sebuah TemplateResponse
, DetailView
menggunakan SingleObjectTemplateResponseMixin
, yang memperpanjang TemplateResponseMixin
, menimpa get_template_names()
seperti diobrolkan diatas. Itu sebenarnya menyediakan kumpulan cukup canggih pilihan, tetapi satu utama yang paling orang akan gunakan adalah <app_label>/<model_name>_detail.html
. Bagian _detail
dapat dirubah dengan mengatur template_name_suffix
pada subkelas atau sesuatu lain. (Sebagai contoh, generic edit views menggunakan _form
untuk membuat dan memperbaharui tampilan, dan _confirm_delete
untuk tampilan menghapus.)
ListView
: bekerja dengan banyak obyek Django¶
Daftar dari obyek mengikuti kurang lebih pola sama: kami butuh sebuah (kemungkinan diberikan nomor) daftar obyek, khususnya sebuah QuerySet
, dan kemudian kami butuh membuat sebuah TemplateResponse
dengan cetakan cocok menggunakan daftar itu dari obyek.
Untuk mendapatkan objek, ListView
menggunakan MultipleObjectMixin
, yang menyediakan get_queryset()
dan paginate_queryset()
. Berbeda dengan SingleObjectMixin
, tidak perlu untuk mengunci bagian dari URL untuk mengetahui kumpulan permintaan yang akan digunakan, jadi awalan menggunakan queryset
atau model
pada kelas tampilan. Alasan umum untuk mengesampingkan get_queryset()
di sini adalah untuk memvariasikan objek secara dinamis, seperti bergantung pada pengguna saat ini atau untuk mengecualikan kiriman di masa mendatang untuk blog.
MultipleObjectMixin
juga menimpa get_context_data()
untuk menyertakan variabel konteks sesuai untuk penomoran halaman (menyediakan contoh jika penomoran halaman ditiadakan). Itu bergantung pada 11object_list`` dilewatkan di sebuah argumen kata kunci, yang ListView
mengatur untuk itu.
Untuk membuat sebuah TemplateResponse
, ListView
kemudian menggunakan MultipleObjectTemplateResponseMixin
; seperti SingleObjectTemplateResponseMixin
diatas, ini menimpa get_template_names()
untuk menyediakan a range of options
, yang paling digunakan-umum berwujud <app_label>/<model_name>_list.html
, dengan bagian _list
kembali diambil dari atribut template_name_suffix
. (Tampilan umum berdasarkan tanggal menggunakan akhiran seperti _archive
, _archive_year
dan sebagainya untuk menggunakan cetakan berbeda untuk beragam tampilan daftar berdasarkan-tanggal khusus.)
Menggunakan mixin tampilan berdasarkan-kelas Django¶
Now we've seen how Django's generic class-based views use the provided mixins, let's look at other ways we can combine them. We're still going to be combining them with either built-in class-based views, or other generic class-based views, but there are a range of rarer problems you can solve than are provided for by Django out of the box.
Peringatan
Tidak semua mixin dapat digunakan bersama-sama, dan tidak semua tampilan berdasarkan kelas umum dapat digunakan dengan semua mixin lain. Disini kami menghadirkan beberapa contoh yang melakukan pekerjaan; jika anda ingin membawa bersama-sama fungsionalitas lain kemudian anda akan harus mempertimbangkan interaksi diantara atribut dan metode yang tumpang tindih diantara kelas-kelas berbeda anda sedang gunakan, dan bagaimana method resolution order akan mempengaruhi versi mana dari metode akan dipanggil di urutan apa.
Acuan dokumentasi untuk class-based views dan class-based view mixins Django akan membantu anda dalam memahami atribut dan metode mana mungkin menyebabkan pertentangan diantara kelas dan mixin berbeda.
Jika ragu, itu sering lebih baik mundur dan dasarkan pekerjaan anda pada View
atau TemplateView
, mungkin dengan SingleObjectMixin
dan MultipleObjectMixin
. Meskipun anda akan mungkin mengakhiri menulis kode lebih, itu lebih mungkin jelas dapat dimengerti pada seseorang lain datang ke itu kemudian, dan dengan sedikit interaksi untuk khawatir tentang anda akan menyimpan beberapa pemikiran anda sendiri. (Tentu saja, anda dapat selalu mempelajari kedalam penerapan Django dari tampilan berdasarkan-kelas umum untuk ilham pada bagaimana memecahkan masalah.)
Menggunakan SingleObjectMixin
dengan View¶
If we want to write a class-based view that responds only to POST
, we'll
subclass View
and write a post()
method
in the subclass. However if we want our processing to work on a particular
object, identified from the URL, we'll want the functionality provided by
SingleObjectMixin
.
Kami akan mempertunjukkan ini dengan model Author
kami gunakan di generic class-based views introduction.
views.py
¶from django.http import HttpResponseForbidden, HttpResponseRedirect
from django.urls import reverse
from django.views import View
from django.views.generic.detail import SingleObjectMixin
from books.models import Author
class RecordInterestView(SingleObjectMixin, View):
"""Records the current user's interest in an author."""
model = Author
def post(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return HttpResponseForbidden()
# Look up the author we're interested in.
self.object = self.get_object()
# Actually record interest somehow here!
return HttpResponseRedirect(
reverse("author-detail", kwargs={"pk": self.object.pk})
)
In practice you'd probably want to record the interest in a key-value
store rather than in a relational database, so we've left that bit
out. The only bit of the view that needs to worry about using
SingleObjectMixin
is where we want to
look up the author we're interested in, which it does with a call to
self.get_object()
. Everything else is taken care of for us by the mixin.
Kami dapat menghubungkan kedalam URL kami dengan cukup mudah:
urls.py
¶from django.urls import path
from books.views import RecordInterestView
urlpatterns = [
# ...
path(
"author/<int:pk>/interest/",
RecordInterestView.as_view(),
name="author-interest",
),
]
Catat kelompok bernama pk
, yang get_object()
menggunakan untuk mencari instance Author
. Anda dapat juga menggunakan sebuah keong, atau apapun dari fitur-fitur lain dari SingleObjectMixin
.
Menggunakan SingleObjectMixin
dengan ListView
¶
ListView
menyediakan penomoran siap-pakai, tetapi anda mungkin ingin memberi nomor daftar dari obyek yang semua terkait (oleh foreign key) ke obyek lain. Di contoh penerbitan kami, anda mungkin ingin memberi nomor melalui semua buku-buku oleh penerbit tertentu.
Satu cara melakukan ini adalah memadukan ListView
dengan SingleObjectMixin
, sehingga queryset untuk memberi nomor halaman dari buku dapat menggantung penerbit ditemukan sebagai obyek tunggal. Untuk melakukan ini, kami butuh memiliki dua queryset berbeda:
- Queryset
Book
untuk digunakan olehListView
Since we have access to the
Publisher
whose books we want to list, we overrideget_queryset()
and use thePublisher
’s reverse foreign key manager.- Queryset
Publisher
untuk digunakan diget_object()
Kami akan bergantung pada penerapan awalan dari
get_object()
untuk mengambil obyekPublisher
benar. Bagaimanapun, kami butuh jelas melewatkan sebuah argumenqueryset
karena jika tidak penerapan awalan dariget_object()
akan memanggilget_queryset()
yang kami telah timpa untuk mengembalikan obyekBook
daripadaPublisher
.
Catatan
Kami harus berpikir hati-hati tentang get_context_data()
. Sejak kedua SingleObjectMixin
dan ListView
akan menaruh hal-hal dalam data konteks dibawah nilai dari context_object_name
jika itu disetel, kami akan jelas memastikan Publisher
adalah dalam data konteks. ListView
akan menambah dalam page_obj
dan paginator
cocok untuk kami menyediakan kami ingat memanggil super()
.
Sekarang kami dapat menulis PublisherDetailView
baru:
from django.views.generic import ListView
from django.views.generic.detail import SingleObjectMixin
from books.models import Publisher
class PublisherDetailView(SingleObjectMixin, ListView):
paginate_by = 2
template_name = "books/publisher_detail.html"
def get(self, request, *args, **kwargs):
self.object = self.get_object(queryset=Publisher.objects.all())
return super().get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["publisher"] = self.object
return context
def get_queryset(self):
return self.object.book_set.all()
Perhatikan bagaimana kami menyetel self.object
dalam get()
jadi kami dapat menggunakan itu kembali kemudian dalam get_context_data()
dan get_queryset()
. Jika anda tidak menyetel template_name
, cetakan akan awalan menjadi pilihan ListView
biasa, yang dalam kasus ini akan menjadi "books/book_list.html"
karena itu adalah daftar dari buku-buku; ListView
tidak mengetahui apapun tentang SingleObjectMixin
, jadi itu tidak mempunyai petunjuk tampilan ini adalah hubungannya dengan Publisher
.
paginate_by
sengaja kecil dalam contoh sehingga anda tidak perlu membuat banyak buku untuk melihat penomoran halaman bekerja! Ini adalah cetakan anda ingin gunakan:
{% extends "base.html" %}
{% block content %}
<h2>Publisher {{ publisher.name }}</h2>
<ol>
{% for book in page_obj %}
<li>{{ book.title }}</li>
{% endfor %}
</ol>
<div class="pagination">
<span class="step-links">
{% if page_obj.has_previous %}
<a href="?page={{ page_obj.previous_page_number }}">previous</a>
{% endif %}
<span class="current">
Page {{ page_obj.number }} of {{ paginator.num_pages }}.
</span>
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}">next</a>
{% endif %}
</span>
</div>
{% endblock %}
Hindari apapun lebih rumit¶
Umumnya anda dapat gunakan TemplateResponseMixin
dan SingleObjectMixin
ketika anda butuh fungsionalitas mereka. Seperti ditunjukkan diatas, dengan sedikit perawatan anda dapat bahkan memadukan SingleObjectMixin
dengan ListView
. Bagaimanapun hal-hal meningkat semakin rumit ketika anda mencoba melakukannya, dan aturan bagus dari ibu jari adalah:
Petunjuk
Setiap tampilan anda harus menggunakan hanya mixin atau tampilan dari saru dari kelompok dari tampilan berdasarkan-kelas umum: detail, list, editing dan tanggal. Sebagai contoh itu adalah baik memadukan TemplateView
(tampilan siap pakai) dengan MultipleObjectMixin
(daftar umum), tetapi anda mungkin memiliki masalah memadukan SingleObjectMixin
(rincian umum) dengan MultipleObjectMixin
(daftar umum).
Untuk menunjukkan apa yang terjadi ketika anda mencoba mendapatkan lebih mutakhir, kami menunjukkan sebuah contoh yang mengorbankan kesiapan dan rawatan ketika ada pemecahan termudah. Pertama, mari kita lihat usaha naif untuk memadukan DetailView
dengan FormMixin
untuk mengadakan kami pada POST
sebuah Form
Django ke URL sama ketika kami sedang memperlihatkan sebuah obyek menggunakan DetailView
.
Menggunakan FormMixin
dengan DetailView
¶
Berpikir kembali ke contoh paling awal dari menggunakan View
dan SingleObjectMixin
bersama-sama. Kami sedang merekam sebuah minat pengguna dalam penulis tertentu; katakan sekarang yang kami ingin membiarkan mereka meninggalkan pesan mengatakan mengapa mereka menyukainya. Kembali, mari kita anggap tidak menyimpan ini dalam basisdata hubungan tetapi sebagai gantinya di lebih esoterik yang kami tidak akan khawatir disini.
Pada titik ini itu adalah alamiah mencapai untuk Form
untuk mengenkapsulasi informasi dikirim dari peramban pengguna pada Django. Katakan juga bahwa kami melakukan investasi di REST, jadi kami ingin menggunakan URL sama untuk memperlihatkan penulis sebagai untuk menangkap pesan dar pengguna. Mari kita menulis kembali AuthorDetailView
kami untuk melakukan itu.
Kami akan menjaga penanganan GET
dari DetailView
, meskipun kami akan harus menambah Form
kedalam data konteks jadi kami dapat mengirim itu dalam cetakan. Kami akan juga ingin menarik dalam pengolahan formulir FormMixin
, dan menulis sedikit kode sehingga pada POST
formulir mendapatkan panggilan yang sesuai.
Catatan
Kami menggunakan FormMixin
dan menerapkan post()
kami sendiri daripada mencoba mencampur DetailView
dengan FormView
(yang menyediakan sudah post()
cocok) karena kedua dari tampilan menerapkan get()
, dan hal-hal akan menjadi lebih membingungkan.
AuthorDetailView
kami yang baru terlihat seperti ini:
# CAUTION: you almost certainly do not want to do this.
# It is provided as part of a discussion of problems you can
# run into when combining different generic class-based view
# functionality that is not designed to be used together.
from django import forms
from django.http import HttpResponseForbidden
from django.urls import reverse
from django.views.generic import DetailView
from django.views.generic.edit import FormMixin
from books.models import Author
class AuthorInterestForm(forms.Form):
message = forms.CharField()
class AuthorDetailView(FormMixin, DetailView):
model = Author
form_class = AuthorInterestForm
def get_success_url(self):
return reverse("author-detail", kwargs={"pk": self.object.pk})
def post(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return HttpResponseForbidden()
self.object = self.get_object()
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def form_valid(self, form):
# Here, we would record the user's interest using the message
# passed in form.cleaned_data['message']
return super().form_valid(form)
get_success_url()
provides somewhere to redirect to, which gets used
in the default implementation of form_valid()
. We have to provide our
own post()
as noted earlier.
Pemecahan terbaik¶
The number of subtle interactions between
FormMixin
and DetailView
is
already testing our ability to manage things. It's unlikely you'd want to
write this kind of class yourself.
In this case, you could write the post()
method yourself, keeping
DetailView
as the only generic functionality, although writing
Form
handling code involves a lot of duplication.
Alternatively, it would still be less work than the above approach to
have a separate view for processing the form, which could use
FormView
distinct from
DetailView
without concerns.
Sebuah pilihan lain pemecahan terbaik¶
Apa kami sedang coba lakukan disini adalah menggunakan dua kelas bebeda berdasarkan tampilan dari URL sama. Jadi mengapa tidak lakukan hal itu? Kami mempunyai pembagian sangat jelas disini: permintaan GET
harus mendapatkan DetailView
(dengan Form
ditambahkan ke data konteks), dan permintaan POST
harus mendapatkan FormView
. Mari kita menyetek tampilan tersebut dahulu.
The AuthorDetailView
view is almost the same as when we
first introduced AuthorDetailView; we have to
write our own get_context_data()
to make the
AuthorInterestForm
available to the template. We'll skip the
get_object()
override from before for clarity:
from django import forms
from django.views.generic import DetailView
from books.models import Author
class AuthorInterestForm(forms.Form):
message = forms.CharField()
class AuthorDetailView(DetailView):
model = Author
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["form"] = AuthorInterestForm()
return context
Then the AuthorInterestFormView
is a FormView
, but we have to
bring in SingleObjectMixin
so we can find
the author we're talking about, and we have to remember to set
template_name
to ensure that form errors will render the same template as
AuthorDetailView
is using on GET
:
from django.http import HttpResponseForbidden
from django.urls import reverse
from django.views.generic import FormView
from django.views.generic.detail import SingleObjectMixin
class AuthorInterestFormView(SingleObjectMixin, FormView):
template_name = "books/author_detail.html"
form_class = AuthorInterestForm
model = Author
def post(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return HttpResponseForbidden()
self.object = self.get_object()
return super().post(request, *args, **kwargs)
def get_success_url(self):
return reverse("author-detail", kwargs={"pk": self.object.pk})
Finally we bring this together in a new AuthorView
view. We
already know that calling as_view()
on
a class-based view gives us something that behaves exactly like a function
based view, so we can do that at the point we choose between the two subviews.
You can pass through keyword arguments to
as_view()
in the same way you
would in your URLconf, such as if you wanted the AuthorInterestFormView
behavior to also appear at another URL but using a different template:
from django.views import View
class AuthorView(View):
def get(self, request, *args, **kwargs):
view = AuthorDetailView.as_view()
return view(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
view = AuthorInterestFormView.as_view()
return view(request, *args, **kwargs)
Pendekatan ini dapat juga digunakan dengan tampilan berdasarkan-kelas umum lainnya diwarisi langsung dari View
or TemplateView
, ketika itu menjaga tampilan berbeda sebagai terpisah mungkin.
Lebih dari hanya HTML¶
Dimana bersinar tampilan berdasarkan-kelas adalah ketika anda ingin melakukan hal sama sebanyak kali. Kiranya anda sedang menulis sebuah API, dan setiap tampilan harus mengembalikan JSON daripada membangun HTML.
Kami dapat membuat sebuah kelas mixin untuk digunakan di semua dari tampilan kami, menangani perubahan pada JSON sekali.
Sebagai contoh, mixin JSON mungkin terlihat seperti ini:
from django.http import JsonResponse
class JSONResponseMixin:
"""
A mixin that can be used to render a JSON response.
"""
def render_to_json_response(self, context, **response_kwargs):
"""
Returns a JSON response, transforming 'context' to make the payload.
"""
return JsonResponse(self.get_data(context), **response_kwargs)
def get_data(self, context):
"""
Returns an object that will be serialized as JSON by json.dumps().
"""
# Note: This is *EXTREMELY* naive; in reality, you'll need
# to do much more complex handling to ensure that arbitrary
# objects -- such as Django model instances or querysets
# -- can be serialized as JSON.
return context
Catatan
Periksa dokumentasi Menserialkan obyek-obyek Django untuk informasi lebih pada bagaimana dengan benar merubah model Django dan queryset menjadi JSON.
This mixin provides a render_to_json_response()
method with the same signature
as render_to_response()
.
To use it, we need to mix it into a TemplateView
for example, and override
render_to_response()
to call render_to_json_response()
instead:
from django.views.generic import TemplateView
class JSONView(JSONResponseMixin, TemplateView):
def render_to_response(self, context, **response_kwargs):
return self.render_to_json_response(context, **response_kwargs)
Equally we could use our mixin with one of the generic views. We can make our
own version of DetailView
by mixing
JSONResponseMixin
with the
BaseDetailView
-- (the
DetailView
before template
rendering behavior has been mixed in):
from django.views.generic.detail import BaseDetailView
class JSONDetailView(JSONResponseMixin, BaseDetailView):
def render_to_response(self, context, **response_kwargs):
return self.render_to_json_response(context, **response_kwargs)
Tampilan ini dapat kemudian disebarkan dalam cara sama seperti lainnya DetailView
, dengan persi perilaku sama -- kecuali untuk bentuk dari tanggapan.
If you want to be really adventurous, you could even mix a
DetailView
subclass that is able
to return both HTML and JSON content, depending on some property of
the HTTP request, such as a query argument or an HTTP header. Mix in both the
JSONResponseMixin
and a
SingleObjectTemplateResponseMixin
,
and override the implementation of
render_to_response()
to defer to the appropriate rendering method depending on the type of response
that the user requested:
from django.views.generic.detail import SingleObjectTemplateResponseMixin
class HybridDetailView(
JSONResponseMixin, SingleObjectTemplateResponseMixin, BaseDetailView
):
def render_to_response(self, context):
# Look for a 'format=json' GET argument
if self.request.GET.get("format") == "json":
return self.render_to_json_response(context)
else:
return super().render_to_response(context)
Karena cara bahwa Python menyelesaikan metode melebihi batas, panggilan pada super().render_to_response(context)
mengakhiri panggilan penerapan render_to_response()
dari TemplateResponseMixin
.