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')
Related
I have an model which is for mapping book(item) to categories(tag),
it shows like this in the django admin page.
id item_uid tag_uid
407 Food Recipe
but in django swagger page, when I try to GET this mapping api with ID 407, it returned like this:
"id": 407,
"item_uid": "http://127.0.0.1:8000/items/237/";
"tag_uid": "http://127.0.0.1:8000/tags/361/"
as you can see, it mapped together correctly, but the response body showed the object url and it's object id, which is not readable for human users. I wonder that if there is anyway to make them like this:
"id": 407,
"item_uid": "Food";
"tag_uid": "Recipe"
edit: codes,
#models.py
class Map_item_tag(models.Model):
item_uid = models.ForeignKey(items, on_delete=models.CASCADE, verbose_name='Item UID')
tag_uid = models.ForeignKey(tags, on_delete=models.CASCADE, verbose_name='Tag UID')
#admin.py
#admin.register(Map_item_tag)
class map_item_tag_admin(ImportExportModelAdmin):
resource_class = map_item_tag_Resource
readonly_fields = ('id',)
list_display = ['id','item_uid','tag_uid']
#serializers.py
class Map_item_tag_Serializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Map_item_tag
fields = ['id','item_uid','tag_uid']
#views.py
class Map_item_tag_ViewSet(viewsets.ModelViewSet):
queryset = Map_item_tag.objects.all().order_by('item_uid')
serializer_class = Map_item_tag_Serializer
parser_classes = (FormParser, MultiPartParser)
permission_classes = [permissions.IsAuthenticated]
thank you for answering!
It seems you are using a HyperlinkedModelSerializer instead of a regular ModelSerializer
Try changing the serializer class to a ModelSerializer:
class ItemSerializer(serializers.ModelSerializer):
class Meta:
model = Item
fields = [] # list of fields you want to include in your Item serializer
class Map_item_tag_Serializer(serializers.ModelSerializer):
item_uid = ItemSerializer()
class Meta:
model = Map_item_tag
fields = ['id','item_uid','tag_uid']
In addition, I would advise you to use CamelCase notation for all your classes. For example: instead of using Map_item_tag_Serializer, change the name to MapItemTagSerializer. The same goes for all your other classes.
I would also avoid using using the _uuid suffix when using ForeignKey relationships. In the MapItemTag model, the ForeignKey relationship inherently means that the field will point to an object Item of Tag object. Hence, no need to specify the _uuid part again.
For example, the following changes would make the model a lot more readable:
class MapItemTag(models.Model):
item = models.ForeignKey(Item, on_delete=models.CASCADE, verbose_name='map_item')
tag = models.ForeignKey(Tag, on_delete=models.CASCADE, verbose_name='map_tag')
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.
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.
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')
When I do something like this http://example.com/api/v1/nisit/?format=json. I will get this error "The model '' has an empty attribute 'page' and doesn't allow a null value."
I want to use reverse relation of tastypie. reverse to the "page" model form "nisit" model. the result that i want is to call 127.0.0.1:8000/api/v1/nisit/?format=json&friend_followingPage_id=1
The point of problem is "how can to set followingPage attribute in Nisit model is the reverse relation to the followingNisit attribute in Page model
this is my model
class Nisit(models.Model):
friends = models.ManyToManyField('self',null=True,blank=True)
class Page(models.Model):
followingNisit = models.ManyToManyField(Nisit,blank=True)
this is my resource
class NisitResource(ModelResource):
friend = fields.ManyToManyField('self','friend',null=True)
followingPage = fields.ToManyField('chula.api.resourse.PageResource','followingPage')
class Meta:
queryset = Nisit.objects.all()
resource_name = 'nisit'
filtering = {
'friend' : ALL_WITH_RELATIONS,
'id' : ALL,
}
In above code. I try to code >>>> page=................. according to this link http://django-tastypie.readthedocs.org/en/latest/resources.html#reverse-relationships
but it can't help
class PageResource(ModelResource):
followingNisit = fields.ManyToManyField(NisitResource, 'followingNisit',null=True)
class Meta:
queryset = Page.objects.all()
resource_name = 'page'
authorization= Authorization()
filtering = {
'followingNisit': ALL_WITH_RELATIONS,
'p_name': ALL,
}
Try adding:
class Page(models.Model):
followingNisit = models.ManyToManyField(Nisit,blank=True, related_name="followingPage")
Clear your cache and maybe remove all your null=True might suppress that error message.