TastyPie data validation issue - django

I wonder what is the proper way of using TastyPie validation.
I have following resource and model:
from tastypie.resources import ModelResource
class Station(models.Model):
name = models.CharField(max_length=20)
city = models.ForeignKey(City)
class StationResource(ModelResource):
city = fields.ForeignKey(CityResource, 'city')
class Meta:
queryset = caModels.Station.objects.all()
resource_name = 'Station'
authorization = Authorization()
validation=FormValidation(form_class=StationForm)
max_limit = None
And I also want to use a ModelForm to validate data:
class StationForm(forms.ModelForm):
class Meta:
model = caModels.Station
def clean_city(self):
return self.cleaned_data['city']
The following query works fine:
curl --dump-header - -H "Content-Type: application/json" -X POST --data '{"city": "/resources/City/89/", "name": "station_1", <and other fields here>}' "http://localhost:8000/resources/Station/?format=json"
HTTP/1.0 201 CREATED
Date: Thu, 06 Feb 2014 13:10:14 GMT
Server: WSGIServer/0.1 Python/2.7.4
Vary: Accept, Cookie
Content-Type: text/html; charset=utf-8
Location: http://localhost:8000/resources/Station/3/
But when I remove city from request (but this field is required) instead of message 'This field is required' I get the following traceback:
Traceback (most recent call last):
File "/home/ak/venv/main_env/local/lib/python2.7/site-packages/django/core/handlers/base.py", line 115, in get_response
response = callback(request, *callback_args, **callback_kwargs)
File "/home/ak/venv/main_env/local/lib/python2.7/site-packages/django/views/decorators/csrf.py", line 77, in wrapped_view
return view_func(*args, **kwargs)
File "/home/ak/venv/main_env/local/lib/python2.7/site-packages/tastypie/resources.py", line 217, in wrapper
response = callback(request, *args, **kwargs)
File "/home/ak/venv/main_env/local/lib/python2.7/site-packages/tastypie/resources.py", line 459, in dispatch_list
return self.dispatch('list', request, **kwargs)
File "/home/ak/venv/main_env/local/lib/python2.7/site-packages/tastypie/resources.py", line 491, in dispatch
response = method(request, **kwargs)
File "/home/ak/venv/main_env/local/lib/python2.7/site-packages/tastypie/resources.py", line 1357, in post_list
updated_bundle = self.obj_create(bundle, **self.remove_api_resource_names(kwargs))
File "/home/ak/venv/main_env/local/lib/python2.7/site-packages/tastypie/resources.py", line 2149, in obj_create
bundle = self.full_hydrate(bundle)
File "/home/ak/venv/main_env/local/lib/python2.7/site-packages/tastypie/resources.py", line 909, in full_hydrate
value = field_object.hydrate(bundle)
File "/home/ak/venv/main_env/local/lib/python2.7/site-packages/tastypie/fields.py", line 732, in hydrate
value = super(ToOneField, self).hydrate(bundle)
File "/home/ak/venv/main_env/local/lib/python2.7/site-packages/tastypie/fields.py", line 165, in hydrate
elif self.attribute and getattr(bundle.obj, self.attribute, None):
File "/home/ak/venv/main_env/local/lib/python2.7/site-packages/django/db/models/fields/related.py", line 389, in __get__
raise self.field.rel.to.DoesNotExist
DoesNotExist
This error happens even before I can validate the data in the form (validation is the first process of save procedure looks like).
.../tastypie/resources.py:
def obj_create(self, bundle, **kwargs):
"""
A ORM-specific implementation of ``obj_create``.
"""
bundle.obj = self._meta.object_class()
for key, value in kwargs.items():
setattr(bundle.obj, key, value)
self.authorized_create_detail(self.get_object_list(bundle.request), bundle)
bundle = self.full_hydrate(bundle)
return self.save(bundle)
def save(self, bundle, skip_errors=False):
self.is_valid(bundle)
...
Can somebody point me where I am wrong, or may be I missed something and this way of validation is completely wrong from TastyPie point of view?

I think your question is very interesting.
I decided to dig in a little in subject..
Here is what I've found.
The docs:
blank
As we see this description is pretty similar to what we already know from Django.
The Code base:
hydrate
If you don't say explicitly Tastypie will does "expect" this field to be there.
Short conclusion: I think about resource like something beyond the models and the Meta may be much different regards to needs, that can give you more flexibility for instance.
Potential solutions:
Say to Tastypie pass it if its blank. Anyway model validation will not pass it.
class StationResource(ModelResource):
city = fields.ForeignKey(CityResource, 'city', blank=True)
Or Add default to hydrate.
class StationResource(ModelResource):
city = fields.ForeignKey(CityResource, 'city')
def hydrate(self, bundle):
if not hasattr(bundle, 'city'):
bundle.data['city'] = None
return bundle
Conclusion: This is in fact quite misleading but on other hand Tastypie is still under development, maybe docs will cover it better or this will change or there is plan to changed it.
If you are more interested there is many issues debated on github: like this one similar to your question: issue

Related

Getting validation error 'is_staff': ["'true' value must be either True or False."] while implementing SSO using djangosaml2

I tried to implement SSO for my Django project.
when I tried to login form SP ('/sso/login') I got the correct response from my IDP but on SP side 'sso/acs' it throwing a validation error.
{'is_superuser': ["'true' value must be either True or False."], 'is_staff': ["'true' value must be either True or False."]}
When I removed is_staff and is_superuser from SAML_ATTRIBUTE_MAPPING.
SSO is working.
Project packages -
django 1.11,
postgress,
djangosaml2idp 0.5.0, for IDP
djangosaml2 0.17.2 for SP
My setting for SP:
SAML_USE_NAME_ID_AS_USERNAME = True
SAML_DJANGO_USER_MAIN_ATTRIBUTE = 'email'
SAML_DJANGO_USER_MAIN_ATTRIBUTE_LOOKUP = '__iexact'
SAML_CREATE_UNKNOWN_USER = False
SAML_LOGOUT_REQUEST_PREFERRED_BINDING = saml2.BINDING_HTTP_POST
SAML_ATTRIBUTE_MAPPING = {
# SAML : DJANGO
# Must also be present in attribute-maps!
'email': ('email', ),
'username': ('username', ),
'account_id': ('account_id', ),
'is_staff': ('is_staff', ),
'is_superuser': ('is_superuser', ),
}
Error Traceback:
Traceback (most recent call last):
File "/Users/lite/Virtual_Envs/spEnv/lib/python3.6/site-packages/django/core/handlers/exception.py", line 34, in inner
response = get_response(request)
File "/Users/lite/Virtual_Envs/spEnv/lib/python3.6/site-packages/django/core/handlers/base.py", line 115, in _get_response
response = self.process_exception_by_middleware(e, request)
File "/Users/lite/Virtual_Envs/spEnv/lib/python3.6/site-packages/django/core/handlers/base.py", line 113, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/Users/lite/Virtual_Envs/spEnv/lib/python3.6/site-packages/django/views/decorators/http.py", line 40, in inner
return func(request, *args, **kwargs)
File "/Users/lite/Virtual_Envs/spEnv/lib/python3.6/site-packages/django/views/decorators/csrf.py", line 54, in wrapped_view
return view_func(*args, **kwargs)
File "/Users/lite/Virtual_Envs/spEnv/lib/python3.6/site-packages/djangosaml2/views.py", line 316, in assertion_consumer_service
create_unknown_user=create_unknown_user)
File "/Users/lite/Virtual_Envs/spEnv/lib/python3.6/site-packages/django/contrib/auth/__init__.py", line 73, in authenticate
user = backend.authenticate(request, **credentials)
File "/Users/lite/Virtual_Envs/spEnv/lib/python3.6/site-packages/djangosaml2/backends.py", line 107, in authenticate
create_unknown_user, main_attribute, attributes, attribute_mapping)
File "/Users/lite/Virtual_Envs/spEnv/lib/python3.6/site-packages/djangosaml2/backends.py", line 154, in get_saml2_user
return self._get_saml2_user(main_attribute, attributes, attribute_mapping)
File "/Users/lite/Virtual_Envs/spEnv/lib/python3.6/site-packages/djangosaml2/backends.py", line 188, in _get_saml2_user
user = self.update_user(user, attributes, attribute_mapping)
File "/Users/lite/Virtual_Envs/spEnv/lib/python3.6/site-packages/djangosaml2/backends.py", line 250, in update_user
user.save()
File "/Users/lite/projects/djangosaml2/sample-sp/sp/models.py", line 105, in save
self.full_clean()
File "/Users/lite/Virtual_Envs/spEnv/lib/python3.6/site-packages/django/db/models/base.py", line 1203, in full_clean
raise ValidationError(errors)
django.core.exceptions.ValidationError: {'is_superuser': ["'true' value must be either True or False."], 'is_staff': ["'true' value must be either True or False."]}
I tried with different DBs and with or without overriding Django user model.
when I tried with default (auth.models) User model sso is working with Postgres and sqlite backend but when I override the User model for my custom user model it throwing above error.
I think on SP is not typecasting IDP response, it just parsing and passing all attributes as strings and while saving on DB it causing a validation error. but why it is working with the default Django User model ?
Application User model
class User(AbstractBaseUser, PermissionsMixin):
username = models.CharField(max_length=15, unique=True,)
email = models.EmailField(max_length=123, blank=False, null=False, unique=True)
is_staff = models.BooleanField(default=False, db_index=True)
objects = UserManager()
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ['email']
def clean(self):
errors = {}
users = get_user_model().objects.filter(username__iexact=self.username)
if len(users) > 1:
errors.setdefault('username', []).append(f'{self.username} not available')
if errors:
raise ValidationError(errors)
def save(self, force_insert=False, force_update=False, using=None,
update_fields=None):
self.full_clean()
return super().save(force_insert, force_update, using, update_fields)
Had the same issue, seems like this is something to do with djangosaml2idp implementation.
From the example code in the source, you should add the following code in the User app view.py file (or in the main project folder, if you didn't customize the User model).
#receiver(pre_user_save, sender=User)
def custom_update_user(sender, instance, attributes, user_modified, **kargs):
""" Default behaviour does not play nice with booleans encoded in SAML as u'true'/u'false'.
This will convert those attributes to real booleans when saving.
"""
for k, v in attributes.items():
u = set.intersection(set(v), set([u'true', u'false']))
if u:
setattr(instance, k, u.pop() == u'true')
return True
Source:
custom_update_user

django rest framework OPTIONS 500

I'm getting a strange 500 error when issuing an OPTION request to an endpoint in my API built with Django Rest Framework. GET, POST, PUT all work fine and DELETE is not allowed.
When issuing an OPTION request to the endpoint, I get the following error and traceback:
Traceback:
File "/Users/awwester/Sites/django/rlg/lib/python3.4/site-packages/django/core/handlers/base.py" in get_response
111. response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/Users/awwester/Sites/django/rlg/lib/python3.4/site-packages/django/views/decorators/csrf.py" in wrapped_view
57. return view_func(*args, **kwargs)
File "/Users/awwester/Sites/django/rlg/lib/python3.4/site-packages/rest_framework/viewsets.py" in view
85. return self.dispatch(request, *args, **kwargs)
File "/Users/awwester/Sites/django/rlg/lib/python3.4/site-packages/rest_framework/views.py" in dispatch
452. response = self.handle_exception(exc)
File "/Users/awwester/Sites/django/rlg/lib/python3.4/site-packages/rest_framework/views.py" in dispatch
449. response = handler(request, *args, **kwargs)
File "/Users/awwester/Sites/django/rlg/lib/python3.4/site-packages/rest_framework/views.py" in options
463. data = self.metadata_class().determine_metadata(request, self)
File "/Users/awwester/Sites/django/rlg/lib/python3.4/site-packages/rest_framework/metadata.py" in determine_metadata
63. actions = self.determine_actions(request, view)
File "/Users/awwester/Sites/django/rlg/lib/python3.4/site-packages/rest_framework/metadata.py" in determine_actions
89. actions[method] = self.get_serializer_info(serializer)
File "/Users/awwester/Sites/django/rlg/lib/python3.4/site-packages/rest_framework/metadata.py" in get_serializer_info
106. for field_name, field in serializer.fields.items()
File "/Users/awwester/Sites/django/rlg/lib/python3.4/site-packages/rest_framework/metadata.py" in <listcomp>
106. for field_name, field in serializer.fields.items()
File "/Users/awwester/Sites/django/rlg/lib/python3.4/site-packages/rest_framework/metadata.py" in get_field_info
129. if hasattr(field, 'choices'):
File "/Users/awwester/Sites/django/rlg/lib/python3.4/site-packages/rest_framework/relations.py" in choices
382. for item in iterable
Exception Type: TypeError at /v1/powerChatSessions
Exception Value: 'NoneType' object is not iterable
Here is the model, serializer, and view:
# models.py - error happens when issuing OPTIONS /powerChatSession
class VideoConversation(models.Model):
"""
Capture data about a video chat session
SO note: this is inherited by other classes besides PowerChat
"""
created = models.DateTimeField(auto_now_add=True)
end = models.DateTimeField(null=True, blank=True)
users = models.ManyToManyField(User)
class PowerChat(VideoConversation):
"""
power chat session
"""
start = models.DateTimeField(null=True, blank=True)
extended = models.BooleanField(default=False)
active = models.BooleanField(default=False)
# serializers.py
class PowerChatSerializer(serializers.ModelSerializer):
class Meta:
model = PowerChat
read_only_fields = ('users', 'start', 'extended',)
# views.py
class PowerChatViewSet(ModelViewSet):
queryset = PowerChat.objects.all()
permission_classes = (PowerChatPermission,)
serializer_class = PowerChatSerializer
resource_name = "powerChatSession"
Any ideas why this is happening? It has something to do with the users field, and it seems that it could be a bug with Django Rest Framework? It's checking if the field has the choices kwarg, which it doesn't, but apparently DRF thinks it does?
I think this issue was addressed by:
https://github.com/tomchristie/django-rest-framework/issues/3115
The gist is that "At the moment, self.action is None in viewsets if I get an OPTIONS request."
This issue ceased for me after I upgraded to djangorestframework-3.2.3

MultiValueDictKeyError when editing inline admin object

I am designing a simple MCQ application using the ManyToOne relationship from django website, django version 1.6.1. I have an inline admin form which supposedly allows me to add / edit answers to a given question from the same changeform. However, if after saving a question with its answers once, i am unable to edit / add answers from the same form and get a MultiValueDictKeyError. My models are:
class SBA (models.Model):
question = models.TextField(blank=False)
system = models.CharField(max_length=3, choices=pacscon.System.which_system)
case = models.ForeignKey('pacscon.Patient')
created = models.DateField(auto_now_add=True)
reference = models.TextField(blank=True)
def __unicode__(self):
return self.question
class Answer(models.Model):
id = models.AutoField(primary_key=True)
body = models.TextField()
correct = models.BooleanField(default=False)
sba = models.ForeignKey(SBA, null=True)
def __unicode__(self):
return self.body
the admin.py looks like this :
from models import Answer, SBA, Mnemonic
#from django import forms
from django.contrib import admin
class AnswerInline(admin.StackedInline):
model = Answer
extra = 2
class SBAAdmin(admin.ModelAdmin):
fieldsets = (
(None, {
'fields': ('question', 'system', 'case', 'reference')
}),
)
inlines = [
AnswerInline,
]
readonly_fields = ('created',)
admin.site.register(SBA, SBAAdmin)
admin.site.register(Answer)
admin.site.register(Mnemonic)
And the error message is :
MultiValueDictKeyError at /admin/knowledge/sba/1/
Traceback:
File "/usr/local/lib/python2.7/dist-packages/django/core/handlers/base.py" in get_response
114. response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/usr/local/lib/python2.7/dist-packages/django/contrib/admin/options.py" in wrapper
432. return self.admin_site.admin_view(view)(*args, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/django/utils/decorators.py" in _wrapped_view
99. response = view_func(request, *args, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/django/views/decorators/cache.py" in _wrapped_view_func
52. response = view_func(request, *args, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/django/contrib/admin/sites.py" in inner
198. return view(request, *args, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/django/utils/decorators.py" in _wrapper
29. return bound_func(*args, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/django/utils/decorators.py" in _wrapped_view
99. response = view_func(request, *args, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/django/utils/decorators.py" in bound_func
25. return func(self, *args2, **kwargs2)
File "/usr/local/lib/python2.7/dist-packages/django/db/transaction.py" in inner
339. return func(*args, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/django/contrib/admin/options.py" in change_view
1229. if all_valid(formsets) and form_validated:
File "/usr/local/lib/python2.7/dist-packages/django/forms/formsets.py" in all_valid
415. if not formset.is_valid():
File "/usr/local/lib/python2.7/dist-packages/django/forms/formsets.py" in is_valid
292. err = self.errors
File "/usr/local/lib/python2.7/dist-packages/django/forms/formsets.py" in errors
267. self.full_clean()
File "/usr/local/lib/python2.7/dist-packages/django/forms/formsets.py" in full_clean
314. form = self.forms[i]
File "/usr/local/lib/python2.7/dist-packages/django/utils/functional.py" in __get__
49. res = instance.__dict__[self.func.__name__] = self.func(instance)
File "/usr/local/lib/python2.7/dist-packages/django/forms/formsets.py" in forms
133. forms = [self._construct_form(i) for i in xrange(self.total_form_count())]
File "/usr/local/lib/python2.7/dist-packages/django/forms/models.py" in _construct_form
848. form = super(BaseInlineFormSet, self)._construct_form(i, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/django/forms/models.py" in _construct_form
564. pk = self.data[pk_key]
File "/usr/local/lib/python2.7/dist-packages/django/utils/datastructures.py" in __getitem__
301. raise MultiValueDictKeyError(repr(key))
Exception Type: MultiValueDictKeyError at /admin/knowledge/sba/1/
Exception Value: "u'answer_set-0-id'"
I have searched similar requests on google and stackoverflow and my django package is fully upto date (including admin inline templates as suggested in some other answers). Will appreciate any help. Thanks
P.S This is almost certainly a bug because I can replicate the problem on official django "Polls" tutorial which uses a similar ManyToOne relationship model.
In Django <= 1.7.3, I needed to change the primary key field of my model to AutoField:
wrong:
class Answer(models.Model):
id = IntegerField(primary_key=True)
correct:
class Answer(models.Model):
id = AutoField(primary_key=True)
See
https://code.djangoproject.com/ticket/15665
I have similar issue which was solved by update django-grappelli. If you did not use it, check your packages maybe you find what can affect work of admin site.
pip freeze | grep django- will show you current packages with versions
This answer https://stackoverflow.com/a/20246225/554807 has the explanation.
The best fix is actually to get silverfix's branch of nested-inlines: https://github.com/silverfix/django-nested-inlines
This has the fix mentioned in that answer, and others.
After migrating an application from Django==1.4.20 to Django==1.8.3 i had an outdated templates_django/admin/edit_inline/stacked.html and templates_django/admin/edit_inline/tabular.html files.
So i have just removed them and everything works fine now.
You can also upgrade them to your current django version instead of deleting.

Saving M2M field with Tastypie

I'm building an API with Tastypie, and I've run into an issue when saving a many-to-many field.
I have a model call Pest and another called Call, and Call has a field called pests representing the pests that can be applied to a call. These already exist and the user can choose one or more to apply to that call - there is no intention to create them at the same time as the Call object.
By default, I get the following error when I try to create a new Call via POST:
{"error_message": "Cannot resolve keyword 'url' into field. Choices are: baitpoint, call, description, id, name, operator", "traceback": "Traceback (most recent call last):\n\n File \"/home/matthew/Projects/Pestability/venv/local/lib/python2.7/site-packages/tastypie/resources.py\", line 217, in wrapper\n response = callback(request, *args, **kwargs)\n\n File \"/home/matthew/Projects/Pestability/venv/local/lib/python2.7/site-packages/tastypie/resources.py\", line 459, in dispatch_list\n return self.dispatch('list', request, **kwargs)\n\n File \"/home/matthew/Projects/Pestability/venv/local/lib/python2.7/site-packages/tastypie/resources.py\", line 491, in dispatch\n response = method(request, **kwargs)\n\n File \"/home/matthew/Projects/Pestability/venv/local/lib/python2.7/site-packages/tastypie/resources.py\", line 1357, in post_list\n updated_bundle = self.obj_create(bundle, **self.remove_api_resource_names(kwargs))\n\n File \"/home/matthew/Projects/Pestability/venv/local/lib/python2.7/site-packages/tastypie/resources.py\", line 2150, in obj_create\n return self.save(bundle)\n\n File \"/home/matthew/Projects/Pestability/venv/local/lib/python2.7/site-packages/tastypie/resources.py\", line 2300, in save\n m2m_bundle = self.hydrate_m2m(bundle)\n\n File \"/home/matthew/Projects/Pestability/venv/local/lib/python2.7/site-packages/tastypie/resources.py\", line 964, in hydrate_m2m\n bundle.data[field_name] = field_object.hydrate_m2m(bundle)\n\n File \"/home/matthew/Projects/Pestability/venv/local/lib/python2.7/site-packages/tastypie/fields.py\", line 853, in hydrate_m2m\n m2m_hydrated.append(self.build_related_resource(value, **kwargs))\n\n File \"/home/matthew/Projects/Pestability/venv/local/lib/python2.7/site-packages/tastypie/fields.py\", line 653, in build_related_resource\n return self.resource_from_uri(self.fk_resource, value, **kwargs)\n\n File \"/home/matthew/Projects/Pestability/venv/local/lib/python2.7/site-packages/tastypie/fields.py\", line 573, in resource_from_uri\n obj = fk_resource.get_via_uri(uri, request=request)\n\n File \"/home/matthew/Projects/Pestability/venv/local/lib/python2.7/site-packages/tastypie/resources.py\", line 838, in get_via_uri\n return self.obj_get(bundle=bundle, **self.remove_api_resource_names(kwargs))\n\n File \"/home/matthew/Projects/Pestability/venv/local/lib/python2.7/site-packages/tastypie/resources.py\", line 2125, in obj_get\n object_list = self.get_object_list(bundle.request).filter(**kwargs)\n\n File \"/home/matthew/Projects/Pestability/venv/local/lib/python2.7/site-packages/django/db/models/query.py\", line 655, in filter\n return self._filter_or_exclude(False, *args, **kwargs)\n\n File \"/home/matthew/Projects/Pestability/venv/local/lib/python2.7/site-packages/django/db/models/query.py\", line 673, in _filter_or_exclude\n clone.query.add_q(Q(*args, **kwargs))\n\n File \"/home/matthew/Projects/Pestability/venv/local/lib/python2.7/site-packages/django/db/models/sql/query.py\", line 1266, in add_q\n can_reuse=used_aliases, force_having=force_having)\n\n File \"/home/matthew/Projects/Pestability/venv/local/lib/python2.7/site-packages/django/db/models/sql/query.py\", line 1134, in add_filter\n process_extras=process_extras)\n\n File \"/home/matthew/Projects/Pestability/venv/local/lib/python2.7/site-packages/django/db/models/sql/query.py\", line 1332, in setup_joins\n \"Choices are: %s\" % (name, \", \".join(names)))\n\nFieldError: Cannot resolve keyword 'url' into field. Choices are: baitpoint, call, description, id, name, operator\n"}
So I had a look and found this answer, which seemed to cover a similar situation. I added the hydrate_pests method to the CallResource class as follows:
class AbstractModelResource(ModelResource):
class Meta:
authorization = DjangoAuthorization()
authentication = ApiKeyAuthentication()
cache = SimpleCache(timeout=10)
always_return_data = True
class FilteredByOperatorAbstractModelResource(AbstractModelResource):
def authorized_read_list(self, object_list, bundle):
user = bundle.request.user
site_user = SiteUser.objects.get(user=user)
return object_list.filter(operator=site_user.operator)
class PestResource(FilteredByOperatorAbstractModelResource):
class Meta(AbstractModelResource.Meta):
queryset = Pest.objects.all()
resource_name = 'pest'
allowed_methods = ['get']
class CallResource(AbstractModelResource):
client = fields.ForeignKey(ClientResource, 'client')
operator = fields.ForeignKey(OperatorResource, 'operator')
pests = fields.ManyToManyField(PestResource, 'pests', null=True)
class Meta(AbstractModelResource.Meta):
queryset = Call.objects.all()
resource_name = 'call'
def hydrate_pests(self, bundle):
pests = bundle.data.get('pests', [])
pest_ids = []
for pest in pests:
m = re.search('\/api\/v1\/pests\/(\d+)\/', str(pest))
try:
id = m.group(1)
pest_ids.append(id)
except AttributeError:
pass
bundle.data['pests'] = Pest.objects.filter(id__in=pest_ids)
return bundle
The pests field is getting passed through as follows:
0: "/api/v1/pests/6/"
1: "/api/v1/pests/7/"
And the pest URL's are showing up correctly when I run bundle.data.get('pests', []) - if I use PDB to set a trace, I can verify that the URLs are being passed through, and Pest.objects.filter(id__in=pest_ids) is returning the correct items. However, although the HTTP POST request is successful, the pests field is not being updated to reflect the new data.
Can anyone see where I've gone wrong? Am I correct in passing through a list of the Pest objects to bundle.data['pests'], or is this not how I should be passing this data through to that field?
What actually gets passed through to bundle.data is as follows:
{'pests': [<Pest: Rats>, <Pest: Mice>], 'notes': u'Blah', 'first_choice_visit_time': u'2013-07-18T02:02', 'client': u'/api/v1/client/28/', 'date': u'2013-07-18', 'second_choice_visit_time': u'2014-03-03T03:02'}
The bundle data holds dictionaries. You're passing it a list of QuerySet objects. Try appending .values() to your queryset.

Django Tastypie Deserializing Multipart/Form-Data To Upload File

I am trying to upload files via a Multipart/Form-Data form and Tastypie API and am running into some issues:
My Model:
class Client(models.Model):
account = models.ForeignKey(Account)
client_image = models.FileField(upload_to=client_image_path, default="/assets/img/default-user-image.png", blank=True, null=True)
client_image_thumb = models.FileField(upload_to=client_image_thumb_path, default="/assets/img/default-user-image.png", blank=True, null=True)
I am using a custom deserialize method as outlined in Tastypie Issue#42:
class MultipartResource(object):
def deserialize(self, request, data, format=None):
if not format:
format = request.META.get('CONTENT_TYPE', 'application/json')
if format == 'application/x-www-form-urlencoded':
return request.POST
if format.startswith('multipart'):
data = request.POST.copy()
data.update(request.FILES)
return data
return super(MultipartResource, self).deserialize(request, data, format)
def put_detail(self, request, **kwargs):
if request.META.get('CONTENT_TYPE').startswith('multipart') and \
not hasattr(request, '_body'):
request._body = ''
return super(MultipartResource, self).put_detail(request, **kwargs)
And here is my corresponding ModelResource:
class ClientResource(MultipartResource, ModelResource):
account = fields.ForeignKey(AccountResource, 'account')
class Meta():
queryset = Client.objects.all()
always_return_data = True
resource_name = 'account/clients/client-info'
authorization = AccountLevelAuthorization()
list_allowed_methods = ['get','post','put','delete','patch']
detail_allowed_methods = ['get', 'post', 'put', 'delete','patch']
authentication = ApiKeyAuthentication()
filtering = {
'username': ALL,
}
If I do a POST with content-type application/JSON and dont include the client_image field, it will successfully create a new Client Object. This indicates that the models/resources are working as they should.
However, when I try to use a Multipart/Form-Data content type I can see that it gets through my deserializer appropriately with this payload:
------WebKitFormBoundaryp0Q7Q9djlsvVGwbb
Content-Disposition: form-data; name="{%0D%0A%22account%22"
"/api/v1/account/account-info/21/",
------WebKitFormBoundaryp0Q7Q9djlsvVGwbb
Content-Disposition: form-data; name="client_image"; filename="donavan.jpg"
Content-Type: image/jpeg
------WebKitFormBoundaryp0Q7Q9djlsvVGwbb--
I am also seeing this QueryDict while debugging, which shows the InMemoryUploadedFile correctly:
<QueryDict: {u'client_image': [<InMemoryUploadedFile: donavan.jpg (image/jpeg)>], u'{%0D%0A%22account%22': [u'"/api/v1/account/account-info/21/"']}>
but I keep getting this error:
{ error_message: "" traceback: "Traceback (most recent call last):
File
"/Users/stevewirig/Documents/www/vu/venv/lib/python2.7/site-packages/tastypie/resources.py",
line 202, in wrapper response = callback(request, *args, **kwargs)
File
"/Users/stevewirig/Documents/www/vu/venv/lib/python2.7/site-packages/tastypie/resources.py",
line 440, in dispatch_list return self.dispatch('list', request,
**kwargs) File "/Users/stevewirig/Documents/www/vu/venv/lib/python2.7/site-packages/tastypie/resources.py",
line 472, in dispatch response = method(request, **kwargs) File
"/Users/stevewirig/Documents/www/vu/venv/lib/python2.7/site-packages/tastypie/resources.py",
line 1328, in post_list updated_bundle = self.obj_create(bundle,
**self.remove_api_resource_names(kwargs)) File "/Users/stevewirig/Documents/www/vu/venv/lib/python2.7/site-packages/tastypie/resources.py",
line 2104, in obj_create bundle = self.full_hydrate(bundle) File
"/Users/stevewirig/Documents/www/vu/venv/lib/python2.7/site-packages/tastypie/resources.py",
line 890, in full_hydrate value = field_object.hydrate(bundle) File
"/Users/stevewirig/Documents/www/vu/venv/lib/python2.7/site-packages/tastypie/fields.py",
line 732, in hydrate value = super(ToOneField, self).hydrate(bundle)
File
"/Users/stevewirig/Documents/www/vu/venv/lib/python2.7/site-packages/tastypie/fields.py",
line 165, in hydrate elif self.attribute and getattr(bundle.obj,
self.attribute, None): File
"/Users/stevewirig/Documents/www/vu/venv/lib/python2.7/site-packages/django/db/models/fields/related.py",
line 343, in get raise self.field.rel.to.DoesNotExist DoesNotExist
" }
Any ideas where this could be broken? Thanks in advance!
This happened to me when I post a data without providing necessary fields. Those fields that cannot be null must be provided while posting.
Initialize the serializer as follows:
serializer = Serializer(formats=['json'])