Update an object using Tastypie Django - django

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

Related

How do I customize the text of the select options in the api browser?

I am using rest_framework v3.1.3 in django 1.8. I am pretty new to django.
Here are the relevant model definitions
#python_2_unicode_compatible
class UserFitbit(models.Model):
user = models.OneToOneField(User, related_name='fituser')
fitbit_user = models.CharField(max_length=32)
auth_token = models.TextField()
auth_secret = models.TextField()
#this is a hack so that I can use this as a lookup field in the serializers
#property
def user__userid(self):
return self.user.id
def __str__(self):
return self.user.first_name + ' ' + self.user.last_name
def get_user_data(self):
return {
'user_key': self.auth_token,
'user_secret': self.auth_secret,
'user_id': self.fitbit_user,
'resource_owner_key': self.auth_token,
'resource_owner_secret': self.auth_secret,
'user_id': self.fitbit_user,
}
def to_JSON(self):
return json.dumps(self, default=lambda o: o.__dict__,
sort_keys=True, indent=4)
class Challenge(models.Model):
name=models.TextField()
status=models.TextField() #active, pending, ended, deleted
start_date=models.DateField()
end_date=models.DateField()
#members=models.ManyToManyField(UserFitbit)
members=models.ManyToManyField(User)
admin=models.ForeignKey(UserFitbit,related_name='admin')
#for each member get stats between the start and end dates
def memberstats(self):
stats = []
for member in self.members.all():
fbu = UserFitbit.objects.filter(user__id=member.id)
fu = UserData.objects.filter(userfitbit=fbu)
fu = fu.filter(activity_date__range=[self.start_date,self.end_date])
fu = fu.annotate(first_name=F('userfitbit__user__first_name'))
fu = fu.annotate(user_id=F('userfitbit__user__id'))
fu = fu.annotate(last_name=F('userfitbit__user__last_name'))
fu = fu.values('first_name','last_name','user_id')
fu = fu.annotate(total_distance=Sum('distance'),total_steps=Sum('steps'))
if fu:
stats.append(fu[0])
return stats
def __str__(self):
return 'Challenge:' + str(self.name)
class Meta:
ordering = ('-start_date','name')
And here is the serializer for the challenge
class ChallengeSerializer(serializers.ModelSerializer):
links = serializers.SerializerMethodField(read_only=True)
memberstats = MemberStatSerializer(read_only=True,many=True)
#these are user objects
#this should provide a hyperlink to each member
members = serializers.HyperlinkedRelatedField(
#queryset defines the valid selectable values
queryset=User.objects.all(),
view_name='user-detail',
lookup_field='pk',
many=True,
)
class Meta:
model=Challenge
fields = ('id','name','admin','status','start_date','end_date','members','links','memberstats',)
read_only_fields = ('memberstats','links',)
def get_links(self, obj) :
request = self.context['request']
return {
'self': reverse('challenge-detail',
kwargs={'pk':obj.pk},request=request),
}
As you can see the Challenge has a many to many relationship with User. This is the built in User model from django not UserFitBit defined here.
With these definitions when I go to the api browser for a challenge I need to be able to select the users based on their name, but the select only shows their User id property and the hyperlink url. I would like the members to be User objects, but I don't know how to change the text for the select options since I don't think I can change the built in User object. What is the best way to change the select box options to show the users name from the User object rather than the username field and hyperlink?
Here is an image:
I'm not sure if this is the best way but after reading DRF's source code, I would try this.
Subclass the HyperlinkedRelatedField and override the choices property.
import six
from collections import OrderedDict
class UserHyperLinkedRelatedField(serializers.HyperLinkedRelatedField):
#property
def choices(self):
queryset = self.get_queryset()
if queryset is None:
return {}
return OrderedDict([
(
six.text_type(self.to_representation(item)),
six.text_type(item.get_full_name())
)
for item in queryset
])
then would simply replace the field in the serializer.
members = UserHyperlinkedRelatedField(
queryset=User.objects.all(),
view_name='user-detail',
lookup_field='pk',
many=True,
)
The DRF docs also mentioned that there's a plan to add a public API to support customising HTML form generation in future releases.
Update
For DRF 3.2.2 or higher, there will be an available display_value method.
You can do
class UserHyperLinkedRelatedField(serializers.HyperLinkedRelatedField):
def display_value(self, instance):
return instance.get_full_name()
Because this is a many related field I also had to extend the ManyRelatedField and override the many_init method of the RelatedField to use that class. Can't say I understand all of this just yet, but it is working.
class UserManyRelatedField(serializers.ManyRelatedField):
#property
def choices(self):
queryset = self.child_relation.queryset
iterable = queryset.all() if (hasattr(queryset, 'all')) else queryset
items_and_representations = [
(item, self.child_relation.to_representation(item))
for item in iterable
]
return OrderedDict([
(
six.text_type(item_representation),
item.get_full_name() ,
)
for item, item_representation in items_and_representations
])
class UserHyperlinkedRelatedField(serializers.HyperlinkedRelatedField):
#classmethod
def many_init(cls, *args, **kwargs):
list_kwargs = {'child_relation': cls(*args, **kwargs)}
for key in kwargs.keys():
if key in MANY_RELATION_KWARGS:
list_kwargs[key] = kwargs[key]
return UserManyRelatedField(**list_kwargs)
members = UserHyperlinkedRelatedField(
queryset=User.objects.all(),
view_name='user-detail',
lookup_field='pk',
many=True,
)

How can I get the user requesting a POST through Tastypie in a custom view

Inside of a Django 1.5.5 project I am trying to allow mobile clients to bet on encounters through the Tastypie's API. After reading POST datas I can then save the Bet object, but I need the User instance for that matter.
I know bundle.request.user but this reference isn't available in custom views (used with prepend_urls like here.
In my custom view, request.user refers to AnonymousUser. The API resources are protected using Tastypie's ApiKeyAuthentication (do I need to get the current User with the API key located in the header ?).
# ──────────────────────────────────────────────────────────────────────────────
class BaseResource(ModelResource):
# ──────────────────────────────────────
def prepend_urls(self):
try:
additional_urls = self._meta.additional_urls
except AttributeError:
additional_urls = []
return [url(r'^'+u[0]+'$', self.wrap_view(u[1]), name=u[2]) for u in additional_urls]
...
# ──────────────────────────────────────
class Meta:
abstract = True
allowed_methods = ['get',]
authentication = ApiKeyAuthentication()
authorization = DjangoAuthorization()
max_limit = 1000
# ──────────────────────────────────────────────────────────────────────────────
class EncounterResource(BaseResource):
...
# ──────────────────────────────────────
def bet(self, request, **kwargs):
self.method_check(request, allowed=['post'])
encounter = int(kwargs.get('encounter', 0))
if not encounter:
return self.create_response(request, {
'success': False,
'message': 'Pretty sure you\'re doing something wrong',
}, HttpApplicationError)
data = self.deserialize(
request,
request.body,
format=request.META.get('CONTENT_TYPE', 'application/json')
)
...
return self.create_response(request, {
'success': True,
}, HttpCreated)
# ──────────────────────────────────────
class Meta(BaseResource.Meta):
allowed_methods = ['get', 'post',]
queryset = Encounter.objects.all()
resource_name = 'encounters'
additional_urls = [
('encounters/(?P<encounter>\d+)/bet/', 'bet', 'api-encounter-bet'),
]
Check out how "dispatch" does it. You want to call the is_authenticated method first, and then you can use request.user
https://github.com/toastdriven/django-tastypie/blob/master/tastypie/resources.py#L443

Django- Get values from Tastypie-Bundle

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.

Django Tastypie, how to search throught multiple modelresource?

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)

django tastypie with non-orm using redis returns no objects

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