Search code examples
djangomixins

How to inject same context in many different Django views?


I want to put info about one object in many views without repeating it in get_context_data in each view. As u understand i need a class with get_context_data inside, that i can mix with other views. Here in my example i want to see 'some_object' in context of UpdateAnotherObjectView:

class BaseObjectInfoView(View):
    def get_context_data(self, **kwargs):
        context_data = super(BaseObjectInfoView, self).get_context_data(**kwargs)
        context_data['some_object'] = SomeObjects.objects.get(pk=1)
        return context_data

class UpdateAnotherObjectView(BaseObjectInfo, UpdateView):
    template_name = 'create_object.html'
    form_class = AnotherObjectForm
    model = AnotherObjects

    def get_context_data(self, **kwargs):
        context_data = super(UpdateAnotherObjectView, self).get_context_data(**kwargs)
        context_data['all_another_objects'] = AnotherObjects.objects.all()
        return context_data

it works, but get_context_data is not a part of parent 'View' class. May be i need more special class to inherit from in BaseObjectInfoView?

or maybe better to construct context with another method ?


Solution

  • Mixins don't need to be views, but it helps IDE's if they have the methods they're overriding.

    Contexts are handled by django.views.generic.base.ContextMixin (details on this very handy site), So the class-based views way of things would be this:

    from django.views import generic
    
    class WebsiteCommonMixin(generic.base.ContextMixin):
        page_title = ''
        active_menu = None
    
        def get_context_data(self, **kwargs):
            context = super(WebsiteCommonMixin, self).get_context_data(**kwargs)
            context.update(dict(page_title=self.page_title, active_menu=self.active_menu))
            return context
    
    class NewsListView(WebsiteCommonMixin, ListView):
        page_title = 'News list'
        active_menu = 'News'
        model = News
        paginate_by = 12
    

    I do this for many projects and the simple views you have to create anyway, are fully declarative. And by simple, I mean that they can consist of mulitple mixins, all doing the hard stuff in either get_queryset, get_context_data or form_valid. More elaborate example, straight from a project:

    class FeedbackMixin(object):
        message = 'Well Done!'
    
        def __init__(self):
            self._message_kwargs = {}
            super().__init__()
    
        def add_message_kwarg(self, name, value) -> None:
            self._message_kwargs[name] = value
    
        def format_message(self, kwargs) -> str:
            return self.message.format(**kwargs)
    
        def generate_message(self) -> None:
            msg = self.format_message(self._message_kwargs)
            messages.success(getattr(self, 'request'), msg)
    
    
    class ModelFeedbackMixin(FeedbackMixin, generic.edit.ModelFormMixin):
        success_view_name = None
        success_url_kwargs = None
    
        def get_success_url_kwargs(self):
            return self.success_url_kwargs
    
        def get_success_url(self) -> str:
            success_url_kwargs = self.get_success_url_kwargs()
            if not self.success_view_name:
                url = super().get_success_url()
            elif success_url_kwargs is not None:
                url = reverse(self.success_view_name, kwargs=success_url_kwargs)
            else:
                if hasattr(self.object, 'slug'):
                    url_kwargs = {'slug': self.object.slug}
                else:
                    url_kwargs = {'pk': self.object.pk}
                url = reverse(self.success_view_name, kwargs=url_kwargs)
            return url
    
        def form_valid(self, form):
            response = super().form_valid(form)
            self.generate_message()
            return response