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.
Related
I've implemented graphql_ws to subscribe to updates from a Notification model that uses multiple GenericForeignKeys.
My setup works well, except when I try to query information from those foreign objects. I then get the following error:
graphql.error.located_error.GraphQLLocatedError: You cannot call this from an async context - use a thread or sync_to_async.
From what I seem to understand, that's because some database query operations are being done outside the async context of get_notifications (that is, in the resolve_actor function). But I'm really not clear on how I can pull the operations of resolve_actor
into the async context.
I've unsuccessfully tried using prefetch_related (plus I'm not sure it'll work with multiple content types per Django's docs).
Here's the code
models.py
class Notification(TimeStampedModel):
# ...
actor_content_type = models.ForeignKey(ContentType, related_name='notify_actor', on_delete=models.CASCADE)
actor_object_id = models.CharField(max_length=255)
actor = GenericForeignKey('actor_content_type', 'actor_object_id')
# ...
schema.py
class ActorTypeUnion(graphene.Union):
"""
All possible types for Actors
(The object that performed the activity.)
"""
class Meta:
types = (UserType,) # here's there's only one type, but other fields have multiple
class NotificationType(DjangoObjectType):
actor = graphene.Field(ActorTypeUnion)
def resolve_actor(self, args):
if self.actor is not None:
model_name = self.actor._meta.model_name
app_label = self.actor._meta.app_label
model = ContentType.objects.get(app_label=app_label, model=model_name)
return model.get_object_for_this_type(pk=self.actor_object_id)
return None
# ...
class Meta:
model = Notification
class Subscription(graphene.ObjectType):
unread_notifications = graphene.List(NotificationType)
async def resolve_unread_notifications(self, info, **kwargs):
user = info.context['user']
if user.is_anonymous:
raise Exception('Not logged in!')
#database_sync_to_async
def get_notifications(user):
notifications = Notification.objects.filter(
recipient=user,
read=False,
organization=user.active_organization,
)
return [notifications]
while True:
await asyncio.sleep(1)
yield await get_notifications(user)
The query (things works well except when I query fields on actor)
subscription {
unreadNotifications {
id,
read,
actor {
... on UserType {
__typename,
id
}
}
}
}
Full traceback
Traceback (most recent call last):
File "/Users/benjaminsoukiassian/.pyenv/versions/logbook/lib/python3.8/site-packages/graphql/execution/executor.py", line 452, in resolve_or_error
return executor.execute(resolve_fn, source, info, **args)
File "/Users/benjaminsoukiassian/.pyenv/versions/logbook/lib/python3.8/site-packages/graphql/execution/executors/asyncio.py", line 74, in execute
result = fn(*args, **kwargs)
File "/Users/benjaminsoukiassian/Projects/logbook-back/logbook/notifications/schema.py", line 52, in resolve_actor
model = ContentType.objects.get(app_label=app_label, model=model_name)
File "/Users/benjaminsoukiassian/.pyenv/versions/logbook/lib/python3.8/site-packages/django/db/models/manager.py", line 85, in manager_method
return getattr(self.get_queryset(), name)(*args, **kwargs)
File "/Users/benjaminsoukiassian/.pyenv/versions/logbook/lib/python3.8/site-packages/django/db/models/query.py", line 431, in get
num = len(clone)
File "/Users/benjaminsoukiassian/.pyenv/versions/logbook/lib/python3.8/site-packages/django/db/models/query.py", line 262, in __len__
self._fetch_all()
File "/Users/benjaminsoukiassian/.pyenv/versions/logbook/lib/python3.8/site-packages/django/db/models/query.py", line 1324, in _fetch_all
self._result_cache = list(self._iterable_class(self))
File "/Users/benjaminsoukiassian/.pyenv/versions/logbook/lib/python3.8/site-packages/django/db/models/query.py", line 51, in __iter__
results = compiler.execute_sql(chunked_fetch=self.chunked_fetch, chunk_size=self.chunk_size)
File "/Users/benjaminsoukiassian/.pyenv/versions/logbook/lib/python3.8/site-packages/django/db/models/sql/compiler.py", line 1173, in execute_sql
cursor = self.connection.cursor()
File "/Users/benjaminsoukiassian/.pyenv/versions/logbook/lib/python3.8/site-packages/django/utils/asyncio.py", line 24, in inner
raise SynchronousOnlyOperation(message)
graphql.error.located_error.GraphQLLocatedError: You cannot call this from an async context - use a thread or sync_to_async.
I am attempting to create a POST endpoint using DRF ListSerializer to create a list of LogLevel objects.
I have tried to serialize the foreign key using PrimaryKeyRelatedField without success.
models.py
relevant fields for LogLevel model. note foreign key to node model
#associated node
node = models.ForeignKey(Node, on_delete=models.DO_NOTHING,
related_name="log_levels")
#logger name
name = models.CharField(max_length=32, choices=LOGGERS)
# Current log level
level = models.IntegerField(default=INFO,
choices=LOG_LEVELS)
# Timestamps
created_datetime = models.DateTimeField(auto_now_add=True)
updated_datetime = models.DateTimeField(auto_now=True,
blank=True, null=True)
serializers.py
class LogLevelListSerializer(serializers.ListSerializer):
def create(self, validated_data):
log_levels = [LogLevel(**item) for item in validated_data]
levels = LogLevel.objects.bulk_create(log_levels)
return levels
class LogLevelCreateUpdateSerializer(serializers.ModelSerializer):
class Meta:
model = LogLevel
fields = "__all__"
list_serializer_class = LogLevelListSerializer
LogLevel view
class LogLevelList(MethodSerializerMixin,
generics.ListCreateAPIView):
"""
Log Level list API Endpoint.
"""
method_serializer_classes = {
("POST",): LogLevelCreateUpdateSerializer
}
def get_queryset(self):
"""
Queryset to use for endpoint.
"""
return LogLevel.objects.all()
def get_serializer(self, *args, **kwargs):
"""
Return the serializer instance that should be used for validating and
deserializing input, and for serializing output.
"""
serializer_class = self.get_serializer_class()
kwargs['context'] = self.get_serializer_context()
# check if many is required
if "data" in kwargs:
data = kwargs["data"]
# check if many is required
if isinstance(data, list):
kwargs["many"] = True
return serializer_class(*args, **kwargs)
MethodSerializerMixin
from rest_framework import exceptions
class MethodSerializerMixin(object):
"""
Utility class to apply a different serializer class depending
on the request method.
For example:
method_serializer_classes = {
("GET", ): MyModelListViewSerializer,
("PUT", "PATCH"): MyModelCreateUpdateSerializer
}
"""
method_serializer_classes = None
def get_serializer_class(self):
assert self.method_serializer_classes is not None, (
f"Expected view {self.__class__.__name__} should contain "
f"method_serializer_classes to get right serializer class."
)
for methods, serializer_cls in self.method_serializer_classes.items():
if self.request.method in methods:
return serializer_cls
raise exceptions.MethodNotAllowed(self.request.method)
Im passing in a json list of simple objects in the request. node is the foreign key id:
[{
"name": "logger1",
"level": 2,
"node": 1
},
{
"name": "logger2",
"level": 3,
"node": 1
}]
I expect the objects to be created and displayed to the client with success status. Currently, the objects are created in the db successfully but a 500: Server Error is returned and this is the stacktrace I see on Django server:
Internal Server Error: /api/clustering/loglevel/set/
Traceback (most recent call last):
File "/opt/cisco/env/iris/lib/python3.6/site-packages/django/core/handlers/exception.py", line 34, in inner
response = get_response(request)
File "/opt/cisco/env/iris/lib/python3.6/site-packages/django/core/handlers/base.py", line 145, in _get_response
response = self.process_exception_by_middleware(e, request)
File "/opt/cisco/env/iris/lib/python3.6/site-packages/django/core/handlers/base.py", line 143, in _get_response
response = response.render()
File "/opt/cisco/env/iris/lib/python3.6/site-packages/django/template/response.py", line 106, in render
self.content = self.rendered_content
File "/opt/cisco/env/iris/lib/python3.6/site-packages/rest_framework/response.py", line 72, in rendered_content
ret = renderer.render(self.data, accepted_media_type, context)
File "/opt/cisco/env/iris/lib/python3.6/site-packages/rest_framework/renderers.py", line 724, in render
context = self.get_context(data, accepted_media_type, renderer_context)
File "/opt/cisco/env/iris/lib/python3.6/site-packages/rest_framework/renderers.py", line 697, in get_context
'post_form': self.get_rendered_html_form(data, view, 'POST', request),
File "/opt/cisco/env/iris/lib/python3.6/site-packages/rest_framework/renderers.py", line 520, in get_rendered_html_form
return self.render_form_for_serializer(serializer)
File "/opt/cisco/env/iris/lib/python3.6/site-packages/rest_framework/renderers.py", line 528, in render_form_for_serializer
serializer.data,
File "/opt/cisco/env/iris/lib/python3.6/site-packages/rest_framework/serializers.py", line 765, in data
ret = super(ListSerializer, self).data
File "/opt/cisco/env/iris/lib/python3.6/site-packages/rest_framework/serializers.py", line 266, in data
self._data = self.get_initial()
File "/opt/cisco/env/iris/lib/python3.6/site-packages/rest_framework/serializers.py", line 600, in get_initial
return self.to_representation(self.initial_data)
File "/opt/cisco/env/iris/lib/python3.6/site-packages/rest_framework/serializers.py", line 683, in to_representation
self.child.to_representation(item) for item in iterable
File "/opt/cisco/env/iris/lib/python3.6/site-packages/rest_framework/serializers.py", line 683, in <listcomp>
self.child.to_representation(item) for item in iterable
File "/opt/cisco/env/iris/lib/python3.6/site-packages/rest_framework/serializers.py", line 527, in to_representation
ret[field.field_name] = field.to_representation(attribute)
File "/opt/cisco/env/iris/lib/python3.6/site-packages/rest_framework/relations.py", line 257, in to_representation
return value.pk
AttributeError: 'int' object has no attribute 'pk'
python==3.6
django==2.2.2
drf==3.8.2
The serializer.data property is only valid if you have a saved an instance to the serializer.
Either call serializer.save() or use serializer.validated_data to access data prior to saving.
Checkout this link for further information.
Had to handle this error by updating to_representation method on the PrimaryKeyRelatedField class
class NodePrimaryKeyField(serializers.PrimaryKeyRelatedField):
"""
Custom DRF serializer field for proper handling of
Node Foreign Key by ListSerializer on validation error
"""
def to_representation(self, value):
"""
Return pk value of serialized Node object
if available else return given ID value
"""
if self.pk_field is not None:
return self.pk_field.to_representation(value.pk)
return getattr(value, 'pk', value)
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
Given these models:
class ModelA(models.Model):
# Some fields
# Some relationship
class ModelB(models.Model):
# Some fields
a = models.ForeignKey(ModelA)
and these tastypie resources:
class ResourceA(ModelResource):
b_list = fields.ToManyField('app.api.ResourceB', 'a_set', null=True, related_name='a')
class ResourceB(ModelResource):
a = fields.ToOneField(ResourceA, 'a', null=True)
If I visit the detail endpoint for a ResourceB (/api/v1/resourceA/##/) I can see all of the related B's uris - the reverse relationship works. The returned data looks like this:
{
'b_list': [
'/api/v1/resourceB/1/',
'/api/v1/resourceB/2/',
]
}
If I attempt to PUT or PATCH a resource A to add a new B to it, say with this data:
{
'b_list': [
'/api/v1/resourceB/1/',
'/api/v1/resourceB/2/',
'/api/v1/resourceB/3/',
]
}
I get the error: 'ManyRelatedManager' object has no attribute 'add' with this stacktrace:
Traceback (most recent call last):
File \"/home/username/.virtualenvs/myapp/local/lib/python2.7/site-packages/tastypie/resources.py\", line 202, in wrapper
response = callback(request, *args, **kwargs)
File \"/home/username/.virtualenvs/myapp/local/lib/python2.7/site-packages/tastypie/resources.py\", line 449, in dispatch_detail
return self.dispatch('detail', request, **kwargs)
File \"/home/username/.virtualenvs/myapp/local/lib/python2.7/site-packages/tastypie/resources.py\", line 472, in dispatch
response = method(request, **kwargs)
File \"/home/username/.virtualenvs/myapp/local/lib/python2.7/site-packages/tastypie/resources.py\", line 1611, in patch_detail
self.update_in_place(request, bundle, deserialized)
File \"/home/username/.virtualenvs/myapp/local/lib/python2.7/site-packages/tastypie/resources.py\", line 1634, in update_in_place
return self.obj_update(bundle=original_bundle, **kwargs)
File \"/home/username/.virtualenvs/myapp/local/lib/python2.7/site-packages/tastypie/resources.py\", line 2166, in obj_update
return self.save(bundle, skip_errors=skip_errors)
File \"/home/username/.virtualenvs/myapp/local/lib/python2.7/site-packages/tastypie/resources.py\", line 2257, in save
self.save_m2m(m2m_bundle)
File \"/home/username/.virtualenvs/myapp/local/lib/python2.7/site-packages/tastypie/resources.py\", line 2382, in save_m2m
related_resource.save(updated_related_bundle)
File \"/home/username/.virtualenvs/myapp/local/lib/python2.7/site-packages/tastypie/resources.py\", line 2257, in save
self.save_m2m(m2m_bundle)
File \"/home/username/.virtualenvs/myapp/local/lib/python2.7/site-packages/tastypie/resources.py\", line 2382, in save_m2m
related_resource.save(updated_related_bundle)
File \"/home/username/.virtualenvs/myapp/local/lib/python2.7/site-packages/tastypie/resources.py\", line 2257, in save
self.save_m2m(m2m_bundle)
File \"/home/username/.virtualenvs/myapp/local/lib/python2.7/site-packages/tastypie/resources.py\", line 2385, in save_m2m
related_mngr.add(*related_objs)
AttributeError: 'ManyRelatedManager' object has no attribute 'add'
If I then immediately PUT/PATCH the same data, I get no error, no stacktrace, and the item is successfully added to the relationship. I can't figure out why this is the case.
Also, I only seem to be able to add items to the relationship - it would be great to be able to delete them too. I may ask another question about this.
Ok. This is a Django error.
I'm not sure, try to use related_name in the django model.
class ModelB(models.Model):
# Some fields
a = models.ForeignKey(ModelA, related_name='b_list')
Remove the related_name from the resource and the attribute name must change from 'a_set' to 'b_list'
class ResourceA(ModelResource):
b_list = fields.ToManyField('app.api.ResourceB', 'b_list', null=True)
I am trying to restrict my modelform foreign key instances using a list:
here is my model:
class Voicemail(models.Model):
internalline = models.ForeignKey(InternalLine)
person = models.ForeignKey(Person)
Then in init method of a corresponding model form I have
class VoicemailForm(ModelForm):
def __init__(self, *args, **kwargs):
....
....
# self.fields['internalline'].queryset = self.person.getPersonLines() # this throws error in template.
self.fields['internalline'].queryset = self.person.line.all()
print(self.person.getPersonLines())
print(self.person.line.all())
The two prints print identical output:
[<InternalLine: 1111>, <InternalLine: 5555>]
[<InternalLine: 1111>, <InternalLine: 5555>]
In getPersonLines, I do some additional logic and return a list of lines:
def getPersonLines(self):
lines = []
for line in self.line.order_by('extension'):
lines.append(line)
for phone in self.phonesst_set.all():
for line in phone.line.order_by('extension'):
if line not in lines:
lines.append(line)
return lines
Now when I try to render in template, if I use the list returned by getPersonLines, I get and error,
Caught AttributeError while rendering: 'list' object has no attribute 'all'
But the same thing works if I populate the queryset using self.person.line.all()..
Am I missing something while trying to populate my queryset using a list?
Thanks in advance!!
Update
Here is the stack trace with
self.fields['internalline'].choices = self.person.getPersonLines()
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "C:\Python26\lib\site-packages\django\utils\encoding.py", line 27, in __str__
return self.__unicode__().encode('utf-8')
File "C:\Python26\lib\site-packages\django\forms\forms.py", line 95, in __unicode__
return self.as_table()
File "C:\Python26\lib\site-packages\django\forms\forms.py", line 217, in as_table
errors_on_separate_row = False)
File "C:\Python26\lib\site-packages\django\forms\forms.py", line 180, in _html_output
'field': unicode(bf),
File "C:\Python26\lib\site-packages\django\forms\forms.py", line 408, in __unicode__
return self.as_widget()
File "C:\Python26\lib\site-packages\django\forms\forms.py", line 439, in as_widget
return widget.render(name, self.value(), attrs=attrs)
File "C:\Python26\lib\site-packages\django\forms\widgets.py", line 516, in render
options = self.render_options(choices, [value])
File "C:\Python26\lib\site-packages\django\forms\widgets.py", line 533, in render_options
for option_value, option_label in chain(self.choices, choices):
TypeError: 'InternalLine' object is not iterable
Update 2:
This is the entire __init__ method
class VoicemailForm(ModelForm):
def __init__(self, *args, **kwargs):
try:
self.person = kwargs.pop('person', None)
super(VoicemailForm, self).__init__(*args, **kwargs)
for field in self.fields:
self.fields[field].widget.attrs['class'] = 'required_field'
print(self.person.getPersonLines())
print(self.person.line.all())
if self.person:
self.fields['internalline'].choices = self.person.getPersonLines()
#self.fields['internalline'].queryset = self.person.line.all()
except Exception as e:
print("Error overwriting __init__ of VoicemailForm")
print(e)
This is how I call my form
voicemail = VoicemailForm(person=person, prefix='voicemail')
Update 3
I tried to create a django form as follows:
class test(forms.Form):
line = forms.ChoiceField()
def __init__(self, *args, **kwargs):
super(test, self).__init__(*args, **kwargs)
self.fields['line'].choices=person.getPersonLines()
But I continue to get the same error
for option_value, option_label in chain(self.choices, choices):
TypeError: 'InternalLineSST' object is not iterable
Then I tired something like:
test = [1,2,3]
class myform(forms.Form):
line = forms.ChoiceField()
def __init__(self, *args, **kwargs):
super(myform, self).__init__(*args, **kwargs)
self.fields['line'].choices = test
>>> form = myform()
>>>print(form)
which gives me a similar error
File "C:\Python26\lib\site-packages\django\forms\forms.py", line 439, in as_widget
return widget.render(name, self.value(), attrs=attrs)
File "C:\Python26\lib\site-packages\django\forms\widgets.py", line 516, in render
options = self.render_options(choices, [value])
File "C:\Python26\lib\site-packages\django\forms\widgets.py", line 533,in render_options
for option_value, option_label in chain(self.choices, choices):
TypeError: 'int' object is not iterable`
Am I missing something here?
Both the .all() and the .order_by() methods are part of the Django Queryset. They are not the same as a Python list. Although the representation is the same, they are indeed different.
Some code in the Django ModelChoiceField does expect a foreignkey to have a queryset instead of a list of results. If you set self.fields['internalline'].choices instead, you won't have this problem.
So try this instead:
self.fields['internalline'].choices = self.person.getPersonLines()
My purpose was using the list was I wanted to merge two query sets to display options for a foreign key.
Based on
https://groups.google.com/forum/?hl=en&fromgroups=#!topic/django-users/0i6KjzeM8OI
I modified my method to return a queryset instead of a list:
def getPersonLines(self):
vm_lines = self.line.all()
for phone in self.phonesst_set.all():
vm_lines = vm_lines | phone.line.all()
and in the __inti__ method of the form, I could use the query set
self.fields['internalline'].queryset = self.person.getPersonLines()