I am trying to build an API view, to handle user management using django rest framework version 2.3.10 with django 1.6. I tried to build a ModelViewSet which based on the URL pk value it would return either current user or public user.
I tried to add a dispatch function which will assigned pk to current user, but it seems like this function is running too soon that its always seeing the user as anonymous
class UserViewSet(viewsets.ModelViewSet):
"""
"""
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = (IsOwnerOrCreateOnly, )
def dispatch(self, request, *args, **kwargs):
if kwargs.get('pk') == 'current' and not request.user.is_anonymous():
kwargs['pk'] = request.user.pk
resp = super(CurrentUserViewSet, self).dispatch(request, *args, **kwargs)
return resp
I tried to do the below, which works
class UserViewSet(viewsets.ModelViewSet):
"""
"""
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = (IsOwnerOrCreateOnly, )
def retrieve(self, request, *args, **kwargs):
if self.kwargs.get('pk') == u'current' and not request.user.is_anonymous():
self.kwargs['pk'] = request.user.pk
return super(CurrentUserViewSet, self).retrieve(request, *args, **kwargs)
but, I don't want to override each and every function on several ModelViewSet classes I have, so, is there a way to use something similar to the dispatcher whereby I can check if the pk is equal to "current" and then assign current user to it?
Another question, how can I change the returned fields programmatically? for example when querying current user I want to include the first and last name from the user model, but when querying by primary key, I want first and last name to not return as response? any suggestions on how todo that?
I got the same problem I solved it by using method "initial" instead of "dispatch"
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = (IsOwnerOrCreateOnly, )
def initial(self, request, *args, **kwargs):
# logic - code #
if kwargs.get('pk') == 'current' and not request.user.is_anonymous():
kwargs['pk'] = request.user.pk
# end #
resp = super(CurrentUserViewSet, self).initial(request, *args, **kwargs)
return resp
see " dispatch "
method in https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/views.py
for better understanding.
Override viewsets.ModelViewSet class with your pk check implementation and use that new class, something like this:
class GenericUserViewSet(viewsets.ModelViewSet):
def retrieve(self, request, *args, **kwargs):
if self.kwargs.get('pk') == u'current' and not request.user.is_anonymous():
self.kwargs['pk'] = request.user.pk
return super(CurrentUserViewSet, self).retrieve(request, *args, **kwargs)
class UserViewSet(GenericUserViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = (IsOwnerOrCreateOnly, )
And for the second question, perhaps creating two serializers (public and current) and changing serializer_class to either one of them in init of GenericUserViewSet may do the trick, I haven't tested this but it's an idea:
class GenericUserViewSet(viewsets.ModelViewSet):
def __init__(self, *args, **kwargs):
if self.kwargs.get('pk') == u'current' and not request.user.is_anonymous():
self.serializer_class = UserSerializer
else:
self.serializer_class = PublicUserSerializer
super(GenericUserViewSet, self).__init__(*args, **kwargs)
I'm assuming that you want to save the current user to your DB model, yes?
If so this should be fairly easy to fix, just add this method to your views:
def pre_save(self, obj):
obj.user = self.request.user
This will execute just before the model is saved. I use this all the time and it works great.
The other thing you can do is write a mixin class in a generic way that does want you want then inherit it in each of the views you need it in. Assuming that is that you have a solution that works, but just don't want to mimic you code all over the place.
Related
I'm using GenericAPIView with the CreateModelMixin to create a model instance. I need my serializer to add additional fields that aren't defined by the user. My Serializer.create method is already set up for this, but I don't know how to pass fields through to the CreateModelMixin.create method. Here's a minimal version of what I have:
class Foo(mixins.CreateModelMixin, generics.GenericAPIView):
permission_classes = [IsAuthenticated]
def get_serializer_class(self):
return FooSerializer
def post(self, request):
return self.create(
request, requester=request.user # Additional field
)
This doesn't work - the requester field isn't passed to FooSerializer.save, so FooSerializer throws an error when it attempts to access requester in FooSerializer.create. Before, I was using APIView and calling the serializer directly, so I could simply:
serializer = FooSerializer(data=request.data)
if serializer.is_valid():
foo = serializer.save(requester=request.user)
Is there any way to achieve this with the GenericAPIView? I want to embrace DRF's DRY-ness and avoid calling serializers in every endpoint method.
Instead of overriding create method you can override perform_create. Also you may need to define post method:
class Foo(mixins.CreateModelMixin, generics.GenericAPIView):
permission_classes = [IsAuthenticated]
def get_serializer_class(self):
return FooSerializer
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
def perform_create(self, serializer):
serializer.save(requester=self.request.user)
I am trying to get some basic equality filtering for my view and if I understand the documentation, I only need the filter_fields() field defined.
Well, they seem to be ignored (/api/organizations?ticker=AMZN lists everything instead of filtering down to a single record):
class OrganizationViewSet(viewsets.ModelViewSet):
queryset = Organization.objects.all()
serializer_class = OrganizationSerializer
pagination_class = CustomPagination
filter_fields = ('sector', 'industry', 'marketplace')
#staticmethod
def pack_persons_to_url(request, data):
data["persons"] = request.build_absolute_uri("/api/persons/%s/" % data["symbol"])
def list(self, request, *args, **kwargs):
response = super(OrganizationViewSet, self).list(request, *args, **kwargs)
for element in response.data["results"]:
self.pack_persons_to_url(request, element)
return response
def retrieve(self, request, *args, **kwargs):
response = super(OrganizationViewSet, self).retrieve(request, *args, **kwargs)
self.pack_persons_to_url(request, response.data)
return response
The first three fields are FKs and the ticker is a CharField. What do I need to fix to make it all work right?
I was experiencing this after upgrading to django-filters 22.1. It seems filter_fields was renamed to filterset_fields.
You need to add DjangoFilterBackend to the filter backends of your viewset
filter_backends = (backends.DjangoFilterBackend, )
My application is using GenericViewSet with ListModelMixin. I have used filter_backends and filter_class to filter out results. (see 'list': serializers.BookingListSerializer from screenshot below)
I am working on the following brief:
Let's say I have a list of animals which are pre-filtered (using filter_backends) and then shown on UI to the user.
Users can further filter results based on some search criteria from UI (let's say name, type, color). These filterations are handled by filter_class.
In a separate Tab on UI which only shows animals of type Dogs rather than the entire collection of animals. And which can again be filtered further based on the name & color.
I must create 2 separate end-points to show both kinds of results to the user (to have more control over results...ya screw DRY!). But I can't figure out how to create them in Django as both animals and dogs use the same django modal and the filter backends and filter class are applied only to the actual modal ie. on the list of animals.
I need simple def list1(request) and def list2(request) where I can filter the query_set based on request params and my filter backends and filter classes.
api.py
class BookingViewSet(
MultipleSerializerMixin,
mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.ListModelMixin,
viewsets.GenericViewSet
):
lookup_field = 'uuid'
queryset = models.Booking.objects.all()
permission_classes = [DRYPermissions, ]
filter_backends = [filters.BookingFilterBackend, DjangoFilterBackend, ]
filter_class = filters.BookingFilter
pagination_class = BookingViewSetPagination
serializer_class = serializers.BookingDetailSerializer
serializer_classes = {
'create': serializers.BookingCreateUpdateSerializer,
'update': serializers.BookingCreateUpdateSerializer,
'duplicate': serializers.BookingCreateUpdateSerializer,
'list': serializers.BookingListSerializer,
'list_drafts': serializers.BookingListSerializer,
'create_draft': serializers.BookingCreateUpdateSerializer,
'submit_draft': serializers.BookingCreateUpdateSerializer,
}
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
booking = services.create_booking(serializer.validated_data)
data = serializers.BookingDetailSerializer(booking, context={'request': request}).data
return response.Created(data)
def update(self, request, *args, **kwargs):
booking = self.get_object()
partial = kwargs.pop('partial', False)
serializer = self.get_serializer(booking, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
booking = services.update_booking(booking, serializer.validated_data)
async('shootsta.bookings.tasks.booking_update_google_calendar_event', booking.pk)
data = serializers.BookingDetailSerializer(booking, context={'request': request}).data
return response.Ok(data)
#detail_route(methods=['POST'], url_path='duplicate')
def duplicate(self, request, *args, **kwargs):
booking = self.get_object()
new_booking = services.duplicate_booking(booking)
data = serializers.BookingDetailSerializer(new_booking, context={'request': request}).data
return response.Created(data)
#list_route(methods=['GET'], url_path='list-drafts')
def list_drafts(self, request, *args, **kwargs):
# Code goes here! Here i'll get some params from url like state and title and then return filtered the results.
pass
#list_route(methods=['POST'], url_path='create-draft')
def create_draft(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
booking = services.create_booking(serializer.validated_data, constants.BookingMode.draft)
data = serializers.BookingDetailSerializer(booking, context={'request': request}).data
return response.Created(data)
#detail_route(methods=['POST'], url_path='submit-draft')
def submit_draft(self, request, *args, **kwargs):
booking = self.get_object()
booking.submit_draft(by=request.user)
booking.save()
data = serializers.BookingDetailSerializer(booking, context={'request': request}).data
return response.Ok(data)
#detail_route(methods=['POST'], url_path='approve')
def approve(self, request, *args, **kwargs):
booking = self.get_object()
booking.approve(by=request.user)
booking.save()
data = serializers.BookingDetailSerializer(booking, context={'request': request}).data
return response.Ok(data)
filters.py
# Standard Library
import operator
from functools import reduce
# Third Party
from django.db.models import Q
from django_filters import rest_framework as filters
from dry_rest_permissions.generics import DRYPermissionFiltersBase
# Project Local
from . import models
class BookingFilterBackend(DRYPermissionFiltersBase):
def filter_list_queryset(self, request, queryset, view):
if request.user.is_role_admin:
return queryset
if request.user.is_role_client:
return queryset.filter(Q(client=request.user.client))
if request.user.is_role_camop:
return queryset.filter(Q(camera_operator=request.user))
return queryset.filter(Q(created_by=request.user))
def filter_booking_title(queryset, name, value):
"""
Split the filter value into separate search terms and construct a set of queries from this. The set of queries
includes an icontains lookup for the lookup fields for each of the search terms. The set of queries is then joined
with the OR operator.
"""
lookups = ['title__icontains', ]
or_queries = []
search_terms = value.split()
for search_term in search_terms:
or_queries += [Q(**{lookup: search_term}) for lookup in lookups]
return queryset.filter(reduce(operator.or_, or_queries))
class BookingFilter(filters.FilterSet):
title = filters.CharFilter(method=filter_booking_title)
class Meta:
model = models.Booking
fields = [
'title',
'state',
'client',
]
class SampleViewset(.....):
#list_route(methods=['GET'])
def list_2(self, request, *args, **kwargs):
myqueryset = MyModel.objects.all() # or whatever queryset you need to serialize
queryset = self.filter_queryset(myqueryset)
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
The key points you should notice here are,
1. The filtering process are being excecuted inside the self.filter_queryset() method, which return a QuerySet after filter applied.
2. You could use self.get_queryset() method in place of myqueryset = MyModel.objects.all() staement, which is the DRF Way of doing such things
UPDATE-1
If you want to use the default queryset , you could use the get_queryset() method as,
class SampleViewset(.....):
#list_route(methods=['GET'])
def list_2(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
or simply,
class SampleViewset(.....):
#list_route(methods=['GET'])
def list_2(self, request, *args, **kwargs):
return self.list(self, request, *args, **kwargs)
I didn't quite get the question but I think you should do the same thing on your custom action that DRF does on its generic list. just call filter_queryset on your initial query for example:
class your_view(....):
...
...
def get_queryset2(self):
return YourotherModel.objects.all() ### or any thing your i.e. specific fiter on your general model
#action(methods=['GET'], detail=False)
def list2(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset2()) ### call filter_queryset on your custom query
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
I'm trying to wire up an API endpoint to allow admins to disable a user via a simple, body-less PATCH command. However, for some reason my View's get_queryset() always returns a 404:
from django.shortcuts import get_object_or_404
from django.contrib.auth.models import User
from .serializers import UserSerializer
# Disable a User
class DisableCompanyUserView(UpdateAPIView):
model = User
serializer_class = UserSerializer
lookup_url_kwarg = 'user_id'
def get_queryset(self):
print(str(self.kwargs)) # returns {'user_id': '12'}
return get_object_or_404(User, pk=self.kwargs[self.lookup_url_kwarg])
---snip---
I thought it might have been a type mismatch (i.e. self.kwargs['user_id'] was returning a string instead of an int) but I tried looking up the User by entering in 12 to the above return (as an integer, obviously) and still got back a 404. I also ripped out the shortcut function call, did the lookup manually, and still got back a 404:
try:
return User.objects.get(pk=self.kwargs[self.lookup_url_kwarg])
# return User.objects.get(pk=12) # also returns a 404
except User.DoesNotExist:
raise Http404()
I'm looking at the raw auth_user table right now and there's a record in there with an id of 12, so I know that that User exists. What am I doing wrong?
And if you try looking it up with
User.objects.get(id=theId)
Does that return any object?
pk is only a short form of id__iexact
EDIT
Have you tried manually doing
User.objects.filter(pk=yourID)[0]
This should get you results. Otherwise, try
User.objects.all()
And look if you can find the user you are looking for
tgdn helped me realize that I was doing something really dumb: I was trying to filter down to a specific user in get_queryset() when I should have been returning all users.
I was able to get things working like this:
# Update a User
class DisableCompanyUserView(UpdateAPIView):
model = User
serializer_class = UserSerializer
lookup_url_kwarg = 'user_id'
def get_queryset(self):
return User.objects.all()
def partial_update(self, request, *args, **kwargs):
kwargs['partial'] = True
return self.update(request, *args, **kwargs)
Which I simplified down to this:
# Update a User
class DisableCompanyUserView(UpdateAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
lookup_url_kwarg = 'user_id'
def partial_update(self, request, *args, **kwargs):
kwargs['partial'] = True
return self.update(request, *args, **kwargs)
I'm trying to initialize the form attribute for MyModelAdmin class inside an instance method, as follows:
class MyModelAdmin(admin.ModelAdmin):
def queryset(self, request):
MyModelAdmin.form = MyModelForm(request.user)
My goal is to customize the editing form of MyModelForm based on the current session. When I try this however, I keep getting an error (shown below). Is this the proper place to pass session data to ModelForm? If so, then what may be causing this error?
TypeError at ...
Exception Type: TypeError
Exception Value: issubclass() arg 1 must be a class
Exception Location: /usr/lib/pymodules/python2.6/django/forms/models.py in new, line 185
Combining the good ideas in Izz ad-Din Ruhulessin's answer and the suggestion by Cikić Nenad, I ended up with a very awesome AND concise solution below:
class CustomModelAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
self.form.request = request #so we can filter based on logged in user for example
return super(CustomModelAdmin, self).get_form(request,**kwargs)
Then just set a custom form for the modeladmin like:
form = CustomAdminForm
And in the custom modelform class access request like:
self.request #do something with the request affiliated with the form
Theoretically, you can override the ModelAdmin's get_form method:
# In django.contrib.admin.options.py
def get_form(self, request, obj=None, **kwargs):
"""
Returns a Form class for use in the admin add view. This is used by
add_view and change_view.
"""
if self.declared_fieldsets:
fields = flatten_fieldsets(self.declared_fieldsets)
else:
fields = None
if self.exclude is None:
exclude = []
else:
exclude = list(self.exclude)
exclude.extend(kwargs.get("exclude", []))
exclude.extend(self.get_readonly_fields(request, obj))
# if exclude is an empty list we pass None to be consistant with the
# default on modelform_factory
exclude = exclude or None
defaults = {
"form": self.form,
"fields": fields,
"exclude": exclude,
"formfield_callback": curry(self.formfield_for_dbfield, request=request),
}
defaults.update(kwargs)
return modelform_factory(self.model, **defaults)
Note that this returns a form class and not a form instance.
If some newbie, as myself, passes here:
I had to define:
class XForm(forms.ModelForm):
request=None
then at the end of the previous post
mfc=modelform_factory(self.model, **defaults)
self.form.request=request #THE IMPORTANT statement
return mfc
i use queryset fot filtering records, maybe this example help you:
.....
.....
def queryset(self, request):
cuser = User.objects.get(username=request.user)
qs = self.model._default_manager.get_query_set()
ordering = self.ordering or () # otherwise we might try to *None, which is bad ;)
if ordering:
qs = qs.order_by(*ordering)
qs = qs.filter(creator=cuser.id)
return qs
Here is a production/thread-safe variation from nemesisfixx solution:
def get_form(self, request, obj=None, **kwargs):
class NewForm(self.form):
request = request
return super(UserAdmin, self).get_form(request, form=NewForm, **kwargs)
class CustomModelAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
get_form = super(CustomModelAdmin, self).get_form(request,**kwargs)
get_form.form.request = request
return get_form
Now in ModelForm, we can access it by
self.request
Example:
class CustomModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(TollConfigInlineForm, self).__init__(*args, **kwargs)
request = self.request
user = request.user