user2492270
user2492270

Reputation: 2285

Is it ok for a Django mixin to inherit another mixin?

I'm pretty sure the answer to this question is obviously "NO", since Django mixins are supposed to

inherit "object"s, but I can't find an alternative solution to my problem :(

To make the question as simple as possible,,,

views.py

class JSONResponseMixin(object):
    def render_to_response(self, context):
        "Returns a JSON response containing 'context' as payload"
        return self.get_json_response(self.convert_context_to_json(context))

    def get_json_response(self, content, **httpresponse_kwargs):
        "Construct an `HttpResponse` object."
        return http.HttpResponse(content,
                                 content_type='application/json',
                                 **httpresponse_kwargs)

    def convert_context_to_json(self, context):
        "Convert the context dictionary into a JSON object"
        # 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 json.dumps(context)


class HandlingAJAXPostMixin(JSONResponseMixin):
    def post(self, request, *args, **kwargs):
        .....
        data = {'somedata': somedata}

        return JSONResponseMixin.render_json_response(data)


class UserDetailView(HandlingAJAXPostMixin, DetailView):
    model = MyUser
    .....



So the problem I have is that, for multiple Views, I want to respond to their "post" request with the same

JSON Response. That is why I defined the HandlingAJAXPostMixin so that I could reuse it for

other Views. Since the HandlingAJAXPostMixin returns a JSON response,

it requires a render_json_response method, which is defined in the JSONResponseMixin.

This is the reason why I am making my HandlingAJAXPostMixin inherit the

JSONResponseMixin, but this obviously seems wrong :(..

Any suggestions..?

Thanks!!!

Upvotes: 1

Views: 1299

Answers (1)

knbk
knbk

Reputation: 53679

It's perfectly valid for a mixin to inherit from another mixin - in fact, this is how most of Django's more advanced mixins are made.

However, the idea of mixins is that they are reusable parts that, together with other classes, build a complete, usable class. Right now, your JSONResponseMixin might as well be a separate class that you don't inherit from, or the methods might just be module-wide methods. It definitely works, there's nothing wrong with it, but that's not the idea of a mixin.

If you look at Django's BaseDetailView, you see the following get() method:

def get(self, request, *args, **kwargs):
    self.object = self.get_object()
    context = self.get_context_data(object=self.object)
    return self.render_to_response(context)

get_object() and get_context_data() are defined in the subclasses of BaseDetailView, but render_to_response() isn't. It's okay for mixins to rely on methods that it's superclasses don't define, this allows different classes that inherit from BaseDetailView to supply their own implementation of render_to_response(). Right now, in Django, there's only one subclass, though.

However, logic is delegated as much as possible to those small, reusable methods that the mixins supply. That's what you want to aim for. If/else logic is avoided as much as possible - the most advanced logic in Django's default views is:

if form.is_valid(): 
    return self.form_valid(form) 
else: 
    return self.form_invalid(form)

That's why very similar views, like CreateView and UpdateView are in fact two separate views, while they could easily be a single view with some additional if/else logic. The only difference is that CreateView does self.object = None, while UpdateView does self.object = self.get_object().

Right now you are using a DetailView that defines a get() method that returns the result of self.render_to_response(). However, you override render_to_response() to return a JSON response instead of a template-based HTML response. You're using a mixin that you don't what to use (SingleObjectTemplateResponseMixin) and then override it's behavior to do something that you don't want to do either, just to get the view doing what you want it to do. A better idea would be to write an alternative for DetailView who's only job is to supply a JSON response based on a single object. To do this, I would create a SingleObjectJSONResponseMixin, similar to the SingleObjectTemplateResponseMixin, and create a class JSONDetailView that combines all needed mixins into a single object:

class SingleObjectJSONResponseMixin(object):
    def to_json(context):
        return json.dumps(context)

    def render_to_response(context, **httpresponse_kwargs):
        return HttpResponse(self.to_json(context),
                            context_type='application/json',
                            **httpresponse_kwargs)

class BaseJSONDetailView(SingleObjectMixin, View):
    # if you want to do the same for get, inherit just from BaseDetailView
    def post(self, request, *args, **kwargs):
        self.object = self.get_object()
        context = self.get_context_data(object=self.object)
        return render_to_response(context)

class JSONDetailView(SingleObjectJSONResponseMixin, BaseJSONDetailView):
    """ 
    Return JSON detail data of a single object.
    """

Notice that this is almost exactly the same as the BaseDetailView and the SingleObjectTemplateResponseMixin provided by Django. The difference is that you define a post() method and that the rendering is much more simple with just a conversion to JSON of the context data, not a complete template rendering. However, logic is deliberately kept simple as much as possible, and methods that don't depend on each other are separated as much as possible. This way, SingleObjectJSONResponseMixin can e.g. be mixed with BaseUpdateView to easily create an AJAX/JSON-based UpdateView. Subclasses can easily override the different parts of the mixins, like overriding to_json() to supply a certain data structure. Rendering logic is where it belongs (in render_to_response()).

Now all you need to do to create a specific JSONDetailView is to subclass and define which model to use:

class UserJSONDetailView(JSONDetailView):
    model = MyUser

Upvotes: 5

Related Questions