I would like to use an existing models.py filtering classmethod in my API.
The thing is I want to avoid to write the same logic twice, and want to keep logic in models (not in API). Here is what I did for now:
In models.py:
class Deal(models.Model):
# some attributes
#classmethod
def get_filtered_deals(cls, client, owner):
return cls.objects.filter(... # complex filtering rules here, I want to keep this logic here, not duplicate it in api.py!!!
But I'm stuck because I don't know how to call the get_filtered_deals() classmethod in my Deal linked resource in Tastypie. I tried something like this:
class Deals(ModelResource):
class Meta(CommonResourceMeta):
queryset = Deal.objects.all()
resource_name = "deals"
list_allowed_methods = ['get']
authorization = DealAuthorization()
class DealAuthorization(Authorization):
def read_list(self, object_list, bundle):
return object_list.get_filtered_deals(
owner=bundle.request.user,
client=int(request.GET['arg2']))
This obviously does not work since object_list has no method named get_filtered_deals
Thanks for your help!
That was pretty simple...
class DealAuthorization(Authorization):
def read_list(self, object_list, bundle):
object_list = Deal.get_filtered_deals(
owner=bundle.request.user,
client=int(request.GET['arg2']))
return object_list
Related
I'm trying to use the following permission class in many apps in the project, the only change needed is the model class that user data is checked from.
Permission class:
class IsAuthorOrForbidden(permissions.BasePermission):
"""
Check if the requesting user the author or not
"""
def __init__(self, modelClass):
self.modelClass = modelClass
def has_permission(self, request, view):
# get the needed model instance or 404 if not available
instance = get_object_or_404(self.modelClass, pk=view.kwargs['pk'])
# Check if the requesting user is the author
if instance.user == request.user:
return True
return False
The permission class in the view class:
class TextNoteGenerateShareKeyAPIView(generics.UpdateAPIView):
"""
Generate a new text note share key
"""
authentication_classes = (TokenAuthentication,)
permission_classes = (IsAuthenticated,
IsAuthorOrForbidden(modelClass=TextNote))
...
When I run the tests, i get this error:
return [permission() for permission in self.permission_classes]
TypeError: 'IsAuthorOrForbidden' object is not callable
Is it possible to do it like this or should I write this permission class in every app in my project?
You can use object level permission:
class IsAuthorOrForbidden(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
return obj.user == request.user
and add it to the view like this:
permission_classes = (IsAuthenticated,
IsAuthorOrForbidden)
Explanation on why the error is occurring
To understand the error, you need to see the implementation of get_permissions method in GitHub source code:
#copy pasted from GitHub
def get_permissions(self):
"""
Instantiates and returns the list of permissions that this view requires.
"""
return [permission() for permission in self.permission_classes]
Here it is making a list of objects from self.permission_classes, meaning permission_classes are suppose to have Class, not objects.
You are adding a IsAuthorOrForbidden object to the permission classes, where you need to add the class. If you want to put a override, then it should be done here:
class TextNoteGenerateShareKeyAPIView(generics.UpdateAPIView):
def get_permissions(self):
return [IsAuthenticated(), IsAuthorOrForbidden(modelClass=TextNote)]
But this is a hackish solution where solution in the original answer is the proper way to implement in DRF.
For those who are using "viewsets" and want to allow only the author to access the content/items you can simply use a mixin like below and use that mixin in whichever viewsets that requires.
# mixin
class FilterByUserMixin:
def get_queryset(self, *args, **kwargs):
queryset = super().get_queryset(*args, **kwargs)
return queryset.filter(user=self.request.user)
# viewsets
class WhateverViewSet(FilterByUserMixin, viewsets.ModelViewSet):
One way to make the OP's code work is to add a __call__ method to the permission class that just returns the object.
class IsAuthorOrForbidden(permissions.BasePermission):
def __call__(self):
return self
This will avoid the TypeError that was being thrown when the permission_classes are being instantiated into objects, by simply returning the object when it is called. This allows for passing arguments to the permission_classes like the OP wanted.
I have a website that is essentially a wiki for a DnD campaign that I am participating in. As such it has articles of Creatures, Characters, Locations and more. I wanted to use Viewsets to access them easily and wanted to use a Viewset action (together with a custom router) to be able to look for individual records not through pk, but through various query-parameters.
I already have something that works for this, now I would like to apply some inheritance to it to not repeat myself. What I'd like to do is something like this:
class WikiBaseViewset (viewsets.ModelViewSet):
detail_with_params_url_pattern_suffix: str
#action(detail=True, url_name="detail-params", url_path=detail_with_params_url_pattern_suffix)
def detail_through_params(self, request, **kwargs):
if self.detail_with_params_url_pattern_suffix == "":
raise InvalidViewsetURLException("URL of view 'detail_through_params' of WikiBaseViewset is not defined!")
model = self.serializer_class.Meta.model
instance = get_object_or_404(model, **kwargs)
serializer = self.get_serializer(instance)
return Response(serializer.data)
class CharacterSerializer (serializers.HyperlinkedModelSerializer):
class Meta:
model = wiki_models.Character
fields = '__all__'
class CharacterViewSet(WikiBaseViewset):
"""Called with URLs: character, character/<str: name>"""
serializer_class = CharacterSerializer
queryset = wiki_models.Character.objects.all()
detail_with_params_url_pattern_suffix = "(?P<name__iexact>.+)"
However, I'm struggling over the fact that the decorator absolutely requires the URL parameter in the base class. Otherwise the code just doesn't compile due to a NameError complaining that detail_with_params_url_pattern_suffix is not defined. If you were to set detail_with_params_url_pattern_suffix="" in the base-class in order to not get an Error when your code is compiled, that still wouldn't matter, as the decorator from my experiments so far still grabs the value of that variable from WikiBaseViewset not CharacterViewSet.
How can I rewrite my BaseClass so that this works? Is there even a way?
I did not find a fully satisfying answer to this problem, but in the end acquiesced with this solution, as it was better than copy pasting.
You might not be able to inherit viewset actions, but you sure can inherit individual methods and then just overwrite them in the child and throw a decorator on top. This lead to this structure:
class WikiBaseViewset (viewsets.ModelViewSet):
detail_with_params_url_pattern_suffix: str
def detail_through_params(self, request, **kwargs):
model = self.serializer_class.Meta.model
instance = get_object_or_404(model, **kwargs)
serializer = self.get_serializer(instance)
return Response(serializer.data)
class CharacterSerializer (serializers.HyperlinkedModelSerializer):
class Meta:
model = wiki_models.Character
fields = '__all__'
class CharacterViewSet(WikiBaseViewset):
"""Called with URLs: character, character/<str: name>"""
serializer_class = CharacterSerializer
queryset = wiki_models.Character.objects.all()
#action(detail=True, url_name="detail-params", url_path="(?P<name__iexact>.+)")
def detail_through_params(self, request, **kwargs):
return super().detail_through_params(request, **kwargs)
I'm using Django with the REST Framework. In a serializer, I would like to assign a field value based on a view or request (request.data['type']) parameter, so I need the view/request in the context.
I succeeded, but only in a cumbersome way, and I am looking into ways to simplify the code. Here's the successful approach (omitting irrelevant fields):
class TypeDefault(object):
def set_context(self, serializer_field):
view = serializer_field.context['view'] # or context['request']
self.type = view.kwargs['type'].upper()
def __call__(self):
return self.type
class RRsetSerializer(serializers.ModelSerializer):
type = serializers.CharField(read_only=True, default=serializers.CreateOnlyDefault(TypeDefault()))
class Meta:
model = RRset
fields = ('type',)
read_only_fields = ('type',)
To simplify things, I tried removing the TypeDefault class, and replacing the type serializer field by
type = serializers.SerializerMethodField()
def get_type(self, obj):
return self.context.get('view').kwargs['type'].upper() # also tried self._context
However, context.get('view') returns None. I am unsure why the view context is not available here. My impression is that it should be possible to get the desired functionality without resorting to an extra class.
As a bonus, it would be nice to specify the default in the field declaration itself, like
type = serializers.CharField(default=self.context.get('view').kwargs['type'].upper())
However, self is not defined here, and I'm not sure what the right approach would be.
Also, I am interested if there is any difference in retrieving information from the view or from the request data. While the context approach should work for both, maybe there's a simpler way to get the CreateOnlyDefault functionality when the value is obtained from request data, as the serializers deals with the request data anyways.
Edit: Per Geotob's request, here is the code of the view that calls the serializer:
class RRsetsDetail(generics.ListCreateAPIView):
serializer_class = RRsetSerializer
# permission_classes = ... # some permission constraints
def get_queryset(self):
name = self.kwargs['name']
type = self.kwargs.get('type')
# Note in the following that the RRset model has a `domain` foreign-key field which is referenced here. It is irrelevant for the current problem though.
if type is not None:
return RRset.objects.filter(domain__name=name, domain__owner=self.request.user.pk, type=type)
else:
return RRset.objects.filter(domain__name=name, domain__owner=self.request.user.pk)
In urls.py, I have (among others):
url(r'^domains/(?P<name>[a-zA-Z\.\-_0-9]+)/rrsets/$', RRsetsDetail.as_view(), name='rrsets'),
url(r'^domains/(?P<name>[a-zA-Z\.\-_0-9]+)/rrsets/(?P<type>[A-Z]+)/$', RRsetsDetail.as_view(), name='rrsets-type'),
SerializerMethodField is a read-only field so I do not think it will work unless you set a default value... and you are back to the same problem as with CharField.
To simply things you could get rid of serializers.CreateOnlyDefault:
class RRsetSerializer(serializers.ModelSerializer):
type = serializers.CharField(read_only=True, default=TypeDefault())
If you want something more dynamic, I can only think of something like this:
class FromContext(object):
def __init__(self, value_fn):
self.value_fn = value_fn
def set_context(self, serializer_field):
self.value = self.value_fn(serializer_field.context)
def __call__(self):
return self.value
class RRsetSerializer(serializers.ModelSerializer):
type = serializers.CharField(read_only=True,
default=FromContext(lambda context: context.get('view').kwargs['type'].upper()))
FromContext takes a function during instantiation that will be used to retrieve the value you want from context.
All in all, your second approach above is the correct one:
Use serializers.SerializerMethodField and access self.context from the serializer method:
class SomeSerializer(serializers.ModelSerializer):
type = serializers.SerializerMethodField()
def get_type(self, obj):
return self.context['view'].kwargs['type'].upper()
The view, request and format keys are automatically added to your serializer context by all of the DRF generic views (http://www.django-rest-framework.org/api-guide/generic-views/#methods at the end of the section). This works just fine.
If you are creating a serializer instance manually, you will have to pass context=contextDict as an argument, where contextDict is whatever you need it to be (http://www.django-rest-framework.org/api-guide/serializers/#including-extra-context).
As #Michael has pointed out in another answer, the SerializerMethodField will be read only. But going by your first example (type = serializers.CharField(read_only=True.....) this seems to be what you want.
Now my code is like:
def use_item(request):
itemname = request.get('value')
if itemname == 'item1':
#do something
if itemname == 'item2':
#do something else
Can I do it in the following way?
views.py
class use_item():
def use_item(self,request):
itemname = request.get('value')
use = getattr(self,itemname) # say itemname is 'item1'
use()
def item1(self,request):
#do something
def item2(self,request):
#do something else
I've tried the second method but it seems that I was not doing it right.
And the reason I want to do it in this way is that I hope to group the methods that they'd be more organized.
the actual code
#views.py
class use_item():
def useitem(self,request):
itemname = request.POST.get('value')
use = getattr(self,itemname)
use()
def jelly(self,request,topic_id):
t = topic.objects.get(id=topic_id)
t.top = True
t.time_topped = datetime.datetime.now()
t.save()
#urls.py
url(r'^use/item/(?P<topic_id>\d+)/$', 'use_item.use_item', name='use_item'),
If you want to have a better organization of your code, and reuse some code accross different views, instead of pasting it where you need, you may use the Django class based views:
# views.py
from django.views import View
class use_item(View):
def get(self, request, *args, **kwargs):
itemname = request.POST.get('value')
use = getattr(self,itemname)
use()
def item1(self,request):
#do something
def item2(self,request):
#do something else
# urls.py
from package.views import use_item
urlpatterns = [
# [...]
url(r'^use/item/(?P<topic_id>\d+)/$', use_item.as_view(), name='use_item'),
# [...]
]
But, if at some point you need to call item1() or item2() from another view (is it the reason you mentioned the other view jelly ?), you will see that it is not possible.
One solution could be moving the common methods in another class and make sure your views inherit it. This is often called mixins in django world.
# views.py
from django.views import View
class ItemsRelatedLMixin:
def item1(self, request):
#do something
def item2(self, request):
#do something else
class use_item(ItemsRelatedLMixin, View):
def get(self, request, *args, **kwargs):
itemname = request.POST.get('value')
use = getattr(self,itemname)
use()
class jelly(ItemsRelatedLMixin, View):
def get(self, request, topic_id):
t = topic.objects.get(id=topic_id)
t.top = True
t.time_topped = datetime.datetime.now()
t.save()
Now, your views jelly and use_item call the common methods. Of course you can define new methods in a view to make them available only from that view. You could also create class members to store values you use often etc. Keep in mind that each request received by Django will trigger creation of a new instance of your view class (you can't keep data stored between 2 requests in class members).
In Django, view functions are usually organized in modules and not in classes.
To keep things organized, use more than one views module: views.py, foo_views.py, bar_views.py, or: views/__init__.py, views/foo.py, views/bar.py.
You need to provide the view in the signature of the class. i.e.:
from django.views import [your_View_name]
Then provide the same view in class definition;
class use_item(your_View_name):
def useitem(self,request):
itemname = request.POST.get('value')
use = getattr(self,itemname)
use()
If you are defining your class for the same view,
class use_item(self):
def useitem(self,request):
itemname = request.POST.get('value')
use = getattr(self,itemname)
use()
You may refer Django docs on Class-Based-View for more in-depth knowledge.
UPDATE:
When you are calling your function useitem you need to use the instance of your class as follows:
user_instance = views.use_item() //Create instance of your class
user_instance.useritem() //call your function using above instance
I have a model which relates to many models, like this:
class Father:
son = # Foreign key to Son model
class Son:
#property
def son_daughters:
if ... :
obj = TypeA.objects.get(...)
elif ... :
obj = TypeB.objects.get(...)
else:
obj = TypeC.objects.get(...)
return obj
I would like to get Father data from daughter name or type. I have this filter class where I need to send two query set parameters related to daughter in order to get daughter ids and apply it as a filter to Father. This is my filter class:
class FatherFilter(django_filters.rest_framework.FilterSet):
def daughter(self, method_name, args, **kwargs):
print(method_name, args, kwargs)
...
daughter_id = django_filters.NumberFilter(method=daughter)
But when I call this endpoint I just get one query parameter and not all.
Is there a way to get the query parameters inside this method instead of just one?
Thanks in advance.
In order to achieve this, I found that Django Rest Framework has a class that extends from django_filters. This class is called BaseFilterBackend and can be used to extend the default backend for filtering any request. So what I did was adding a class extended from BaseFilterBackend like this:
from rest_framework import filters
class FatherFilterBackend(filters.BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
daughter_id = request.query_params.get("daughter_id", None)
daughter_name = request.query_params.get("daughter_name", None)
if daughter_id and daughter_name:
kwargs = {
daughter_name: daughter_id
}
queryset = queryset.filter(**kwargs)
return queryset
This filter will apply before others, so even if you are using a FilterSet class, you will not lose the filters from your BaseFilterBackend. The problem with this solution is that relies in rest_framework.filters package, a filter that its not related to django_filters.
This might not be the best way to achieve this, so if you have better ideas please add them to help others with a similar problem.