Skip to content

Commit

Permalink
Added request to Resource.get_via_uri for consistency and per…
Browse files Browse the repository at this point in the history
…-object privilege checking.
  • Loading branch information
MacMaru authored and toastdriven committed Aug 18, 2011
1 parent f71efb6 commit 252f4d3
Show file tree
Hide file tree
Showing 4 changed files with 923 additions and 897 deletions.
94 changes: 48 additions & 46 deletions docs/resources.rst
Expand Up @@ -22,18 +22,18 @@ A sample resource definition might look something like::
from tastypie.authorization import DjangoAuthorization
from tastypie.resources import ModelResource, ALL, ALL_WITH_RELATIONS
from myapp.models import Entry


class UserResource(ModelResource):
class Meta:
queryset = User.objects.all()
resource_name = 'auth/user'
excludes = ['email', 'password', 'is_superuser']


class EntryResource(ModelResource):
user = fields.ForeignKey(UserResource, 'user')

class Meta:
queryset = Entry.objects.all()
list_allowed_methods = ['get', 'post']
Expand Down Expand Up @@ -97,7 +97,7 @@ As an example, we'll walk through what a GET request to a list endpoint (say
* the user is authenticated (``is_authenticated``),
* the user is authorized (``is_authorized``),
* & the user has not exceeded their throttle (``throttle_check``).

At this point, ``dispatch`` actually calls the requested method (``get_list``).

* ``get_list`` does the actual work of the API. It does:
Expand Down Expand Up @@ -237,10 +237,10 @@ A simple example::
class MyResource(ModelResource):
# The ``title`` field is already added to the class by ``ModelResource``
# and populated off ``Note.title``. But we want allcaps titles...

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

def dehydrate_title(self, bundle):
return bundle.data['title'].upper()

Expand All @@ -250,21 +250,21 @@ A complex example::
# As is, this is just an empty field. Without the ``dehydrate_rating``
# method, no data would be populated for it.
rating = fields.FloatField(readonly=True)

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

def dehydrate_rating(self, bundle):
total_score = 0.0

# Make sure we don't have to worry about "divide by zero" errors.
if not bundle.obj.rating_set.count():
return rating

# We'll run over all the ``Rating`` objects & calculate an average.
for rating in bundle.obj.rating_set.all():
total_score += rating.rating

return total_score / bundle.obj.rating_set.count()

The return value is updated in the ``bundle.data``. You should avoid altering
Expand All @@ -284,7 +284,7 @@ A simple example::
class MyResource(ModelResource):
class Meta:
queryset = Note.objects.all()

def dehydrate(self, bundle):
# Include the request IP in the bundle.
bundle.data['request_ip'] = bundle.request.META.get('REMOTE_ADDR')
Expand All @@ -296,15 +296,15 @@ A complex example::
class Meta:
queryset = User.objects.all()
excludes = ['email', 'password', 'is_staff', 'is_superuser']

def dehydrate(self, bundle):
# If they're requesting their own record, add in their email address.
if bundle.request.user.pk == bundle.obj.pk:
# Note that there isn't an ``email`` field on the ``Resource``.
# By this time, it doesn't matter, as the built data will no
# longer be checked against the fields on the ``Resource``.
bundle.data['email'] = bundle.obj.email

return bundle

This method should return a ``bundle``, whether it modifies the existing one or creates a whole new one. You can even remove any/all data from the
Expand Down Expand Up @@ -366,10 +366,10 @@ A simple example::
class MyResource(ModelResource):
# The ``title`` field is already added to the class by ``ModelResource``
# and populated off ``Note.title``. But we want lowercase titles...

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

def hydrate_title(self, bundle):
return bundle.data['title'].lower()

Expand All @@ -387,17 +387,17 @@ Example::
# The ``title`` field is already added to the class by ``ModelResource``
# and populated off ``Note.title``. We'll use that title to build a
# ``Note.slug`` as well.

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

def hydrate(self, bundle):
# Don't change existing slugs.
# In reality, this would be better implemented at the ``Note.save``
# level, but is for demonstration.
if not bundle.obj.pk:
bundle.obj.slug = slugify(bundle.data['title'])

return bundle

This method should return a ``bundle``, whether it modifies the existing one or creates a whole new one. You can even remove any/all data from the
Expand All @@ -421,18 +421,18 @@ relationship looks like so::
from tastypie import fields
from tastypie.resources import ModelResource
from myapp.models import Note, Comment


class NoteResource(ModelResource):
comments = fields.ToManyField('myapp.api.resources.CommentResource', 'comments')

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


class CommentResource(ModelResource):
note = fields.ToOneField(NoteResource, 'notes')

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

Expand All @@ -450,11 +450,11 @@ a similar relation in Tastypie would look like::
from tastypie import fields
from tastypie.resources import ModelResource
from myapp.models import Note


class NoteResource(ModelResource):
sub_notes = fields.ToManyField('self', 'notes')

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

Expand Down Expand Up @@ -519,7 +519,7 @@ The inner ``Meta`` class allows for class-level configuration of how the
Controls what list & detail REST methods the ``Resource`` should respond to.
Default is ``None``, which means delegate to the more specific
``list_allowed_methods`` & ``detail_allowed_methods`` options.

You may specify a list like ``['get', 'post', 'put', 'delete']`` as a shortcut
to prevent having to specify the other options.

Expand Down Expand Up @@ -553,7 +553,7 @@ The inner ``Meta`` class allows for class-level configuration of how the

An override for the ``Resource`` to use when generating resource URLs.
Default is ``None``.

If not provided, the ``Resource`` or ``ModelResource`` will attempt to name
itself. This means a lowercase version of the classname preceding the word
``Resource`` if present (i.e. ``SampleContentResource`` would become
Expand All @@ -571,7 +571,7 @@ The inner ``Meta`` class allows for class-level configuration of how the

Provides a list of fields that the ``Resource`` will accept client
filtering on. Default is ``{}``.

Keys should be the fieldnames as strings while values should be a list of
accepted filter types.

Expand All @@ -580,7 +580,7 @@ The inner ``Meta`` class allows for class-level configuration of how the

Specifies the what fields the ``Resource`` should should allow ordering on.
Default is ``[]``.

Values should be the fieldnames as strings. When provided to the ``Resource``
by the ``order_by`` GET parameter, you can specify either the ``fieldname``
(ascending order) or ``-fieldname`` (descending order).
Expand All @@ -590,7 +590,7 @@ The inner ``Meta`` class allows for class-level configuration of how the

Provides the ``Resource`` with the object that serves as the data source.
Default is ``None``.

In the case of ``ModelResource``, this is automatically populated by the
``queryset`` option and is the model class.

Expand All @@ -599,7 +599,7 @@ The inner ``Meta`` class allows for class-level configuration of how the

Provides the ``Resource`` with the set of Django models to respond with.
Default is ``None``.

Unused by ``Resource`` but present for consistency.

``fields``
Expand Down Expand Up @@ -632,11 +632,11 @@ The inner ``Meta`` class allows for class-level configuration of how the

Specifies all HTTP methods (except ``DELETE``) should return a serialized form
of the data. Default is ``False``.

If ``False``, ``HttpNoContent`` (204) is returned on ``POST/PUT``
with an empty body & a ``Location`` header of where to request the full
resource.

If ``True``, ``HttpAccepted`` (202) is returned on ``POST/PUT``
with a body containing all the data in a serialized form.

Expand Down Expand Up @@ -680,19 +680,19 @@ additional constraints (e.g. text filtering using `django-haystack
filter the queryset before processing a request::

from haystack.query import SearchQuerySet

class MyResource(Resource):
def build_filters(self, filters=None):
if filters is None:
filters = {}

orm_filters = super(MyResource, self).build_filters(filters)

if "q" in filters:
sqs = SearchQuerySet().auto_query(filters['q'])

orm_filters = {"pk__in": [ i.pk for i in sqs ]}

return orm_filters


Expand Down Expand Up @@ -887,10 +887,10 @@ HTTP methods to check against. Usually, this looks like::

# The most generic lookup.
self.method_check(request, self._meta.allowed_methods)

# A lookup against what's allowed for list-type methods.
self.method_check(request, self._meta.list_allowed_methods)

# A useful check when creating a new endpoint that only handles
# GET.
self.method_check(request, ['get'])
Expand Down Expand Up @@ -996,11 +996,13 @@ Returns a URL specific to this resource's list endpoint.
``get_via_uri``
---------------

.. method:: Resource.get_via_uri(self, uri)
.. method:: Resource.get_via_uri(self, uri, request=None)

This pulls apart the salient bits of the URI and populates the
resource via a ``obj_get``.

Optionally accepts a ``request``.

If you need custom behavior based on other portions of the URI,
simply override this method.

Expand Down

0 comments on commit 252f4d3

Please sign in to comment.