I'm using Django REST Framework's ModelViewSet. Inside "ModelViewSet-1", I need to break down a POST (create) request into 3 parts. The first part will be used by "ModelViewSet-1" and I need to delegate the other two parts to "ModelViewSet-2" and "ModelViewSet-3" respectively. Can someone suggest a workflow?
For now, I'm thinking of using python's 'requests' library, to send circular HTTP calls from 'ModelViewSet-1" to the other ModelViewSets. But that doesn't seem elegant.
Calling other ModelViewSets is possible, but not recommended (by me). But here's how you could do it if that's what you really want to do:
class ViewSet3(ModelViewSet):
def create(self, request):
# Preproccess
....
# Call view 1 & 2
response1 = ViewSet1.as_view({'post': 'create'})(request)
response2 = ViewSet2.as_view({'post': 'create'})(request)
However, it's more likely that you just need a custom create method on your serializer to handle creating all of the other objects, not multiple views (I assume you're using JSON).
class View3Serializer(serializers.Serializer):
# my nested fields
view_1_data = View2Serializer(required=False)
view_2_data = View3Serializer(required=False)
def create(self, validated_data):
view_1_data = validated_data.pop('view_1_data', None)
view_2_data = validated_data.pop('view_2_data', None)
# Create my View3 object normally
instance = View3Model.objects.create(**validated_data)
# Create other objects with other data here
....
return instance
Related
I am using DjangoRestApi and while it works like a charm with queryset (orm-based) views, I am struggling to make views that use different back-end to behave same way orm-based views are. Notably I want to add filters and have them cast and validated automatically.
Pseudo code below:
class NewsFilter(django_filters.FilterSet):
category = django_filters.NumberFilter(name='category')
limit = django_filters.NumberFilter(name='limit')
page = django_filters.NumberFilter(name='page')
class NewsView(generics.APIView):
filter_class = NewsFilter
def get(self, request):
filters = self.filter_class(??) # not sure, what to put here
payload = logic.get_business_news(**filters.data) # same
return Response(payload, status=status.HTTP_200_OK)
Any hint how to tackle problem will be appreciated.
Ultimate goal is to:
user types something into url or sends via POST, django-rest intercepts relevant values, extracts them, casts them into correct type and return as a dictionary
filters are displayed as they would if serializer was ORM based
The function signature to any single filter is like
class MyFilter(django_filters.Filter):
def filter(self,queryset,value):
[...]
The function signature to a FilterSet is:
def __init__(self, data=None, queryset=None, prefix=None, strict=None):
So, it looks like you pass in request.GET as data param and then pass in your queryset.
Can Django's user_passes_test() access view parameters?
For example I have view that receives an id to retrieve specific record:
def property(request, id):
property = Property.objects.get(id=int(id))
The record has a field named user_id that contains the id for user that originally created record. I want users to be able to view only their own records otherwise be redirected.
I'd like to use a custom decorator which seems simple and clean.
For custom decorator is there some variation of something like this that will work?
#user_passes_test(request.user.id = Property.objects.get(id=int(id)).id, login_url='/index/')
def property(request, id):
property = Property.objects.get(id=int(id))
I have tried creating separate test_func named user_is_property_owner to contain logic to compare current user to property record user_id
#user_passes_test(user_is_property_owner(id), login_url='/index/')
def property(request, id):
property = Property.objects.get(id=int(id))
def user_is_property_owner(property_id):
is_owner = False
try:
Property.objects.filter(id=property_id, user_id=user_id).exists()
is_owner = True
except Property.DoesNotExist:
pass
But having trouble getting current user id and the property id from the request into the user_is_property_owner decorator function.
EDIT to add solution I was using. I did test inside each view test was required. It is simple. i thought using a decorator might be prettier and slightly more simple.
def property(request, id):
# get object
property = Property.objects.get(id=int(id))
# test if request user is not user id on property record
if request.user.id != property.user_id:
# user is not same as property user id so redirect to index
return redirect('index')
# rest of the code if request user is user_id on property record
# eg it is ok to let user into view
Typically, (using class based views), I'll handle this in the get_queryset method so it would be something like
class PropertyDetail(DetailView):
def get_queryset(self):
return self.request.user.property_set.all()
and that will give a 404 if the property isn't for the current user. You might prefer to use a project like django-guardian if you end up with more permission relationships than just Property.
If you take a look at UserPassesTestMixin you'll see that it processes the test_func before calling dispatch so you'll have to call self.get_object(request) yourself if you decide to go that route.
I'm using the Django Rest Framework 3.2. I have a model "Report" with a ForeignKey to the model "Office".
I want to set the report.office based on the request.
This is how I did it (which I don't like, because it requires duplication):
In my view I differ between GET or POST/PUT/PATCH requests, where GET uses a nested Serializer representation, and POST/PUT/PATCH uses flat serializer representations.
Then I do in my view:
def perform_create(self, serializer):
serializer.save(office=Office.objects.get_only_one_from_request(self.request))
and in my FlatOfficeSerializer:
def validate(self, attrs):
if self.instance is not None:
instance = self.instance
instance_fields = instance._meta.local_fields
for f in instance_fields:
f = self.get_initial().get(f.name)
else:
instance = self.Meta.model(**attrs)
request = self.context.get('request')
instance.office = Office.objects.get_only_one_from_request(request)
instance.clean()
return attrs
It is clear that there is a duplication there and although it is working I don't like it. Without the duplication the validation fails (of course) on the instance.clean(), which calls the clean method of the model. I really need this, because my business logic is in the model's clean and save methods.
I am trying to update data via Django Rest PUT method.
class TableView(generics.ListAPIView, generics.UpdateAPIView, generics.CreateAPIView):
serializer_class = TableSerializer
def update(self, request, *args, **kwargs):
if kwargs.__len__() != 0:
tableid = kwargs['id']
mycol = request.DATA['col']
Table.objects.filter(id=tableid).update(col=mycol)
So, this works but data is updated by Table.object which is model. Is there any generic way to update data? I mean, if I PUT col1 and col2 data, it will update them.. If I send only col1, it will update just it..
yes you can use something like this
def update(self, instance,validated_data):
# The instance is the already saved object
instance.col=validated_data['col']
instance.col2=validated_data['col2']
instance.save()
return instance
check this link for the .update() method http://www.django-rest-framework.org/api-guide/serializers/#writable-nested-representations
This is what PATCH requests does for you. However, in some cases PATCH is not supported (some older versions of Lighttpd for instance) or you might need to use PUT for legacy reasons.
The good thing is that PATCH and PUT requests are almost the same in Django REST Framework. They share most of the code, and the main difference is that PATCH requests sets a parameter partial to True.
What you could try to do is, in your update() method by manually settings the partial flag and passing it along to your serializer like:
def update(self, request, *args, **kwargs):
partial = True
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=partial)
This should only update the parts that was passed as arguments in the request.
Normally a PATCH request will call the perform_update() function in your ModelViewSet, and just set the partial flag to true and then call the update() function after that. Source: https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/mixins.py#L76
Some info from the Django REST Framework page about the update mixin: http://www.django-rest-framework.org/api-guide/generic-views/#updatemodelmixin
I have a model with a version number in it. I want it to self-increment when new data is posted with an existing id via TastyPie. I'm currently doing this via the hydrate method, which works as long as two users don't try to update at once:
class MyResource(ModelResource):
...
def hydrate_version(self, bundle):
if 'id' in bundle.data:
target = self._meta.queryset.get(id=int(bundle.data['id']))
bundle.data['version'] = target.version+1
return bundle
I'd like to do this more robustly by using Django's F() expressions, e.g.:
def hydrate_version(self, bundle):
if 'id' in bundle.data:
from django.db.models import F
target = self._meta.queryset.get(id=int(bundle.data['id']))
bundle.data['version'] = F('version')+1
return bundle
However, this gives me an error:
TypeError: int() argument must be a string or a number, not 'ExpressionNode'
Is there a way to more robustly increment the version number with TastyPie?
thanks!
I would override the save() method for your Django Model instead, and perform the update there. That has the added advantage of ensuring the same behavior regardless of an update from tastypie or from the django/python shell.
def save(self, *args, **kwargs):
self.version = F('version') + 1
super(MyModel, self).save(*args, **kwargs)
This has been answered at github here, though I haven't tried this myself yet. To quote from that link:
You're setting bundle.data inside a hydrate method. Usually you modify bundle.obj in hydrate methods and bundle.data in dehydrate methods.
Also, those F objects are meant to be applied to Django model fields.
I think what you want is:
def hydrate_version(self, bundle):
if bundle.obj.id is not None:
from django.db.models import F
bundle.obj.version = F('version')+1
return bundle