I'm making a RESTful API using Django-Tastypie.
I need to get(retrieve) the values POSTed/send through my form. Here is my code.
class InstallationResource(ModelResource):
class Meta:
queryset = Installation.objects.all()
resource_name = 'installation'
class ApiActionsResource(ModelResource):
installation_id = fields.ForeignKey(InstallationResource, 'installation111')
class Meta:
queryset = Controller.objects.all()
resource_name = 'actions'
allowed_methods = ['post']
fields = ['installation_id']
def obj_create(self, bundle, **kwargs):
print bundle #<Bundle for obj: 'Controller object' and with data: '{'installation_id': u'related'}'>
print kwargs #{}
return super(EnvironmentResource, self).obj_create(bundle, user=bundle.request.user)
When I print bundle, I get <Bundle for obj: 'Controller object' and with data: '{'installation_id': u'12'}'>. I want to get the installation_id from this bundle. How do I get it?
`
The data lies within bundle.data, which is a plain Python dictionary.
You can retrieve the values like this: bundle.data.get('installation_id').
More info on bundle structures here: http://django-tastypie.readthedocs.org/en/latest/bundles.html.
Related
I'm working with Django-Rest-Framework's serializers. I have two serializers one nested with the other.
class NestedSerializer(serializers.Serializer):
value = AttributeValueField(required=True)
name = serializers.CharField(required=True)
class OuterSerializer(serializers.Serializer):
info = serializers.CharField()
nested = NestedSerializer()
In order to validate the nested serializer's data I need to retrieve input data from the parent serializer, something like this:
class NestedSerializer(serializers.Serializer):
...
def validate(self, data):
# of course, it doesn't work, but thats the idea.
info = self.parent.info
# then validate the NestedSerializer with info.
I can't find any way to get access to those input data from the validate method. Any suggestions? Thanks for your help :).
Before validate() method, DRF serializers call to_internal_value(self, data). You will get all data of parent serializer there. So as you defined validate() method in serializer, define to_internal_value() method and catch parent serializer's data.
You can access initial_data on the parent serializer from the nested serializers validate() method. I've also added some code for using the parent fields run_validation() method, which would validate and return the internal value from to_internal_value(), which might be a better than dealing with the initial data.
class NestedSerializer(serializers.Serializer):
def validate(self, data):
# Retrieve the initial data, perhaps this is all you need.
parent_initial_data = self.parent.initial_data
info = parent_initial_data.get("info", None)
# Get the corresponding field and use `run_validation` or `to_internal_value` if needed
if info:
info_field = self.parent.fields["info"]
info = info_field.run_validation(info)
# info = info_field.to_internal_value(info) # If you don't want validation, but do want the internal value
# Do your thing
return data
Try self.root.instance to get the parent instance in a nested serializer.
It might not be the best idea to do it this way, NestedSerializer should not be aware of the parent object. It would make your code difficult to maintain, also it would make NestedSerializer dependent on OuterSerializer.
Instead, define a validate(self, data) method in the OuterSerializer and run the mutual validation there.
Here's what I'm doing now but I'm interested to see other answers..
Basically I've created a custom field for the field in the parent serializer that needs to be accessed in the child serializer - in this case "customer". Then override to_internal_value() to add the field's validated data as an attribute on the parent serializer.
Once it's been added as an attribute it can be accessed on the child serializer through self.parent.<attribute_name> or on child serializer fields by self.root.<attribute_name>
class CustomerField(serializers.PrimaryKeyRelatedField):
def to_internal_value(self, data):
# Set the parent serializer's `customer` attribute to the validated
# Customer object.
ret = super().to_internal_value(data)
self.parent.customer = ret
return ret
class DebitField(serializers.PrimaryKeyRelatedField):
default_related_name = {
'OnAccount': 'onaccounts',
'Order': 'orders'
}
def get_queryset(self):
# Method must be overridden so the `queryset` argument is not required.
return super().get_queryset()
def set_queryset_from_context(self, model_name):
# Override the queryset depending on the model name.
queryset = self.default_related_name[model_name]
self.queryset = getattr(self.parent.customer, queryset)
def to_internal_value(self, data):
# Get the model from the `debit_type` and the object id from `debit`
# then validate that the object exists in the related queryset.
debit_type = data.pop('debit_type')
self.set_queryset_from_context(debit_type)
super().to_internal_value(data)
class PaymentLineSerializer(serializers.ModelSerializer):
debit = DebitField()
class Meta:
model = PaymentLine
fields = (
'id',
'payment',
'debit_type',
'debit', # GenericForeignKey
'amount',
)
def to_internal_value(self, data, *args):
data['debit'] = {
'debit': data.pop('debit'),
'debit_type': data.pop('debit_type'),
}
ret = super().to_internal_value(data)
return ret
def to_representation(self, instance):
data = super().to_representation(instance)
data['debit'] = instance.debit._meta.object_name
return data
class PaymentSerializer(serializers.ModelSerializer):
customer = CustomerField(queryset=Customer.objects.all())
class Meta:
model = Payment
fields = (
'id',
'customer',
'method',
'type',
'date',
'num_ref',
'comment',
'amount',
)
def __init__(self, *args, **kwargs):
self.customer = None
super().__init__(*args, **kwargs)
self.fields['lines'] = PaymentLineSerializer(
context=self.context,
many=True,
write_only=True,
)
You are almost there!!!
Use self.parent.initial_data to access the data given to the parent serializer.
class NestedSerializer(serializers.Serializer):
value = AttributeValueField(required=True)
name = serializers.CharField(required=True)
def validate(self, attrs):
attrs = super().validate(attrs)
the_input_data = self.parent.initial_data
info = the_input_data['info'] # this will not be the "validated data
# do something with your "info"
return attrs
Do not hardcode the field_name
self.parent.initial_data[self.field_name]
I want to update multiple instances of my models. I can currently update them one by one just fine.
I want to be able to update them with a PUT request to my URL:
www.my-api.com/v1/mymodels/
with the request data like so:
[ { "mymodel_id": "id1", "name": "foo"}, { "mymodel_id": "id2", "alert_name": "bar"} ]
If I try it this way now, I receive the following Django error:
Serializers with many=True do not support multiple update by default, only multiple create.
For updates it is unclear how to deal with insertions and deletions.
If you need to support multiple update, use a `ListSerializer` class and override `.update()` so you can specify the behavior exactly.
My model has a Serializer class MyModelSerializer
class MyModelSerializer(ModelSerializerWithFields):
class Meta:
model = MyModel
fields = "__all__"
def to_representation(self, instance):
data = super().to_representation(instance)
if instance.name is None:
del data['name']
return data
ModelSerializerWithFields extends serializers.ModelSerializer.
The View for MyModel is very basic:
class MyModelViewSet(MultipleDBModelViewSet):
serializer_class = MyModelSerializer
queryset = MyModel.objects.none()
MultipleDBModelViewSet extends BulkModelViewSet, and contains
def filter_queryset(self, queryset):
ids = self.request.query_params.get("ids", None)
if ids:
return queryset.filter(pk__in=json.loads(ids))
# returns normal query set if no param
return queryset
At which level do I need to use the ListSerializer class? ie: in ModelSerializerWithFields or in MyModelSerializer? Or somewhere else completely?
If anyone has any examples of this implementation, I'd be very grateful
Serializer Must be inherited from BulkSerializerMixin
So the serializer code will be like
from rest_framework_bulk.serializers import BulkListSerializer, BulkSerializerMixin
class SimpleSerializer(BulkSerializerMixin,
ModelSerializer):
class Meta(object):
model = SimpleModel
# only required in DRF3
list_serializer_class = BulkListSerializer
At Viewset don't forget to use the
filter_queryset method.
So your view will be like
class MyModelViewSet(MultipleDBModelViewSet):
serializer_class = MyModelSerializer
queryset = MyModel.objects.none()
def filter_queryset(self, queryset):
return queryset.filter(<some_filtering>)
I'm trying to update an object using Tastypie Api in Django but I can't find any way to do it...
I would like to send an URL like /api/vote/pk=3, which would update the object with pk=3 and increment the vote number field... Is it possible to do it easily ?
Thanks by advance...
I just tried to create a Ressource like this : but it doesn't work even if i don't do any process. It requires to define obj_get_list etc...
class IphoneVoteRessource(Resource):
id = fields.IntegerField(attribute='id')
name = fields.CharField(attribute='name')
class Meta:
resource_name = 'poi_vote'
object_class = Row
def obj_update(self, bundle, request=None, **kwargs):
# update an existing row
pk = int(kwargs['pk'])
return bundle `
I already used a lot of times Tastypie to get data, but never to update one object..
I have finally used a ModelResource,if somebody wants an example which works :
class VoteResourceAnonymous(ModelResource):
class Meta:
queryset = VoteAnonymous.objects.all()
resource_name = 'vote_object'
# need to send 'vote_value' and 'object_id'
excludes = ['created_at', 'content_type', 'content_object', 'user_agent', 'ip_address']
allowed_methods = ['get', 'post', 'put', 'delete']
always_return_data = True
def obj_create(self, bundle, request=None, **kwargs):
vote_value = bundle.data['vote_value']
object_id = bundle.data['object_id']
tmp_poi = PointOfInterestActivity.objects.get(id=object_id)
content_type_object = ContentType.objects.get_for_model(tmp_poi)
bundle.obj = VoteAnonymous(vote_value=vote_value,
object_id=object_id,
user_agent="",
ip_address="",
content_type=content_type_object,
)
bundle.obj.save()
if vote_value > 0:
bundle.data['new_nb_votes'] = tmp_poi.nb_votes + 1
else:
bundle.data['new_nb_votes'] = tmp_poi.nb_votes - 1
return bundle
I'm looking for a way to add a 'generic' search throught some of my ModelResource.
Using a 'v1' api, I would like to be able to query some of my ModelResources allready registered with this kind of url : /api/v1/?q='blabla'. Then I'd like to recover some of my ModelResourceS that could fill inside the query.
What approach do you think is the best one ?
I tried to build a GenericResource(Resource), with my own class reprensenting row data, without success. Would you have got some links to help me ?
Regards,
For a mobile application that we were creating an API for we created a similar "Search" type resource. Basically we agreed upon a set of types and some common fields that we would show in the search feed on the application. See the code below for the implementation:
class SearchObject(object):
def __init__(self, id=None, name=None, type=None):
self.id = id
self.name = name
self.type = type
class SearchResource(Resource):
id = fields.CharField(attribute='id')
name = fields.CharField(attribute='name')
type = fields.CharField(attribute='type')
class Meta:
resource_name = 'search'
allowed_methods = ['get']
object_class = SearchObject
authorization = ReadOnlyAuthorization()
authentication = ApiKeyAuthentication()
object_name = "search"
include_resource_uri = False
def detail_uri_kwargs(self, bundle_or_obj):
kwargs = {}
if isinstance(bundle_or_obj, Bundle):
kwargs['pk'] = bundle_or_obj.obj.id
else:
kwargs['pk'] = bundle_or_obj['id']
return kwargs
def get_object_list(self, bundle, **kwargs):
query = bundle.request.GET.get('query', None)
if not query:
raise BadRequest("Missing query parameter")
#Should use haystack to get a score and make just one query
objects_one = ObjectOne.objects.filter(name__icontains=query).order_by('name').all)[:20]
objects_two = ObjectTwo.objects.filter(name__icontains=query).order_by('name').all)[:20]
objects_three = ObjectThree.objects.filter(name__icontains=query).order_by('name').all)[:20]
# Sort the merged list alphabetically and just return the top 20
return sorted(chain(objects_one, objects_two, objects_three), key=lambda instance: instance.identifier())[:20]
def obj_get_list(self, bundle, **kwargs):
return self.get_object_list(bundle, **kwargs)
I am trying to use tastypie with non-orm using the redis.
I implemented a custom Resource, like suggested on http://django-tastypie.readthedocs.org/en/v0.9.11/non_orm_data_sources.html
Here is the part of the code:
class OrderResource(Resource):
order_id = fields.CharField(attribute='order_id')
store_url = fields.CharField(attribute='store_url')
products = fields.ListField(attribute='products')
class Meta:
queryset = Order
resource_name = 'order'
allowed_methods = ['get', 'post', 'put', 'delete', 'patch']
authorization = Authorization()
def _client(self):
return redis.Redis('localhost')
def detail_uri_kwargs(self, bundle_or_obj):
kwargs = {}
if isinstance(bundle_or_obj, Bundle):
kwargs['pk'] = bundle_or_obj.obj.order_id
else:
kwargs['pk'] = bundle_or_obj.order_id
return kwargs
def get_object_list(self, request):
query = self._client()
results = list()
for store_url in query.smembers('store_url'):
orders_id = query.hgetall('store_url:%s' % store_url)
for order in orders_id.keys():
order = Order(store_url=store_url, order_id=order)
results.append(order)
return results
def obj_get_list(self, request=None, **kwargs):
# Filtering disabled for brevity...
return self.get_object_list(request)
But when I try to retrieve all orders, the json objects is empty, even with the total_count right.
I checked and the Bundle is right:
<Bundle for obj: '<orders.models.Order object at 0x10c6f1e90>' and with data: '{'order_id': u'1', 'store_url': u'test.com', 'products': [u'a', u'b', u'c'], 'resource_uri': None}'>
What am I doing wrong?
OBS: I can't use django-nonrel
What is Order in your code? With Resource you don't need Meta.queryset. Try to remove it.
Also, make sure Order is serializable. Most probably Tastypie unable to serialize it. Try with simple dictionary objects instead of Order first. Read more about Serialization and Dehydration.
Add the object_class in the Meta solves the problem