استفاده از میکسین‌ها در نمایش‌های مبتنی بر کلاس جنگو: راهنمای کامل برای طراحی ویوهای ماژولار و انعطاف‌پذیر

این مقاله نحوه استفاده از میکسین‌ها در نمایش‌های مبتنی بر کلاس جنگو را توضیح می‌دهد. موضوعاتی مانند TemplateResponseMixin، ContextMixin، SingleObjectMixin، MultipleObjectMixin، نحوه ساخت DetailView و ListView با استفاده از میکسین‌ها، و همچنین ترکیب دستی میکسین‌ها با View بررسی می‌شود.

میکسین جنگو، SingleObjectMixinMultipleObjectMixin، TemplateResponseMixinContextMixin، DetailView، ListView

~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 داشته باشیم:

  1. queryset مربوط به Publisher برای استفاده در get_object()
  2. 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 %}
  1. {{ book.title }}
  2. {% 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ها بهره ببرید.

نوشته و پژوهش شده توسط دکتر شاهین صیامی