Example where model != resource?

19 views
Skip to first unread message

Idan Gazit

unread,
Aug 28, 2009, 5:49:20 AM8/28/09
to django-piston
Hey folks,

I'm new to piston, assuredly my question is kinda stupid.

I've read all the docs, and spent some time with resource.py,
handler.py, emitters.py. I get how they interact.

What I'm not quite clear on is the "right" way to build and return
resources that aren't 1:1 with a model. For example:

class Foo(models.Model):
#... some fields

class Bar(models.Model):
#... some fields
foo = models.ForeignKey(Foo)

If I have an API endpoint like /api/foo/<id>, and I want it to return
all of the related Bars inline, am I supposed to be building a dict in
my handler's read() and returning that? Writing a custom emitter? Or
is there something I'm missing?

Alternatively, if somebody can post a slightly more complex example
than what's in the docs, I'd be happy to read from / learn from
that...

Cheers,

-Idan

jespern

unread,
Aug 28, 2009, 5:54:32 AM8/28/09
to django-piston
On Aug 28, 12:49 pm, Idan Gazit <i...@pixane.com> wrote:
> Hey folks,
>
> I'm new to piston, assuredly my question is kinda stupid.
>
> I've read all the docs, and spent some time with resource.py,
> handler.py, emitters.py. I get how they interact.
>
> What I'm not quite clear on is the "right" way to build and return
> resources that aren't 1:1 with a model. For example:
>
> class Foo(models.Model):
>    #... some fields
>
> class Bar(models.Model):
>    #... some fields
>    foo = models.ForeignKey(Foo)
>
> If I have an API endpoint like /api/foo/<id>, and I want it to return
> all of the related Bars inline, am I supposed to be building a dict in
> my handler's read() and returning that? Writing a custom emitter? Or
> is there something I'm missing?

Hey Idan,

Piston handles these relations internally, so all you have to do is
add the accessor in "fields". For example, fields = ('id', 'bars').

This'll crawl the Bar's and serialize them for you. You can nest your
definition if you want specific fields, like fields = ('id', ('bars',
('id', 'name', 'foo'))).


HTH,

Jesper

Idan Gazit

unread,
Aug 28, 2009, 6:38:32 AM8/28/09
to django-piston

On Aug 28, 12:54 pm, jespern <jno...@gmail.com> wrote:
> Piston handles these relations internally, so all you have to do is
> add the accessor in "fields". For example, fields = ('id', 'bars').
>
> This'll crawl the Bar's and serialize them for you. You can nest your
> definition if you want specific fields, like fields = ('id', ('bars',
> ('id', 'name', 'foo'))).
>

Hey Jesper,

Cool -- don't know if this is a bug, but I had tried adding the name
of the reverse relation to fields like:

fields = ('id', 'bars')

And the result gave me a repr() of RelatedManager, not the related
objects. It works as expected with the nesting thing, though.

I'm keeping notes as I go, once I've spent a little more time with
piston I'll send you some documentation additions/patches.

Thanks!

-Idan

jespern

unread,
Aug 28, 2009, 6:42:53 AM8/28/09
to django-piston
On Aug 28, 1:38 pm, Idan Gazit <i...@pixane.com> wrote:
> On Aug 28, 12:54 pm, jespern <jno...@gmail.com> wrote:
>
> > Piston handles these relations internally, so all you have to do is
> > add the accessor in "fields". For example, fields = ('id', 'bars').
>
> > This'll crawl the Bar's and serialize them for you. You can nest your
> > definition if you want specific fields, like fields = ('id', ('bars',
> > ('id', 'name', 'foo'))).
>
> Hey Jesper,
>
> Cool -- don't know if this is a bug, but I had tried adding the name
> of the reverse relation to fields like:
>
> fields = ('id', 'bars')
>
> And the result gave me a repr() of RelatedManager, not the related
> objects. It works as expected with the nesting thing, though.

Sounds like a bug. I seem to remember fixing this in trunk though, so
you could try with the latest hg tip.

> I'm keeping notes as I go, once I've spent a little more time with
> piston I'll send you some documentation additions/patches.

Great! I'll be watching any forks of yours.


Jesper

Idan Gazit

unread,
Aug 30, 2009, 7:23:01 AM8/30/09
to django-piston
More stupid questions below! Extends the example above.

I'm extending django's User in the officially-sanctioned way - FK from
Bar to User and setting AUTH_PROFILE_MODULE. Extending the above
example:

class Bar(models.Model):
user = ForeignKey(User, unique=True)

However I'd prefer not to present user info in two pieces in the API.
Continuing the example from above:

class FooHandler(BaseHandler):
fields: ('id', 'field1', 'field2', ('bars', ('url', 'timezone',
'avatar', 'user')))

It works -- but the base User info and my custom extensions are split
in two, where I'd prefer to present a unified view of data describing
a user. I want my extensions ('timezone', 'avatar', etc) appear on the
same footing with the fields from User ('username', 'first_name',
'last_name', etc), as if they were coming from the same model.

Is there a way to achieve the desired effect? No need to spoonfeed,
just point me in the right direction.

Thanks,

-Idan

jespern

unread,
Aug 30, 2009, 7:33:30 AM8/30/09
to django-piston
This is where nesting falls short :-) I'd suggest you construct a
generic resource method and specify that in fields instead.

Rough example:

def user_info(cls, user):
return { 'username': user.username, 'first_name':
user.first_name, ... }

class SomeFunkyHandler(BaseHandler):
fields = ('id', 'field1', 'field2', 'user') # user is NOT a field,
it'a method
user = user_info

I did a small test on whether this would work (since user_info may
have to be a @staticmethod), but it works fine. user_info receives the
class name as the first arg, so it's all good.

Let me know how it works for you,


Jesper

Idan Gazit

unread,
Aug 30, 2009, 9:16:52 AM8/30/09
to django-piston
On Aug 30, 2:33 pm, jespern <jno...@gmail.com> wrote:
> class SomeFunkyHandler(BaseHandler):
>    fields = ('id', 'field1', 'field2', 'user') # user is NOT a field,
> it'a method
>    user = user_info

Ok, beginning to get the hang of things. Am feeling the generic
resource method love, but the following recipie doesn't work as soon
as I add nesting to the mix:

def bar_info(cls, bar):
return { 'id': bar.id, 'url': bar.url, ..., 'username':
bar.user.username }

class FooHandler(BaseHandler):
fields: ('id', 'field1', 'field2', ('bars', ('bar_info')))

It appears (from adding some print statements) that bar_info() is
never called in this case. A little playing reveals that generic
resource methods are called just fine when they're not nested.

I can work around this by doing something like:

def foo_bars_info(cls, foo):
bars = []
for bar in foo.bars:
bars.append( {'id': bar.id, ..., 'username':
bar.user.username })
return bars

fields: ('id', 'field1', 'field2', 'foo_bars_info')

On the right track? Or am I doing it wrong?

Thanks again for the handholding -- I really appreciate it!

-Idan

jespern

unread,
Aug 30, 2009, 9:42:47 AM8/30/09
to django-piston
On Aug 30, 4:16 pm, Idan Gazit <i...@pixane.com> wrote:
> On Aug 30, 2:33 pm, jespern <jno...@gmail.com> wrote:
>
> > class SomeFunkyHandler(BaseHandler):
> >    fields = ('id', 'field1', 'field2', 'user') # user is NOT a field,
> > it'a method
> >    user = user_info
>
> Ok, beginning to get the hang of things. Am feeling the generic
> resource method love, but the following recipie doesn't work as soon
> as I add nesting to the mix:
>
> def bar_info(cls, bar):
>    return { 'id': bar.id,  'url': bar.url, ...,  'username':
> bar.user.username }
>
> class FooHandler(BaseHandler):
>     fields: ('id', 'field1', 'field2', ('bars', ('bar_info')))
>
> It appears (from adding some print statements) that bar_info() is
> never called in this case. A little playing reveals that generic
> resource methods are called just fine when they're not nested.

Rigth, resource methods and "fields" are mutually exclusive, they
can't be post-processed by nesting. That's by design.

We'd need something new for that, some sort of "proxy" method, but
there's nothing like that currently. Patches welcome ;-)

> I can work around this by doing something like:
>
> def foo_bars_info(cls, foo):
>     bars = []
>     for bar in foo.bars:
>        bars.append( {'id': bar.id, ..., 'username':
> bar.user.username })
>     return bars
>
> fields: ('id', 'field1', 'field2', 'foo_bars_info')
>
> On the right track? Or am I doing it wrong?

That's not a work-around, that's the correct way to do it. On a side
note, you could probably "yield" here, as we'll flatten in the
serializer.


Jesper
Reply all
Reply to author
Forward
0 new messages