Using mixins in class based view - Django - django

So I started using the generic class based views and I gotta say, they do save time. But I was wondering whether I could use mixins to provide generic impl rather than having to code in each view. for e.g.
I have a ListView and DetailView. To restrict the listing and editing, I could override the get_queryset() and filter it by the logged in user. But as you guessed, i would have to do that in each view,
class JediListView(ListView):
def get_queryset(self):
q = <call super>.filter(user=request.user) #assume i have 'login_required' in the urls
class JediDetailView(DetailView):
def get_queryset(self):
q = <call super>.filter(user=request.user) #assume i have 'login_required' in the urls
I could create a new parent class for each of the view but I would still be repeating the code.
class RepublicListView(ListView):
# override get_queryset code as above
class JediListView(RepublicListView):
# repeat fot DetailView, DeleteView, UpdateView
I was wondering about mixins, I am exactly sure how mixins work [from java background, so I am awed and fearful at the same time]

You actually almost found the answer by yourself. You can write the following Mixin:
class UserFilterMixin:
def get_queryset(self):
return <call super>.filter(user=self.request.user)
And then use it in the classes like this:
class RepublicListView(LoginRequiredMixin, UserFilterMixin, ListView):
And so on for the other classes...

You can use LoginRequiredMixin from django-braces.
from django.views.generic import ListView, DetailView
from braces.views import LoginRequiredMixin
class JediListView(LoginRequiredMixin, ListView):
model = JediModel
class JediDetailView(LoginRequiredMixin, Detail):
model = JediModel
As per Chapter-8: Best Practices for Class-Based Views from TWO SCOOPS of DJANGO,
THIRD-PARTY PACKAGES: CBVs + django-braces Are Great Together We feel
that django-braces is the missing component for Django CBVs. It
provides a set of clearly coded mixins that make Django CBVs much
easier and faster to implement. !e next few chapter will demonstrate
it's mixins in various code examples.

Related

Same api endpoint for CRUD operations in Django generic apiview

I have been creating different api endpoint for different requests, for eg every single api for get, post, delete and update in generic apiview. But my frontend developer has told me it's a very bad practice and I need to have a single api for all those 4 requests. When I looked it up in the documentation, there is a ListCreateApiView for listing and creating an object, but I cant use it for delete and also for update. How can I include those two in a single endpoint. I don't use modelset view and also functional view. I mostly use generic api views.
Did you try rest framework's ModelViewSet?
i.e.:
from rest_framework.viewsets import ModelViewSet
Which has all the mixins (CRUD) and you can inherit from it in your API view.
Or you can add these mixins based on your requirements:
from rest_framework.mixins import CreateModelMixin, ListModelMixin, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin
and you can inherit from each one of them seperately. For instance:
Class SomeView(CreateModelMixin, DestroyModelMixin, GenericViewSet):
pass
which has create and delete ability. You can also use mixins with GenericAPIView:
Class SomeView(CreateModelMixin, DestroyModelMixin, GenericAPIView):
pass
One of the easiest ways to achieve this is by use of Generic views.
Simply start by adding this import to your views.py:
from rest_framework import generics
from .serializers import modelnameSerializer
from .model import modelname
Then here goes your generic class based view.
This view is for listing all items in your model of choice
class yourmodelnameList(generics.ListCreateAPIView):
queryset = modelname.objects.all()
serializer_class = modelnameSerializer
This view is for updating, deleting and retrieving:
class modelnameDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = modelname.objects.all()
serializer_class = modelnameSerializer
In urls.py simply add:
path('yourulchoice/',views.modelnameList.as_view()),
path('yourulchoice/<int:pk>/', views.modelnameDetail.as_view()),
Other things to note:
django rest framework is installed.
ensure rest_framework is added in INSTALLED_APPS in settings.py file.
The two url routes should be able to handle your CRUD needs.

Update two or more models at same time with UpdateView

Is there some way to update two or more models at the same time with UpdateView? For example having this:
class PEEncargadoView(UpdateModelMixin,UpdateView):
model = Encargado
form_class = FormEncargado
success_url = '/'
template_name = 'productores/PE/encargado.html'
Update other models besides of Encargado but in this same view
I know that this could be possible overriding get_context_data, but exactly how? if I don't need to pass any variables to the template.
you can't do it with UpdateModelMixin - it's designed for working with a single model.
This question shows how to work with multiple forms in CBV: Django: Can class-based views accept two forms at a time?.
Django has formsets https://docs.djangoproject.com/en/1.8/topics/forms/formsets/ which can be used together with ModelForm to allow edition of multiple models on one page.

override what objects.all() returns when using PassThroughManager in django-model-utils?

I'd like to use
PassThroughManager.
Example covers how to define custom manager methods, but can't find info on how to change the default queryset, ie. objects.all().
How can I specify what my objects.all() will return when using PassThroughManager?
--- edit --
For future readers,
django 1.7 seems to have PassThroughManager built-in
https://docs.djangoproject.com/en/dev/topics/db/managers/#custom-managers-and-model-inheritance
Can't you just override the all() method on your custom QuerySet?
class PostQuerySet(QuerySet):
def all(self):
...
As of Django 3.0+, you can no longer override the all function in QuerySet. The latest solution for this without having to use any third party is:
from django.db import models
class CustomQuerySet(models.query.QuerySet):
pass
class CustomManager(models.Manager):
def get_queryset(self):
qs = CustomQuerySet(self.model, using=self._db)
return qs.filter(...) # your custom logic here
class CustomModel(models.Model):
objects = CustomManager()
Django 3.1 Documentation on custom manager & queryset
See the #jproffitt answer, but if you really need to define that method in manager
I guess you can do:
from django.db import models
from django.db.models.query import QuerySet
from model_utils import managers
class MyManager(models.Manager):
def all(self):
# ... Your custom method
class MyQuerySet(QuerySet):
pass
MyThThroughManager = managers.create_pass_through_manager_for_queryset_class(MyManager, MyQuerySet)
I saw the model utils docs and its code

Django Resuable QuerySet

I have a QuerySet in a view like the following. I would like to be able to use the same query in other views, but I don't want to have to copy and paste the code. This also feels like I would be violating the DRY principal.
If I would like to modify the query at a later date, I would have to change it in all my views, which clearly isn't ideal. Is there a class I should create or a method in my model which would let me call this from many different views? Are there any best practices concerning this?
tasks = Task.objects.filter(user = request.user).order_by('-created_at', 'is_complete')
One of the solutions would be to create a classmethod on the model or extending model's manager.
from django.db import models
Adding classmethod
class MyModel(models.Model):
#classmethod
def get_user_tasks(cls, user):
return cls.objects.filter(...).order_by(...)
Overriding manager
class MyModelManager(models.Manager):
def get_user_tasks(self, user):
return self.filter(...).order_by(...)
class MyModel(models.Model):
objects = MyModelManager()
# and in the view...
queryset = MyModel.objects.get_user_tasks(request.user)

testing admin.ModelAdmin in django

I am trying to find out the best way for testing admin.ModelAdmin in admin.py. Specifically I am overriding the save_model() function which I want to test. From the research I have done, the only solution I have found was writing a request/response test and then query the database.
As suggested in Udi's answer, we can study Django's own ModelAdmin tests, to determine the basic ingredients for a ModelAdmin test. Here's a summary:
Basic ingredients
In addition to the Django TestCase stuff, the basic ingredients are:
An instance of AdminSite:
from django.contrib.admin.sites import AdminSite
Your model class and corresponding ModelAdmin (sub)class:
from my_app.models import MyModel
from my_app.admin import MyModelAdmin
Optionally, depending on your needs, a (mock) request and/or form.
Recipe
The first two ingredients are required to create an instance of your (custom) ModelAdmin:
my_model_admin = MyModelAdmin(model=MyModel, admin_site=AdminSite())
Based on the ModelAdmin source, the default save_model implementation only requires an instance of your model, so it can be called, for example, as follows:
my_model_admin.save_model(obj=MyModel(), request=None, form=None, change=None)
# some test assertions here
It all depends on what your save_model does, and what you want to test.
Suppose your save_model checks user permissions, then you would need to provide a request (i.e. the third ingredient) with a valid user, in addition to the model instance:
from unittest.mock import Mock
...
my_user = User.objects.create(...)
my_model_admin.save_model(
obj=MyModel(), request=Mock(user=my_user), form=None, change=None
)
# some test assertions here
Here we use unittest.mock.Mock to create a mock-request. Based on the Django test source, a minimal request consists of a Python object with a user attribute.
The user attribute may refer to a mock user, or an actual instance of your AUTH_USER_MODEL, depending on your needs. An alternative would be to use django.test.RequestFactory.
This basic approach applies to the other ModelAdmin methods as well.
Check out Django's ModelAdminTests for examples.
You can specify custom modelform for modeladmin then simply test this modelform ;)
https://docs.djangoproject.com/en/1.8/ref/contrib/admin/#django.contrib.admin.ModelAdmin.form
forms
class SomeModelForm(forms.ModelForm):
class Meta:
model = SomeModel
admin
class SomeModelAdmin(admin.ModelAdmin):
form = SomeModelForm
admin.site.register(SomeModel, SomeModelAdmin)
tests
class TestSomeModel(TestCase):
def test_form(self):
form = SomeModelForm(**kwargs)
self.assertTrue(form.is_valid())
# ...