Django tastypie and GenericForeignKey
Asked Answered
H

7

9

I have Page model with GFK.

class Page(models.Model):
    title = models.CharField(max_length=200)
    content_type = models.ForeignKey(ContentType,null=True,blank=True)
    object_id = models.CharField(max_length=255,null=True,blank=True)
    content_object = generic.GenericForeignKey('content_type', 'object_id')

and

class TextContent(models.Model):
    content = models.TextField(null=True, blank=True)
    pages = generic.GenericRelation(Page)

I do Page.objects.get(pk=1).content_object and I got it.

Help me please to show a link (or output to JSON) that anchored object in REST.

class PageResource(ModelResource):
    content_object = fields.?????

    class Meta:
        queryset = Page.objects.all()
        resource_name = 'page'

How to do it right?

Thanks!

Vitaliy

Haft answered 18/11, 2011 at 18:31 Comment(1)
did you ever get this working without applying the patches to tastypie?Chuchuah
H
7

There's currently no easy way using generic relations in tastypie. There have been some patches submitted at tastypie github page but these have not been merged as of this writing.

The easiest way to do this, define a contenttype resource and use that for the resources having a generic relation. Something along the lines of:

class ContentTypeResource(ModelResource):
    class Meta:
        queryset = ContentType.objects.all()
        resource_name = "contrib/contenttype"
        fields = ['model']
        detail_allowed_methods = ['get',]
        list_allowed_methods = ['get']

class PageResource(ModelResource):
    content_object = fields.ToOneField('myresources.ContentTypeResource', 'content_object')


    class Meta:
        queryset = Page.objects.all()
        resource_name = 'page'

Hope this helps.

Hardworking answered 13/12, 2011 at 17:30 Comment(2)
I'm trying to implement this now. Could you please provide a little bit more context to help me understand. I have an Additive, which has a generic relation to Attributes. I'd like to get all attributes for a given additive. in your example what does 'myresources, "contrib/contenttype", 'model' refer to. Thanks.Chuchuah
Just a note for something that was bugging me on implementing this: if you are using reverse generic relations, you can't forget to use fields.ToManyField on the model that's accessing the generic resource.Megadeath
D
5

The "myresources" thing is the app that contains the ContentTypeResource. If it's in the same app as your other resources, you don't need to qualify it. Deleted in the code below.

The "contrib/contenttype" is the name of the resource. Setting your own name is optional. Tastypie will create one for you if you don't specify it. I've deleted it in the update code below.

The fields = ['model'] section limits the accessible fields from the model that this resource represents. If you look at the definition of the ContentType model in the Django code, you'll see it has a field called 'model'.

I think the original answer had its field names mixed up. You're trying to create a new resource for content_type, and hook that up to the content_type foreign key in your model. The code above sorts this out.

class ContentTypeResource(ModelResource):
    class Meta:
        queryset = ContentType.objects.all()
        fields = ['model']
        detail_allowed_methods = ['get',]
        list_allowed_methods = ['get']

class PageResource(ModelResource):
    content_type = fields.ToOneField('ContentTypeResource', 'content_type')

    class Meta:
        queryset = Page.objects.all()
        resource_name = 'page'

You're also going to need to register ContentTypeResource in your urls.py as you have with all your other resources:

from myapp.api import ContentTypeResource

v1_api = Api(api_name='v1')
v1_api.register(ContentTypeResource())

The "myapp" bit is again the app with the api code containing ContentTypeResource.

I hope this clears things up. I just got it working myself...

Dietsche answered 29/2, 2012 at 6:5 Comment(0)
W
3

It looks like this was officially added to Tastypie a month ago, check out the example here.

https://github.com/toastdriven/django-tastypie/blob/master/docs/content_types.rst

Wastepaper answered 18/9, 2012 at 1:15 Comment(1)
But that needs knowing which models you will be pointing your relationship to. Which kind of defeats the purpose of having a GenericForeignKey in the first place.Glyconeogenesis
D
2

We cracked the code!

class ContentTypeResource(ModelResource):

    class Meta:
        queryset = ContentType.objects.all()
        resource_name = 'content_type'
        allowed_methods = ['get',]

class PageObjectResource(ModelResource):

    content_object = fields.CharField()

    content_type = fields.ToOneField(
        ContentTypeResource,
        attribute = 'content_type',
        full=True)

    class Meta:
        queryset = models.PageObject.objects.all()
        resource_name = 'page_object'
        allowed_methods = ['get',]

    def dehydrate_content_object(self, bundle):
        for resource in api._registry.values():
            if resource._meta.object_class == bundle.obj.content_object.__class__:
                return resource.full_dehydrate(resource.build_bundle(obj=bundle.obj.content_object, request=bundle.request)).data
        return ''

Which results in something like:

"page_objects": [
{
"content_object": {
"id": "186",
"look_stills": [
{
"_image": "/static/media/uploads/looks/DSC_0903_PR_MEDIUM_QUALITY_RGB_FA.jpg",
"aspect": "front",
"id": "186",
"look_still_icons": [
{
"colour_code": "58",
"enabled": true,
"id": "186",
"in_stock_only": true,
"look_product": {
"colour_code": "58",
"enabled": true,
"id": "186",
"resource_uri": "/api/look_product/186/",
"style_code": "420215"
},
"resource_uri": "/api/look_still_icon/186/",
"x_coord": 76,
"y_coord": 5
}
],
"ordering": 1,
"resource_uri": "/api/look_still/186/"
}
],
"resource_uri": "/api/look_still_set/186/",
"slug": ""
},
"content_type": {
"app_label": "looks_beta",
"id": "97",
"model": "lookstillset",
"name": "look still set",
"resource_uri": "/api/content_type/97/"
},
"id": "2",
"object_id": 186,
"resource_uri": "/api/page_object/2/"
}
],
"page_order": 3,
"page_template": "look_still",
"resource_uri": "/api/page/2/",
"slug": "",
"spread_number": 2,
"title": ""
},
Doody answered 31/7, 2012 at 9:11 Comment(0)
G
1

This gives you the content_object field as a nested object. It's simple, it works, and it's (unfortunately) as efficient the technology will allow.

class PageResource(ModelResource):

    def full_dehydrate(self, bundle):
        new_bundle = super(PageResource, self).full_dehydrate(bundle)
        new_bundle.data['content_object'] = get_serializable(bundle.obj.content_object)
        return new_bundle

    class Meta:
        queryset = Page.objects.all()


def get_serializable(model):

    data = {'type': model.__class__.__name__}
    for field in model._meta.fields:
        data[field.name] = getattr(model, field.name)
    return data
Gummosis answered 15/5, 2012 at 23:20 Comment(0)
D
0

We managed to the get the uri of the content object, if it had a corresponding ModelResource:

class ContentTypeResource(ModelResource):

    class Meta:
        queryset = ContentType.objects.all()
        resource_name = 'content_type'
        allowed_methods = ['get',]

class PageObjectResource(ModelResource):

    content_object_uri = fields.CharField()

    content_type = fields.ToOneField(
        ContentTypeResource,
        attribute = 'content_type',
        full=True)

    class Meta:
        queryset = models.PageObject.objects.all()
        resource_name = 'page_object'
        allowed_methods = ['get',]

    def dehydrate_content_object_uri(self, bundle):
        for resource in api._registry.values():
            if resource._meta.object_class == bundle.obj.content_object.__class__:
                return resource.get_resource_uri(bundle.obj.content_object)
        return ''
Doody answered 31/7, 2012 at 8:10 Comment(0)
G
0

They have in fact added support for this as Mario suggested. Since it took forever to figure out i thought this may help some people out. Here is an example using Django's built-in comment models where i get a reverse relation to the comments from the commented object:

Add this to the model that comments get attached to:

class CmntedObject(models.Model):
    comments = generic.GenericRelation(Comment,
                           content_type_field='content_type',
                           object_id_field='object_pk')

and the resources look like this:

class UserResource(ModelResource):
    what ever you need here....

class CmntedObjectResource(ModelResource):
    comments = fields.ToManyField('path.to.api.CmntedObjectResource', 'comments', full=True, null=True)
    class Meta:
        queryset = CmntedObject.objects.all()
        resource_name = 'cmntedobject'
        allowed_methods = ['get', 'post', 'delete']
        authorization = DjangoAuthorization()

class CommentResource(ModelResource):
    user = fields.ToOneField('path.to.api.UserResource', 'user', full=True)
    content_type_id = fields.CharField(attribute = 'content_type_id')
    site_id = fields.CharField(attribute = 'site_id')
    content_object = GenericForeignKeyField({
                       CmntedObject: CmntedObjectResource, #shown above
                       OtherCmntedObject: OtherCmntedObjectResource, #optional
                    }, 'content_object', null=True)

    class Meta:
        queryset = Comment.objects.all()
        resource_name = 'cmnt'
        allowed_methods = ['get', 'post', 'delete']
        authorization = DjangoAuthorization()

    def obj_create(self, bundle, **kwargs):
        #here we get the current authenticated user as the comment user.
        bundle = super(CmntResource, self).obj_create(bundle, user=bundle.request.user)
        return bundle
Gangway answered 3/3, 2013 at 4:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.