Generic ViewSets — Custom Mixins

Caronex Labs
Django Rest for Not Beginners
6 min readJul 19, 2020

Welcome back! or Hey there! depending on whether you’re following the series or using this as a reference… both of which is perfectly alright by the way, you don’t need a lot of context to understand the crux of this article.

If you really only want to see the syntax or the rudimentary application of a mixin, just scroll all the way down to Step 3: The Mixin. It’s towards the end of the article.

So let’s get started!

Like every time, here is a list of prerequisites that you should be familiar to truly appreciate this article:

  • Generic ViewSets — You can check out the docs or our article here.
  • Custom Endpoints — Again, the docs… or our article here.
  • AbstractUser — Not completely required, but you’ll be more aware of what’s happening.
  • ModelSerializers.

Do consider paying a visit to our publication if you wish to understand the project thoroughly.

I believe that the best place to start is to see where we want to end up, so…

Here’s our aim:

We’re trying to replicate Medium’s profile page backend.

Which means we need to retrieve and update the users information and their stories. We’re avoiding anything too complicated, so don’t worry about this too much.

This time, we will build the endpoints to retrieve the current users Stories and then create a mixin to reduce code.

Here is the page we are building:

Some Background:

Last time, we created a custom endpoint and called it the me endpoint. It returned the data of the current user. Here is the view, the serializer, the model and the urls for the same. Just go through it, we don’t need to be familiar with every bit of this code:

The Model:

users_module/models.py

The View:

users_module/views.py

The Serializer:

users_module/serializers.py

The URLs:

_1_GenericViewSets/urls.py

If you really wish to understand the project better, here it is on Github.

Let’s Start:

Now that everyone is on the same page, let’s get to what we want to do:

  • Introduce the Stories Model.
  • Create an endpoint to get the current users stories.
  • Create a custom mixin to reduce the amount of code.

Step 1: The Model -

The title of this series is Django Rest For NOT Beginners…

Here it is :

users_module/models.py

Pro Tip:

A Meta class is a class, generally inside another class, that contains metadata about the parent class.

The default behavior of Django is to just add an ‘s’ at the end of the name of the model to get the plural name.

In our case, we are using the Meta class to edit the metadata -the plural form of the name of this model. You can also use the Meta class to modify other metadata like ordering or db_index.

This change is purely cosmetic, it will make the name of the model appear properly in the Admin Panel that comes with Django.

Step 2: The Endpoint -

We’ve got our model in place, now let’s look at the view. We will be creating a new Generic ViewSet for our stories.

This is what it looks like:

The Serializer:

The View:

users_module/views.py

Let’s break it down:

  • We added a new Serializer called StorySerializer. It’s a simple ModelSerializer.
  • We added a new ViewSet called StoryViewSet.
  • We created a custom endpoint called me that returns the current users stories. In order to do that:
    - We got our serializer
    - We passed in the current user’s stories (We used the related name that links the User model to the Stories model)
    -We mentioned many=True that tells the serializer to expect multiple objects, because each user could have authored multiple stories.
  • We returned the desired data as a Response

Notice how the two me endpoints are really similar. We are violating a very important principle of development: DRY. It stands for Don’t Repeat Yourself. And that is what we will try to fix now.

So, lets try to make both the me functions the same. Again, in steps.

  1. Find the difference between the two functions.

The two me functions differ by only a single line. This one: data=serializer(request.user).data — UserViewSet
data=serializer(request.user.stories, many=True) — StoryViewSet

Even in this line, there’s really two things that differ. They are the arguments that we pass to the serializer.
— To be able to change these arguments dynamically, we must first understand what they are.

2. Understand the arguments.

A serializer takes many arguments. We perhaps delve into them in a later series. But what concerns us now are the instance and the many arguments.

  • The instance takes in an object that needs to be serialized.
  • The many argument is a boolean that indicates if we are passing a single object or a list of objects (A Queryset).

3. Abstract the arguments away.

Let’s get to the coding part.

First thing we must do is to create variables out of the arguments we pass.

This can get a little confusing, so try to code along. That always helps me understand.

  • Create Variables Out Of Arguments:

Right now, we are passing data to our serializers directly. Which is causing the two me functions to be different. Which is why, we will now pass the data indirectly through variables.

# The UserViewSet me Endpoint@action(methods=['get'], detail=False)    
def me(self, request):
serializer = self.get_serializer_class()

instance = request.user
many = False

data = serializer(instance = instance, many = many).data
return Response(data, status=status.HTTP_200_OK)
# The StoryViewSet me Endpoint@action(methods=['get'], detail=False)
def me(self, request):
serializer = self.get_serializer_class()

instance = request.user.stories
many = True

data = serializer(instance = instance, many = many).data
return Response(data, status=status.HTTP_200_OK)
  • Take those variables out of the function and make them properties of the parent class. The use the self keyword to access them.
class UserViewSet(GenericViewSet):
...

instance = request.user
many = False
@action(methods=['get'], detail=False)
def me(self, request):
serializer = self.get_serializer_class()

data = serializer(
instance = self.instance,
many = self.many
).data

return Response(data, status=status.HTTP_200_OK)

class StoryViewSet(GenericViewSet):
...
instance = request.user.stories
many = True
@action(methods=['get'], detail=False)
def me(self, request):
serializer = self.get_serializer_class()

data = serializer(
instance = self.instance,
many = self.many
).data

return Response(data, status=status.HTTP_200_OK)

We’ve got ourselves an identical me function. Yay!

But the above code won’t run just yet. The issue is that you cannot access the request keyword outside a function. So how do we assign the instance as request.user.

To solve this problem, we will create a function inside each of the two ViewSets. This function simply returns the above two variables inside a dictionary. Let’s call this function: get_me_config because, that’s what we’re doing… configuring the me function.

Here is what the function will look like:

# This is for the UserViewSetdef get_me_config(self):
return {
'instance': self.request.user,
'many' : False
}

And now our me function will have to be modified like this:

# This is for the UserViewSet but now the function is same for both@action(methods=['get'], detail=False)    
def me(self, request):
serializer = self.get_serializer_class()

data = serializer(
instance=self.get_me_config().get('instance'),
many = self.get_me_config().get('many')
).data

return Response(data, status=status.HTTP_200_OK)

And we’re done with the abstraction.
Both our me functions are now identical.

This has been heavy. So let’s take a breather.

Here is our final code after all the changes we just made:

So simple it looks right… after all the pain we just went through.

The me function is now clearly repeated. Let’s get rid of the repetition.

Step 3: The Mixin -

Well, after that life changer… turns out that, ironically, this section is the easiest… even though it’s the whole reason you are reading this article.

Making a mixin is really simple. Here are the steps

  • Make a class.
  • Copy the repeating code.
  • Include this new class in the ViewSet.

So let’s begin.

  1. Make A Class
class MeMixin:

Done.

2. Copy The Repeating Code

class MeMixin:

@action(methods=['get'], detail=False)
def me(self, request):
serializer = self.get_serializer_class()
data = serializer(
**self.get_me_config()
).data
return Response(data, status=status.HTTP_200_OK)

Pro Tip: using ** before a dictionary or a function call that returns a dictionary will basically allow the function to use the dictionary contents as arguments.

Done.

3. Include The New Mixin in the ViewSet

class UserViewSet(MeMixin, GenericViewSet):        serializer_class = UserSerializer    
permission_classes = [IsAuthenticated]
def get_queryset(self):
return User.objects.all()

Ta-da!

This time we’re done for real.

Here is the final code.

Does this look good? : Yes

Was it worth the pain? : Probably

This situation was probably not one in which you would consider using a custom mixin. We didn’t save a whole bunch of lines by doing what we did.

But I chose this example because the relative ease of the aim let us focus on what was really important. Creating a Mixin and messing around with it.

This one was probably a little more complex than the other articles we’ve been through and I hope I was able to convey the information across well.

Let’s wrap it up here. Next time, we will work multiple serializers in a single viewset. I hope to see you there.

--

--