~11 دقیقه مطالعه • بروزرسانی ۲۳ اسفند ۱۴۰۴
مقدمه
میکسینها یکی از قدرتمندترین ابزارهای جنگو برای ساخت نمایشهای مبتنی بر کلاس (CBV) هستند. آنها امکان ترکیب رفتارهای کوچک و قابلاستفاده مجدد را فراهم میکنند و به شما اجازه میدهند ویوهایی انعطافپذیر، تمیز و قابل نگهداری بسازید. البته استفاده از میکسینها نیازمند درک خوبی از ساختار CBVهاست.
چرا از میکسینها استفاده کنیم؟
گاهی میخواهید بخشی از رفتار یک ویو را داشته باشید، اما نه کل آن را. مثلاً شاید بخواهید فقط در متد POST یک قالب را رندر کنید، اما در GET رفتار دیگری داشته باشید. بهجای بازنویسی TemplateView، میتوانید از TemplateResponseMixin استفاده کنید.
مزایای میکسینها:
- کاهش تکرار کد
- افزایش ماژولار بودن ویوها
- امکان ترکیب رفتارهای مختلف
- سفارشیسازی آسانتر
میکسینهای مرتبط با قالب و context
TemplateResponseMixin
این میکسین متد render_to_response() را فراهم میکند که تقریباً تمام ویوهای مبتنی بر قالب از آن استفاده میکنند. این متد از get_template_names() برای یافتن قالب مناسب استفاده میکند.
ContextMixin
این میکسین متد get_context_data() را فراهم میکند که دادههای context را برمیگرداند. اکثر ویوها این متد را override میکنند تا دادههای بیشتری به قالب ارسال کنند.
ساخت DetailView با استفاده از میکسینها
برای نمایش جزئیات یک شیء، دو کار لازم است:
- واکشی شیء
- رندر قالب با آن شیء
جنگو این کار را با ترکیب دو میکسین انجام میدهد:
- SingleObjectMixin: متد
get_object()را فراهم میکند. - SingleObjectTemplateResponseMixin: قالب مناسب را انتخاب میکند.
قالب پیشفرض:
<app_label>/<model_name>_detail.html
ساخت ListView با استفاده از میکسینها
برای نمایش لیست اشیا، جنگو از دو میکسین دیگر استفاده میکند:
- MultipleObjectMixin: متدهای
get_queryset()وpaginate_queryset() - MultipleObjectTemplateResponseMixin: انتخاب قالب مناسب
قالب پیشفرض:
<app_label>/<model_name>_list.html
ترکیب دستی میکسینها با View
گاهی لازم است یک ویو کاملاً سفارشی بسازید. در این حالت میتوانید میکسینها را مستقیماً با View ترکیب کنید.
مثال: استفاده از SingleObjectMixin با View
class RecordInterestView(SingleObjectMixin, View):
model = Author
def post(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return HttpResponseForbidden()
self.object = self.get_object()
# ثبت علاقهمندی کاربر
return HttpResponseRedirect(
reverse("author-detail", kwargs={"pk": self.object.pk})
)
اتصال به URL
path(
"author/<int:pk>/interest/",
RecordInterestView.as_view(),
name="author-interest",
)
در اینجا pk توسط get_object() برای یافتن شیء استفاده میشود.
نکات مهم درباره ترکیب میکسینها
همه میکسینها با هم سازگار نیستند. هنگام ترکیب آنها باید به موارد زیر توجه کنید:
- ترتیب وراثت و MRO
- متدهای همنام
- تداخل در ویژگیها و رفتارها
اگر ترکیب پیچیده شد، بهتر است از View یا TemplateView شروع کنید و فقط میکسینهای لازم را اضافه کنید.
جمعبندی
میکسینها ابزار قدرتمندی برای ساخت ویوهای انعطافپذیر و قابل نگهداری در جنگو هستند. با درک نحوه استفاده از میکسینهایی مانند SingleObjectMixin، MultipleObjectMixin، TemplateResponseMixin و ContextMixin میتوانید ویوهایی بسازید که دقیقاً مطابق نیاز شما رفتار کنند، بدون اینکه مجبور باشید کدهای تکراری بنویسید.
مقدمه
گاهی در پروژههای جنگو نیاز داریم یک شیء اصلی را نمایش دهیم و همزمان لیستی از اشیای مرتبط با آن را نیز صفحهبندی کنیم. برای مثال، نمایش یک ناشر (Publisher) و صفحهبندی کتابهای او. جنگو بهصورت پیشفرض چنین ویوی ترکیبی ارائه نمیدهد، اما با ترکیب SingleObjectMixin و ListView میتوانیم این رفتار را بسازیم.
چرا ترکیب SingleObjectMixin با ListView؟
ListView صفحهبندی داخلی دارد، اما فقط روی یک queryset کار میکند.
SingleObjectMixin یک شیء را بر اساس URL واکشی میکند.
با ترکیب این دو:
- شیء اصلی (Publisher) را واکشی میکنیم
- لیست اشیای مرتبط (Book) را از طریق رابطهٔ معکوس دریافت میکنیم
- لیست کتابها را صفحهبندی میکنیم
چالش اصلی: دو queryset متفاوت
برای این ترکیب باید دو queryset داشته باشیم:
- queryset مربوط به Publisher برای استفاده در
get_object() - queryset مربوط به Book برای استفاده در
ListView
اگر این کار را اشتباه انجام دهیم، get_object() بهجای Publisher، کتابها را واکشی میکند!
پیادهسازی PublisherDetailView
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()
نکات مهم:
self.objectدرget()تنظیم میشود تا درget_queryset()وget_context_data()قابل استفاده باشد.- اگر
template_nameرا مشخص نکنیم، ListView قالبbook_list.htmlرا انتخاب میکند، چون از وجود Publisher بیخبر است. - مقدار
paginate_byکوچک انتخاب شده تا صفحهبندی راحت دیده شود.
قالب پیشنهادی
{% extends "base.html" %}
{% block content %}
Publisher {{ publisher.name }}
{% for book in page_obj %}
- {{ book.title }}
{% endfor %}
{% endblock %}
هشدار: از ترکیبهای پیچیدهتر پرهیز کنید
جنگو توصیه میکند که هر ویو فقط از میکسینها و ویوهای یک «گروه» استفاده کند:
- گروه detail
- گروه list
- گروه edit
- گروه date
ترکیب SingleObjectMixin (گروه detail) با MultipleObjectMixin (گروه list) ممکن است کار کند، اما ترکیبهای پیچیدهتر معمولاً باعث رفتارهای غیرقابلپیشبینی میشوند.
مثال مشکلساز: ترکیب FormMixin با DetailView
فرض کنید میخواهیم در همان صفحهٔ نمایش نویسنده (Author) یک فرم هم ارسال کنیم.
ترکیب FormMixin با DetailView بهظاهر منطقی است، اما مشکلات زیادی ایجاد میکند.
نمونه کد (غیر توصیهشده)
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)
return self.form_invalid(form)
مشکلات این ترکیب:
- هر دو کلاس متد
get()دارند → تداخل در MRO - مدیریت context پیچیده میشود
- رفتار form و detail در یک ویو قاطی میشود
راهحل بهتر
بهجای ترکیب پیچیده:
- یا یک ویو ساده بنویسید و فرم را خودتان مدیریت کنید
- یا یک ویو جداگانه برای فرم بسازید (FormView)
این روشها خواناتر، قابلنگهداریتر و کمخطرتر هستند.
جمعبندی
ترکیب SingleObjectMixin با ListView یکی از معدود ترکیبهای پیچیدهای است که واقعاً مفید و قابلاعتماد است. اما ترکیب میکسینهای نامرتبط معمولاً باعث پیچیدگی و رفتارهای غیرمنتظره میشود. با رعایت اصول طراحی CBVها و استفادهٔ هوشمندانه از میکسینها، میتوانید ویوهایی قدرتمند، تمیز و قابلگسترش بسازید.
مقدمه
گاهی نیاز داریم یک URL واحد هم صفحهٔ نمایش جزئیات یک شیء را ارائه دهد و هم فرم ارسال اطلاعات را پردازش کند. ترکیب مستقیم FormMixin با DetailView معمولاً منجر به پیچیدگی، تداخل متدها و مشکلات MRO میشود.
راهحل بهتر این است که GET و POST را در دو ویوی جداگانه مدیریت کنیم و یک ویوی واسط وظیفهٔ هدایت درخواستها را بر عهده بگیرد.
مرحله اول: ویوی GET (نمایش جزئیات + فرم)
این ویو فقط وظیفهٔ نمایش نویسنده و قراردادن فرم در context را دارد.
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
مرحله دوم: ویوی POST (پردازش فرم)
برای پردازش فرم از FormView استفاده میکنیم و با 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})
این کار باعث میشود:
- خطاهای فرم در همان قالب GET نمایش داده شوند
- شیء Author بهدرستی در POST واکشی شود
مرحله سوم: ویوی واسط برای هدایت GET و POST
این ویو فقط تصمیم میگیرد که درخواست را به کدام ویو ارسال کند.
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)
مزایا:
- کد تمیز و قابلنگهداری
- عدم تداخل متدها و MRO
- امکان استفادهٔ مجدد از هر ویو بهصورت مستقل
- انعطافپذیری بالا برای توسعههای آینده
ساخت ویوهای JSON با استفاده از میکسین
اگر در حال ساخت API هستید، میتوانید یک میکسین برای تولید JSON بسازید و آن را در ویوهای مختلف استفاده کنید.
JSONResponseMixin
class JSONResponseMixin:
def render_to_json_response(self, context, **response_kwargs):
return JsonResponse(self.get_data(context), **response_kwargs)
def get_data(self, context):
return context
این میکسین جایگزینی برای render_to_response() فراهم میکند.
استفاده با TemplateView
class JSONView(JSONResponseMixin, TemplateView):
def render_to_response(self, context, **response_kwargs):
return self.render_to_json_response(context, **response_kwargs)
ساخت JSONDetailView
class JSONDetailView(JSONResponseMixin, BaseDetailView):
def render_to_response(self, context, **response_kwargs):
return self.render_to_json_response(context, **response_kwargs)
ویوهای Hybrid: بازگرداندن HTML یا JSON
میتوانید ویویی بسازید که بسته به درخواست، HTML یا JSON برگرداند.
class HybridDetailView(
JSONResponseMixin, SingleObjectTemplateResponseMixin, BaseDetailView
):
def render_to_response(self, context):
if self.request.GET.get("format") == "json":
return self.render_to_json_response(context)
return super().render_to_response(context)
بهدلیل MRO، super().render_to_response() در نهایت متد TemplateResponseMixin را فراخوانی میکند و HTML تولید میشود.
جمعبندی
بهجای ترکیب پیچیدهٔ چند میکسین، بهتر است GET و POST را در ویوهای جداگانه مدیریت کنید و یک ویوی واسط برای هدایت درخواستها بسازید.
این روش تمیز، قابلنگهداری و انعطافپذیر است.
همچنین با استفاده از میکسینهایی مثل JSONResponseMixin میتوانید ویوهای JSON یا Hybrid بسازید و از قدرت واقعی CBVها بهره ببرید.
نوشته و پژوهش شده توسط دکتر شاهین صیامی