Tastypie - Linking to a "ForeignKey" - django

I have two legacy models listed below. The Library.libtype_id is effectively a foreign key to LibraryType when libtype_id > 0. I want to represent this as a ForeignKey Resource in TastyPie when that condition is met.
Can someone help me out? I have seen this but I'm not sure it's the same thing? Thanks much!!
# models.py
class LibraryType(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=96)
class Library(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=255)
project = models.ForeignKey('project.Project', db_column='parent')
libtype_id = models.IntegerField(db_column='libTypeId')
Here is my api.py
class LibraryTypeResource(ModelResource):
class Meta:
queryset = LibraryType.objects.all()
resource_name = 'library_type'
class LibraryResource(ModelResource):
project = fields.ForeignKey(ProjectResource, 'project')
libtype = fields.ForeignKey(LibraryTypeResource, 'libtype_id' )
class Meta:
queryset = Library.objects.all()
resource_name = 'library'
exclude = ['libtype_id']
def dehydrate_libtype(self, bundle):
if getattr(bundle.obj, 'libtype_id', None) != 0:
return LibraryTypeResource.get_detail(id=bundle.obj.libtype_id)
When I do this however I'm getting the following error on http://0.0.0.0:8001/api/v1/library/?format=json
"error_message": "'long' object has no attribute 'pk'",

Shouldn't
libtype = fields.ForeignKey(LibraryTypeResource, 'libtype_id' )
be
libtype = fields.ForeignKey(LibraryTypeResource, 'libtype' )
(without the '_id')
I believe that as it is you are handing the field an int and it is attempting to get the pk from it.
UPDATE:
Missed that libtype_id is an IntegerField, not a ForeignKey (whole point of the question)
Personally I would add a method to the Library to retrieve the LibraryType object. This way you have access to the LibraryType from the Library and you don't have to override any dehydrate methods.
class Library(models.Model):
# ... other fields
libtype_id = models.IntegerField(db_column='libTypeId')
def get_libtype(self):
return LibraryType.objects.get(id=self.libtype_id)
.
class LibraryResource(ModelResource):
libtype = fields.ForeignKey(LibraryTypeResource, 'get_libtype')

Related

Serialize relation both ways with Django rest_framework

I wonder how to serialize the mutual relation between objects both ways with "djangorestframework". Currently, the relation only shows one way with this:
class MyPolys(models.Model):
name = models.CharField(max_length=20)
text = models.TextField()
poly = models.PolygonField()
class MyPages2(models.Model):
name = models.CharField(max_length=20)
body = models.TextField()
mypolys = models.ManyToManyField(MyPolys)
# ...
class MyPolysSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = testmodels.MyPolys
class MyPages2Serializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = testmodels.MyPages2
# ...
class MyPolyViewSet(viewsets.ReadOnlyModelViewSet):
queryset = testmodels.MyPolys.objects.all()
serializer_class = srlz.MyPolysSerializer
class MyPages2ViewSet(viewsets.ReadOnlyModelViewSet):
queryset = testmodels.MyPages2.objects.all()
serializer_class = srlz.MyPages2Serializer
The many-to-many relation shows up just fine in the api for MyPages2 but nor for MyPolys. How do I make rest_framework aware that the relation goes both ways and needs to be serialized both ways?
The question also applies to one-to-many relations btw.
So far, from reading the documentation and googling, I can't figure out how do that.
Just do it like this:
class MyPolysSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = testmodels.MyPolys
fields =('id','name','text','poly')
class MyPages2Serializer(serializers.HyperlinkedModelSerializer):
mypolys = MyPolysSerializer(many=True,read_only=True)
class Meta:
model = testmodels.MyPages2
fields =('id','name','body','mypolys')
I figured it out! It appears that by adding a mypolys = models.ManyToManyField(MyPolys) to the MyPages2 class, Django has indeed automatically added a similar field called mypages2_set to the MyPolys class, so the serializer looks like this:
class MyPolysSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = testmodels.MyPolys
fields = ('name', 'text', 'id', 'url', 'mypages2_set')
I found out by inspecting an instance of the class in the shell using ./manage.py shell:
pol = testmodels.MyPolys.objects.get(pk=1)
pol. # hit the tab key after '.'
Hitting the tab key after the '.' reveals additional fields and methods including mypages2_set.

Django Tastypie Reference the Same ForeignKey Model More Than Once

Is there a way to reference the same ForeignKey model/resource more than once in Tastypie?
Assume the models:
class Case(models.Model):
name = models.CharField(max_length=10)
class Interaction(models.Model):
case = models.ForeignKey(Case, related_name="interaction_cases")
type = models.CharField(max_length=2, choices=TYPE_CHOICES)
Assume the TastyPie resources:
class CaseResource(ModelResource):
type_one_interactions = fields.ManyToManyField('TypeOneInteractionFullResource', 'interaction_cases', null=True, full_list=True, full=True)
type_two_interactions = fields.ManyToManyField('TypeTwoInteractionFullResource', 'interaction_cases', null=True, full_list=True, full=True)
class Meta:
queryset = Case.objects.all()
class TypeOneInteractionResource(ModelResource):
case = fields.ForeignKey(Case,'case')
class Meta:
queryset = Interaction.objects.all()
def get_object_list(self, request):
return super(TypeOneInteractionResource, self).get_object_list(request).filter(type='A')
class TypeTwoInteractionResource(ModelResource):
case = fields.ForeignKey(Case,'case')
class Meta:
queryset = Interaction.objects.all()
def get_object_list(self, request):
return super(TypeTwoInteractionResource, self).get_object_list(request).filter(type='B')
Basically I am trying to create a single resource with two reverse resources to the same model with different data. When I access the CaseResource I see both TypeOneInteractionResource and TypeTwoInteractionResource in the result, but the data is not being filtered correctly.
I assume it has something to do with the "related_name" being the same and the way TastyPie does model joining internally. Has anybody been successful doing this? Is it even possible?
The reason is because get_object_list is not called at all when dehydrating the ToManyField for related resources (see https://github.com/toastdriven/django-tastypie/blob/master/tastypie/fields.py#L780).
Instead, you'd want to use the dehydrate_type_one_interactions and dehydrate_type_two_interactions methods on the CaseResource.
On the other hand, you can provide properties on the Case model that would return desired QuerySets and use those properties for attribute names in ManyToManyFields.

Denormalizing models in tastypie

What i'm trying to do is to add a query result from a model to a modelresource, as you can see in this block of code:
def dehydrate(self, bundle):
bundle.data['image'] = place_image.image.get(place=1).get(cardinality=0)
I want to add a field to PlaceResource that will contain the image from place_site model where place=1 and cardinality=0. But im recieving an error:
The 'image' attribute can only be accessed from place_image instances
So, my question is: Is it impossible to use the query result from another model in a tastypie modelresource? Im sorry for my bad english, please correct me if something's wrong. Thanks for your time.
There's the complete code:
MODELS.py:
class place(models.Model):
idPlace = models.AutoField(primary_key=True)
Name = models.CharField(max_length=70)
class place_image(models.Model):
idImage = models.AutoField(primary_key=True)
place = models.ForeignKey(place,
to_field='idPlace')
image = ThumbnailerImageField(upload_to="place_images/", blank=True)
cardinality = models.IntegerField()
API.py
from models import place
from models import place_image
class PlaceResource(ModelResource):
class Meta:
queryset = place.objects.all()
resource_name = 'place'
filtering = {"name": ALL}
allowed_methods = ['get']
def dehydrate(self, bundle):
bundle.data['image'] = place_image.image.get(place=1).get(cardinality=0)
return bundle
class PlaceImageResource(ModelResource):
place = fields.ForeignKey(PlaceResource, 'place')
class Meta:
queryset = place_image.objects.all()
resource_name = 'placeimage'
filtering = {"place": ALL_WITH_RELATIONS}
allowed_methods = ['get']
The error you are getting is caused by the fact that you are accessing the image attribute of a model class, not instance.
The object that is being dehydrated in the dehydrate method is stored in obj attribute of the bundle parameter. Also, you are trying to filter place_image models to only those with place=1 and cardinality=0 by accessing the image attribute of place_image model class. Such filtering won't work as image is not a ModelManager instance. You should use objects attribute instead. Furthermore, get() method returns an actual model instance thus a subsequent call to get() will raise AtributeError as your place_image model instances have no attribute get.
So, all in all, your dehydrate should look like this:
def dehydrate(self, bundle):
bundle.data['image'] = place_image.objects.get(place_id=1, cardinality=0).image
return bundle
Notice that this code requires the place_image with desired values to exist, otherwise a place_image.DoesNotExist will be thrown.
There is also some redundancy in your models:
idPlace and idImage can be removed, as django by default creates an AutoField that is a primary key called id when no other primary key fields are defined
place_image.place field has a redundant to_field parameter, as by default ForeignKey points to a primary key field

Tastypie Reverse Relation

I am trying to get my api to give me the reverse relationship data with tastypie.
I have two models, DocumentContainer, and DocumentEvent, they are related as:
DocumentContainer has many DocumentEvents
Here's my code:
class DocumentContainerResource(ModelResource):
pod_events = fields.ToManyField('portal.api.resources.DocumentEventResource', 'pod_events')
class Meta:
queryset = DocumentContainer.objects.all()
resource_name = 'pod'
authorization = Authorization()
allowed_methods = ['get']
def dehydrate_doc(self, bundle):
return bundle.data['doc'] or ''
class DocumentEventResource(ModelResource):
pod = fields.ForeignKey(DocumentContainerResource, 'pod')
class Meta:
queryset = DocumentEvent.objects.all()
resource_name = 'pod_event'
allowed_methods = ['get']
When I hit my api url, I get the following error:
DocumentContainer' object has no attribute 'pod_events
Can anyone help?
Thanks.
I made a blog entry about this here: http://djangoandlove.blogspot.com/2012/11/tastypie-following-reverse-relationship.html.
Here is the basic formula:
API.py
class [top]Resource(ModelResource):
[bottom]s = fields.ToManyField([bottom]Resource, '[bottom]s', full=True, null=True)
class Meta:
queryset = [top].objects.all()
class [bottom]Resource(ModelResource):
class Meta:
queryset = [bottom].objects.all()
Models.py
class [top](models.Model):
pass
class [bottom](models.Model):
[top] = models.ForeignKey([top],related_name="[bottom]s")
It requires
a models.ForeignKey relationship from the child to the parent in this case
the use of a related_name
the top resource definition to use the related_name as the attribute.
Change your line in class DocumentContainerResource(...), from
pod_events = fields.ToManyField('portal.api.resources.DocumentEventResource',
'pod_events')
to
pod_events = fields.ToManyField('portal.api.resources.DocumentEventResource',
'pod_event_set')
The suffix for pod_event in this case should be _set, but depending on the situation, the suffix could be one of the following:
_set
_s
(no suffix)
If each event can only be associated with up to one container, also consider changing:
pod = fields.ForeignKey(DocumentContainerResource, 'pod')
to:
pod = fields.ToOneField(DocumentContainerResource, 'pod')

Django filters - Using an AllValuesFilter (with a LinkWidget) on a ManyToManyField

This is my first Stack Overflow question, so please let me know if I do anything wrong.
I wish to create an AllValues filter on a ManyToMany field using the wonderful django-filters application. Basically, I want to create a filter that looks like it does in the Admin, so I also want to use the LinkWidget too.
Unfortunately, I get an error (Invalid field name: 'operator') if I try this the standard way:
# Models
class Organisation(models.Model):
name = models.CharField()
...
class Sign(models.Model):
name = models.CharField()
operator = models.ManyToManyField('Organisation', blank=True)
...
# Filter
class SignFilter(LinkOrderFilterSet):
operator = django_filters.AllValuesFilter(widget=django_filters.widgets.LinkWidget)
class Meta:
model = Sign
fields = ['operator']
I got around this by creating my own filter with the many to many relationship hard coded:
# Models
class Organisation(models.Model):
name = models.CharField()
...
class Sign(models.Model):
name = models.CharField()
operator = models.ManyToManyField('Organisation', blank=True)
...
# Filter
class MyFilter(django_filters.ChoiceFilter):
#property
def field(self):
cd = {}
for row in self.model.objects.all():
orgs = row.operator.select_related().values()
for org in orgs:
cd[org['id']] = org['name']
choices = zip(cd.keys(), cd.values())
list.sort(choices, key=lambda x:(x[1], x[0]))
self.extra['choices'] = choices
return super(AllValuesFilter, self).field
class SignFilter(LinkOrderFilterSet):
operator = MyFilter(widget=django_filters.widgets.LinkWidget)
I am new to Python and Django. Can someone think of a more generic/elegant way of doing this?
Why did you subclass LinkOrderFilterSet?
Maybe the connect way to use it is this:
import django_filters
class SignFilter(django_filters.FilterSet):
operator = django_filters.AllValuesFilter(widget=django_filters.widgets.LinkWidget)
class Meta:
model = Sign
fields = ['operator']
You can use this
class CircleFilter(django_filters.FilterSet):
l = []
for c in Organisation.objects.all():
l.append((c.id, c.name))
operator = django_filters.ChoiceFilter(
choices=set(l))
class Meta:
model = Sign
fields = ['operator']