Overriding django admin get_queryset() - python

I have two models which is one of them proxy model.
In admin I registered both and overrided get_queryset() method but it is not working as expected.
admin.py
#admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
def get_queryset(self, request):
qs = super().get_queryset(request)
return qs.filter(language='en')
#admin.register(ProxyCategory)
class ProxyCategoryAdmin(CategoryAdmin):
def get_queryset(self, request):
qs = super().get_queryset(request)
return qs.filter(language='zh')
In admin page ProxyCateegoryAdmin not showing objects, if I remove get_queryset() from CategoryAdmin, it works but wanted filter both of them.
Thanks in advance

You can use self.model in the get_queryset method to filter the correct model.
#admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
def get_queryset(self, request):
qs = super().get_queryset(request)
if self.model == Category:
return qs.filter(language='en')
return qs
#admin.register(ProxyCategory)
class ProxyCategoryAdmin(CategoryAdmin):
pass

If you want ProxyCategoryAdmin to return both languages, do this:
#admin.register(ProxyCategory)
class ProxyCategoryAdmin(admin.ModelAdmin):
def get_queryset(self, request):
qs = super().get_queryset(request)
return qs.filter(language_in=['zh','en'])
When you inherit the CategoryAdmin, you end up with 2 filters: EN and ZH instead of EN or ZH

Related

How to write permissions in a viewset with conditional statements in DRF?

I have a viewset written in DRF:
class MyViewSet(ModelViewSet):
serializer_class = MySerializer
queryset = models.MyClass.objects.all()
def get_serializer_class(self):
permission = self.request.user.permission
if permission=='owner' or permission=='admin':
return self.serializer_class
else:
return OtherSerializer
def perform_create(self, serializer):
permission = self.request.user.permission
if permission=='owner' or permission=='admin':
serializer.save()
else:
employee = models.Employee.objects.get(user=self.request.user)
serializer.save(employee=employee)
Here, I am using the following statements in both get_serializer_class and perform_create which looks like a repetitive code:
permission = self.request.user.permission
if permission=='owner' or permission=='admin':
Is there any way to write it once and then use it as a permission_class somehow?
Create a Custom Permission class
https://www.django-rest-framework.org/api-guide/permissions/#custom-permissions
from rest_framework.permissions import BasePermission, SAFE_METHODS
class CustomPermission(BasePermission):
def has_permission(self, request, view):
if request.method in SAFE_METHODS:
return True
permission = self.request.user.permission
if permission=='owner' or permission=='admin':
return True
return False
in Views.py
class MyViewSet(ModelViewSet):
serializer_class = MySerializer
queryset = models.MyClass.objects.all()
permission_classes = (CustomPermission,)

How can I access the serializer in extra actions?

I have the following two classes and I want two merge EventInvitationCreateView into EventInvitationViewSet.
However, I am struggling to bring perform_create into create_invitation as I still need to access serializer. Do you have any input on how to achieve that?
class EventInvitationCreateView(CreateAPIView):
serializer_class = InvitationSerializer
permission_classes = [RetoolPermission]
queryset = Invitation.objects.none()
def perform_create(self, serializer):
email = serializer.validated_data["email"]
event = self.request.event
invitation_exists = Invitation.objects.filter(
email__iexact=email, event=event
).exists()
if invitation_exists:
raise ValidationError("Email already exists")
serializer.save(event=event)
class EventInvitationViewSet(
mixins.CreateModelMixin, mixins.ListModelMixin, viewsets.GenericViewSet
):
permission_classes = [RetoolPermission]
serializer_class = InvitationSerializer
filter_backends = [filters.SearchFilter]
search_fields = ["email"]
queryset = Invitation.objects.none()
def get_queryset(self) -> QuerySet:
return self.request.event.invitations.all()
#action(detail=True, methods=["post"])
def create_invitation(self, request, pk=None):
[...perform_create]
the action is still a standard view's method, so you can use self.get_serializer()
for instance
#action(detail=True, methods=["post"])
def create_invitation(self, request, pk=None):
s = self.get_serializer(data=request.data)
....
If you need to use a "action specific serializer" you can specify the serializer class in the decorator
#action(detail=True, methods=["post"], serializer_class=MyOtherSerializer)
def create_invitation(self, request, pk=None):
s = self.get_serializer(data=request.data) # <-- instance of MyOtherSerializer
....

How to return custom JSON in Django REST Framework

I am trying to return custom json with get_queryset but always get 404 error in response.
class TestViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows groups to be viewed or edited.
"""
queryset = Test.objects.all()
serializer_class = TestSerializer
def get_queryset(self):
if self.request.method == "GET":
content = {'user_count': '2'}
return HttpResponse(json.dumps(content), content_type='application/json')
If I delete everything starting from def I'll got correct response with standard json data. What I am doing wrong?
If you don't need a ModelViewSet and just want custom JSON on a GET request
You can also use an APIView, which doesn't require a model
class MyOwnView(APIView):
def get(self, request):
return Response({'some': 'data'})
and
urlpatterns = [
url(r'^my-own-view/$', MyOwnView.as_view()),
]
With a ModelViewSet
You've put the custom JSON into get_queryset, that's wrong. If you want to use a ModelViewSet, this by itself should be enough:
class TestViewSet(viewsets.ModelViewSet):
queryset = Test.objects.all()
serializer_class = TestSerializer
This ModelViewSet comes with default implementations for .list(), .retrieve(), .create(), .update(), and .destroy(). Which are available for you to override (customize) as needed
Returning custom JSON from .retrieve() and/or .list() in ModelViewSet
E.g. to override .retrieve() to return custom view when retrieving a single object. We can have a look at the default implementation which looks like this:
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.get_serializer(instance)
return Response(serializer.data)
So as an example to return custom JSON:
class TestViewSet(viewsets.ModelViewSet):
queryset = Test.objects.all()
serializer_class = TestSerializer
def retrieve(self, request, *args, **kwargs):
return Response({'something': 'my custom JSON'})
def list(self, request, *args, **kwargs):
return Response({'something': 'my custom JSON'})
There are 2 ways to custom the response in Class-based views with ModelViewSet
Solution 1: custom in views.py
class StoryViewSet(viewsets.ModelViewSet):
permission_classes = (permissions.AllowAny,)
queryset = Story.objects.all()
serializer_class = StorySerializer
def retrieve(self, request, *args, **kwargs):
# ret = super(StoryViewSet, self).retrieve(request)
return Response({'key': 'single value'})
def list(self, request, *args, **kwargs):
# ret = super(StoryViewSet, self).list(request)
return Response({'key': 'list value'})
Solution 2: custom in serializers.py (I recommend this solution)
class StorySerializer(serializers.ModelSerializer):
class Meta:
model = Story
fields = "__all__"
def to_representation(self, instance):
ret = super(StorySerializer, self).to_representation(instance)
# check the request is list view or detail view
is_list_view = isinstance(self.instance, list)
extra_ret = {'key': 'list value'} if is_list_view else {'key': 'single value'}
ret.update(extra_ret)
return ret

Django Class model access self

I'm using a django package that checks whether the browser is mobile. I want to apply it to paginate_by so on a mobile device there's less galleries using self.request.mobile. Here's the class:
class GalleryList(ListView):
model = Gallery
paginate_by = 20
context_object_name = 'galleries'
category = None
def get_queryset(self):
if self.request.mobile:
self.template_name = 'mobile/gallery.html'
qs = Gallery.objects.filter(visible=True,).order_by('-created','-hot')
return qs
You can override the method get_template_names of ListView like the following:
def get_template_names(self):
if self.request.mobile:
return 'mobile/gallery.html'
return 'normal/gallery.html'
EDIT:
For paginate by you can try this:
def get_paginate_by(self, queryset):
if self.request.mobile:
return 5
return 20

Django admin list display optimize queryset

In User model i have a method:
#cached_property
def income(self):
return PartnerIncome.objects.all().aggregate(Sum('income'))['income__sum']*self.share_of_profit
PartnerIncome model:
class PartnerIncome(models.Model):
title = models.CharField(max_length=255)
income = models.FloatField(default=0)
Now i want to show 'inccome' in list_display=('income', ) at admin panel, but each object make extra query to database. How i can make PartnerIncome.objects.all().aggregate(Sum('income')) as a global variable for admin change list...
How about just overriding the get_queryset method in your ModelAdmin?
class UserAdmin(admin.ModelAdmin):
list_display = ('income', ...)
def income(self, obj):
return obj.income
def get_queryset(self, request):
queryset = super(UserAdmin, self).get_queryset(request)
# you logic here to `annotate`the queryset with income
return queryset
Documentation on aggregation:
https://docs.djangoproject.com/en/dev/topics/db/aggregation/
Without me really understanding your models and business logic.. here's a snippet from my own code as an example:
class AuthorAdmin(admin.ModelAdmin):
list_display = ('__str__', 'books_count',)
def books_count(self, obj):
return obj.books_count
def get_queryset(self, request):
return super(AuthorAdmin, self).get_queryset(
request).annotate(books_count=Count('books'))

Categories