|
from django.conf.urls import url |
|
from django.core.urlresolvers import reverse |
|
from tastypie.utils.urls import trailing_slash |
|
|
|
def patch_class_func(cls, func_name): |
|
""" |
|
Allows chaining of return values on an existing method in a class. The original unaltered method is called, |
|
and its return value is passed to the decorated function, which can then alter the returned value. |
|
|
|
Differs from simply overriding the patched method because this can be called multiple times, adding to the |
|
original returned value each time (e.g., appending to a list, adding to a dict). |
|
|
|
Silly example which extends collections.OrderedDict.copy() to always add an additional entry: |
|
>>> from collections import OrderedDict |
|
>>> @api_resources.patch_class_func(OrderedDict, 'copy') |
|
... def kilroy_was_here(self, orig_copy): |
|
... orig_copy['kilroy'] = "was here" |
|
... return orig_copy |
|
... |
|
>>> od = OrderedDict(a=1, b=2, c=3) |
|
>>> od.copy() |
|
OrderedDict([('a', 1), ('c', 3), ('b', 2), ('kilroy', 'was here')]) |
|
""" |
|
def wrapped_new_func(new_func): |
|
orig_func = getattr(cls, func_name) |
|
|
|
def call_new_func_on_return_value_of_old(self, *args, **kwargs): |
|
return new_func(self, orig_func(self, *args, **kwargs)) |
|
setattr(cls, func_name, call_new_func_on_return_value_of_old) |
|
return wrapped_new_func |
|
|
|
|
|
def add_related_uri_to_resource(resource_class, related_resource_name, related_resource_class, related_resource_fk_field_name): |
|
""" |
|
Adds a URI for a collection of related models to a TastyPie resource. |
|
|
|
Usage example: |
|
class BaseResource(resources.ModelResource): |
|
class Meta: |
|
resource_name = 'base' |
|
|
|
class RelatedResource(resources.ModelResource): |
|
base_parent = fields.ForeignKey(BaseResource, 'base_parent') |
|
|
|
add_related_uri_to_resource(BaseResource, 'related_resources', RelatedResource, 'base_parent') |
|
|
|
This will create a "related_resources_uri" on the dehydrated BaseResource instances which, when visited, |
|
will display the list of RelatedResources with that instance as a parent. In this case, if the detail |
|
view for a base instance is /api/v1/base/1/, the URI for its related resource collection will be |
|
/api/v1/base/1/related_resources/. |
|
""" |
|
class_resource_name = resource_class.Meta.resource_name |
|
|
|
uri_key = '{}_uri'.format(related_resource_name) |
|
url_name = 'api_get_{}_{}'.format(class_resource_name, related_resource_name) |
|
view_func_name = 'get_{}'.format(related_resource_name) |
|
|
|
@patch_class_func(resource_class, 'dehydrate') |
|
def wrap_dehydrate(self, dehydrated_bundle): |
|
# build related resource URI |
|
kwargs = dict(api_name=self._meta.api_name, resource_name=class_resource_name, \ |
|
pk=dehydrated_bundle.data[self._meta.object_class._meta.pk.name]) |
|
dehydrated_bundle.data[uri_key] = reverse(url_name, kwargs=kwargs) |
|
return dehydrated_bundle |
|
|
|
@patch_class_func(resource_class, 'prepend_urls') |
|
def wrap_prepend_urls(self, url_list): |
|
# copy the detail URL for the base resource |
|
dispatch_detail_url = [u for u in self.base_urls() if u.name == 'api_dispatch_detail'][0] |
|
|
|
# append the related resource name to it |
|
related_url_pattern = dispatch_detail_url.regex.pattern.rstrip('$?/') |
|
related_url_pattern = r'{}/{}{}$'.format(related_url_pattern, related_resource_name, trailing_slash()) |
|
|
|
# and create the URL pattern for the related resource list view |
|
url_list.append(url(related_url_pattern, self.wrap_view(view_func_name), name=url_name)) |
|
return url_list |
|
|
|
def get_related_list(self, request, **kwargs): |
|
# allow only GET requests on the related resource list |
|
self.method_check(request, str('get')) |
|
return related_resource_class().get_list(request, **{related_resource_fk_field_name: kwargs['pk']}) |
|
|
|
setattr(resource_class, view_func_name, get_related_list) |