Home Django What are Mixins in Django

What are Mixins in Django

When you mix generic views with Mixins, their true power emerges. A mixin is another class you create and whose methods your view class can inherit.

Assume you want the additional variable ‘page_title’ in the template to appear in every view. You create a mixin using this method and let your views inherit instead of overriding the get_context_data method each time you develop the view.

The advantage of this approach is that your code becomes much more structured than it is with functional views. Your whole logic for individual tasks is contained in a single location. You’ll also save a lot of time, especially if you have a lot of views performing the same duties with different objects.

# Custom Mixin
class CustomMixin(object):
    
    def get_context_data(self, **kwargs):
        
        # retrieve context Calls to class's get_context_data method 
        context = super().get_context_data(**kwargs)
        
        context['page_title'] = 'page title'
        return context

# The Mixin is now inherited by the view function
class CreateObject(CustomMixin, CreateView):
    model = SampleObject
    form_class = SampleObjectForm
    success_url = 'url_to_redirect_to'

# it is similar to all other view functions needing these methods
class EditObject(CustomMixin, EditView):
    model = SampleObject
    # …

Some general guidelines for using mixins to create your view classes, as proposed by Kenneth Love :

  • Django’s base view classes are always oriented to the right.
  • Mixins should inherit from Python’s built-in object type and go to the left of the primary view.

Example:

class CustomFormMessageMixin(object):
  
    @property
    def form_valid_message(self):
        return NotImplemented

    form_invalid_message = 'Make changes to the errors below.'

    def form_valid(self, form):
        messages.success(self.request, self.form_valid_message)
        return super(CustomFormMessageMixin, self).form_valid(form)

    def form_invalid(self, form):
        messages.error(self.request, self.form_invalid_message)
        return super(CustomFormMessageMixin, self).form_invalid(form)


class CustomDocumentCreateView(CustomFormMessageMixin, CreateView):
    model = Doc
    fields = ('name', 'file')
    success_url = reverse_lazy('documents')
    form_valid_message = 'Document has been created successfully!'

You could, for example, reuse the same FormMessageMixin in a UpdateView while overriding the original form_invalid_message:

class CustomDocumentUpdateView(CustomFormMessageMixin, UpdateView):
    model = Doc
    fields = ('name', )
    success_url = reverse_lazy('documents')
    form_valid_message = 'Document has been updated successfully!'
    form_invalid_message = 'Errors exist in the form below.'
    

LoginRequiredMixin and UserPassesTestMixin are built-in mixins in Django 1.9. If you use them in your view classes, always put them on the far left side, as seen below:

class CustomDocumentUpdateView(LoginRequiredMixin, FormMessageMixin, UpdateView):
    model = Doc
    fields = ('name', )
    success_url = reverse_lazy('documents')
    form_valid_message = 'The document was successfully updated!'

Using SingleObjectMixin in a View

We’ll subclass view and implement a post() method if we want to write a class-based view that only reacts to POST. However, we’ll need SingleObjectMixin’s capabilities if our processing is to work on a particular object defined by the URL.

from django.http import HttpResponseForbidden, HttpResponseRedirect
from django.urls import reverse
from django.views import View
from django.views.generic.detail import SingleObjectMixin
from books.models import NewAuthor

class RecordCustomInterestView(SingleObjectMixin, View):
    """Records the current user's interest in an author."""
    model = NewAuthor

    def post(self, request, *args, **kwargs):
        if not request.user.is_authenticated:
            return HttpResponseForbidden()

        # Look up the author we're interested in.
        self.object = self.get_object()
        
        # Actually record interest somehow here!
        return HttpResponseRedirect(reverse('author-detail', kwargs={'pk': self.object.pk}))

Using SingleObjectMixin with a ListView

Although ListView has built-in pagination, you might want to paginate a list of objects related to another object through a foreign key. You might want to paginate through all of the books published by a specific publisher in our publishing example.

Combining ListView with SingleObjectMixin is one method to accomplish this since the queryset for the paginated list of books may be hung off the publisher retrieved as a single object. We’ll need two separate querysets to accomplish this:

from django.views.generic import ListView
from django.views.generic.detail import SingleObjectMixin
from books.models import MainPublisher

class CustomPublisherDetailView(SingleObjectMixin, ListView):
    paginate_by = 4
    template_name = "custom/books/publisher_detail.html"

    def get(self, request, *args, **kwargs):
        self.object = self.get_object(queryset=MainPublisher.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()
      
      

Take note of how we set self.object in get() so that we may reuse it in get_context_data() and get_queryset() later. If you don’t specify a template name, the template will default to the standard ListView option, which in this example would be “custom/books/book_list.html” because the view is a list of books; ListView is unaware of SingleObjectMixin, so it has no idea this view is related to a Publisher.

In the example, paginate_by is intentionally limited so that you don’t have to build a lot of books to show how pagination works!

When you need the features of TemplateResponseMixin and SingleObjectMixin, you should utilize them. As demonstrated above, you can even mix SingleObjectMixin and ListView with a bit of care. However, as you strive to do so, things become more complicated, and a decent rule of thumb is:

  • Only use mixins or views from one of the four sets of generic class-based views: detail, list, editing, and date in each of your views. Then combine TemplateView which is a built-in view with MultipleObjectMixin.
  • The generic list is fine, while SingleObjectMixin (generic detail) with MultipleObjectMixin (generic list) is likely to cause difficulties (generic list).

Using DetailView and FormMixin

Consider our previous example of combining View and SingleObjectMixin. We were keeping track of a user’s interest in a specific author; now, let’s say we want them to write a message explaining why they like them. Let’s assume we’re not going to store this in a relational database, but rather in something more arcane about which we won’t get into right now.

It’s natural to look for a Form at this point to encapsulate the data delivered from the user’s browser to Django. Assume we’ve invested extensively in REST and want to use the same URL for showing the author and recording the user’s message. To accomplish this, we’ll need to update our AuthorDetailView.

We’ll preserve DetailView’s GET processing, but we’ll need to add a Form to the context data so that we can render it in the template. We’ll also want to use FormMixin’s form processing and create code to ensure that the form is called appropriately on POST.

from django import forms
from django.http import HttpResponseForbidden
from django.urls import reverse
from django.views.generic import DetailView
from django.views.generic.edit import FormMixin
from books.models import NewAuthor

class CustomAuthorInterestForm(forms.Form):
    message = forms.CharField()

class CustomAuthorDetailView(FormMixin, DetailView):
    model = NewAuthor
    form_class = CustomAuthorInterestForm

    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)
        else:
            return self.form_invalid(form)

    def form_valid(self, form):
        # Here, we would record the user's interest using the message
        # passed in form.cleaned_data['message']
        return super().form_valid(form)


      

A more suitable option

Our capacity to manage things is already being tested by the number of intricate interactions between FormMixin and DetailView. It’s doubtful that you’d want to create a class like this on your own.

Although implementing Form handling code entails a lot of repetition, you might build the post() method yourself in this situation, retaining DetailView as the only generic feature.

Alternatively, having a separate view for processing the form, which could use FormView apart from DetailView without worries, would be less work than the preceding solution.

from django import forms
from django.views.generic import DetailView
from books.models import NewAuthor

class CustomAuthorInterestForm(forms.Form):
    message = forms.CharField()

class CustomAuthorDetailView(DetailView):
    model = NewAuthor

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['form'] = CustomAuthorInterestForm()
        return context

Creating custom -mixins in Django

Slugged – adds a slug field to a model with a title or name – This class can be used to construct a slugged field in any object. For example, the model object that stores this ‘Note’ is derived from the Slugged class since we use the slug created from the title to create URLs.

class CustomSlugged(models.Model):
    """
    The abstract model handles the Auto-generation of slugs.
    """

    the_title = models.CharField(_("Title"), max_length=120, default='')
    the_slug = models.CharField(_("Slug"), max_length=225, db_index=True, unique=True, blank=True)

    class Meta:
        abstract = True
        ordering = ("the_title",)

    def __unicode__(self):
        return self.the_title

    def clean(self):
        from django.core.exceptions import ValidationError
        if self.the_title:
            the_slug = self.get_slug()
            if not the_slug:
                raise ValidationError(' Enter a valid title, has atleast a single character')

    def save(self, *args, **kwargs):
        """
		Unique slug creation by index appending
        
        """
        update_slug = kwargs.pop('update_slug', False)
        concrete_model = base_concrete_model(Slugged, self)

        create_new_slug = False
        if not self.slug or update_slug:
            create_new_slug = True

        if create_new_slug:
          self.the_slug = self.get_slug()

    def get_slug(self):
        """
        Allows subclasses to implement their slug creation logic.
        
        """
        the_slug = ''
        
        # slugify returns 'none' as string if title appears as None, 
        if self.the_title is not None:
            the_slug = slugify(self.the_title)

        # if the titles looks like !@#$!@#$, then an empty string is returned by slugify
        if the_slug == '':
            the_slug = str(uuid.uuid1())[:7]
        return the_slug[:256]

So, if you have a the_title in a model object and need a the_slug to produce unique urls, all you need is sub-classing the model from this class. The class contains a function called ‘get_slug,’ which must be overridden by the model class and provides the logic for creating the slug. The slugify function in Python can generate a slug from the the_title, then check for duplicate slugs and append a digit or random UUID to the end of the slug if one exists. In fact, appending a UUID at the end of each slug should be standard practice to assure uniqueness.

Generic is used to create a Generic foreign key. Before detailing this, let us first define generic content types. Instead of creating a nullable foreign key for each of them, if you have a model with a foreign key to several model types, you may use a generic content type, which stores the object_id of the connected object and the content_type to identify which object we’re referring to.

As your application grows in complexity, you’ll notice that you’re using Django Generic foreign keys more frequently, and having an abstract base class that allows you to add this without having to duplicate your code is always a brilliant idea.

class Generic(models.Model):
    """
    Abstract model for generic content types.
    
    """

    content_type = models.ForeignKey(ContentType, db_index=True)
    object_id = models.PositiveIntegerField(db_index=True)
    content_object = generic.GenericForeignKey('content_type', 'object_id')

    class Meta:
        abstract = True

Add a timestamp and a destroyed attribute to every model object. The concept is to establish an abstract base class, which you then subclass every model from.

class Base(models.Model):
  
    """
    Base parent model for all the models
    
    """
    timestamp = models.DateTimeField(blank=True, db_index=True)
    hidden = models.BooleanField(default=False, db_index=True)

    objects = BaseManager()

    def __init__(self, *args, **kwargs):
        super(Base, self).__init__(*args, **kwargs)
        
    class Meta:
        abstract = True

    # Override save method.
    def save(self,  *args, **kwargs):
        if not self.timestamp:
            self.timestamp = datetime.now()

        update_timestamp = kwargs.pop('update_timestamp', False)
        if update_timestamp:
            self.timestamp = datetime.now()

        super(Base, self).save(*args, **kwargs)
        
     # Override delete method.## #
    def delete(self, **kwargs):
        self._forced_delete = kwargs.pop('forced', False)
        if not self._forced_delete:
          model = self.__class__
          kwargs.update({'is_trashed': True})
          model.objects.(self._db).filter(pk=self.id).update(**kwargs) #using
        else:
          super(Base, self).delete(**kwargs)

Every model in the codebase is descended from the Base model and has the three qualities listed below.

  • Timestamp – recording the timestamp when any object in your database is created is vital; there are so many things you can do with this data that it’s necessary. At first, it is not very apparent, but as your system grows more complicated, you will see the value of having this.
  • Trashed – This is another critical property; as you develop, you’ll notice that trashing items is preferable to removing them from your database. If you delete them all at once, your application will be utterly lost, but as your program grows more complicated, you’ll find that you don’t need to destroy everything; instead, ensure they are not available to the application. Note that implementing the trashed functionality necessitates more than this mixin. In addition to adding this attribute, one must also ensure that the destroyed object is not included in the querylist whenever the model is queried.
class BaseManager(models.Manager):
    """
    Base models' Base Manager
    """
    def __init__(self):
        super(BaseManager, self).__init__()

    def get_query_set(self):
        return BaseQuerySet(model=self.model, using=self._db).filter(is_trashed=False)

    # requires definition on newer Django versions
    def get_queryset(self):
        return BaseQuerySet(model=self.model, using=self._db).filter(is_trashed=False)

  
  • Apart from the obvious benefit of not duplicating your code, utilizing mixins standardizes the names you give properties and adds structure to the way you construct models.

Conclusion

In Object-Oriented Programming, a mixin is a notion that seeks to solve part of the problem of multiple inheritances. You’re removing duplicating logic from numerous classes and isolating it into its class, as we prefer to think about it. They can then be “mixed” in wherever you need them.

Objects and characteristics applied to other objects are contained in these classes. Some reminders when using them. Mixins should, first and foremost, have concrete qualities and logic. They should not be overridden or implemented by classes that use them. Secondly, these classes are not meant to be self-contained. It’s not a good idea to instantiate them on. They’re designed to give any classes they’re used in more functionality.

You may also like

Leave a Comment