1from __future__ import unicode_literals
2
3from copy import copy, deepcopy
4from datetime import datetime
5import logging
6import sys
7from time import mktime
8import traceback
9import warnings
10from wsgiref.handlers import format_date_time
11
12from django.conf import settings
13from django.core.exceptions import (
14    ObjectDoesNotExist, MultipleObjectsReturned, ValidationError, FieldDoesNotExist
15)
16from django.core.signals import got_request_exception
17from django.core.exceptions import ImproperlyConfigured
18from django.db.models.fields.related import ForeignKey
19from django.urls.conf import re_path
20from tastypie.utils.timezone import make_naive_utc
21try:
22    from django.contrib.gis.db.models.fields import GeometryField
23except (ImproperlyConfigured, ImportError):
24    GeometryField = None
25from django.db.models.constants import LOOKUP_SEP
26try:
27    from django.db.models.fields.related import\
28        SingleRelatedObjectDescriptor as ReverseOneToOneDescriptor
29except ImportError:
30    from django.db.models.fields.related_descriptors import\
31        ReverseOneToOneDescriptor
32
33from django.http import HttpResponse, HttpResponseNotFound, Http404
34from django.utils.cache import patch_cache_control, patch_vary_headers
35from django.utils.html import escape
36from django.views.decorators.csrf import csrf_exempt
37
38import six
39
40from tastypie.authentication import Authentication
41from tastypie.authorization import ReadOnlyAuthorization
42from tastypie.bundle import Bundle
43from tastypie.cache import NoCache
44from tastypie.compat import NoReverseMatch, reverse, Resolver404, get_script_prefix, is_ajax
45from tastypie.constants import ALL, ALL_WITH_RELATIONS
46from tastypie.exceptions import (
47    NotFound, BadRequest, InvalidFilterError, HydrationError, InvalidSortError,
48    ImmediateHttpResponse, Unauthorized, UnsupportedFormat,
49    UnsupportedSerializationFormat, UnsupportedDeserializationFormat,
50)
51from tastypie import fields
52from tastypie import http
53from tastypie.paginator import Paginator
54from tastypie.serializers import Serializer
55from tastypie.throttle import BaseThrottle
56from tastypie.utils import (
57    dict_strip_unicode_keys, is_valid_jsonp_callback_value, string_to_python,
58    trailing_slash,
59)
60from tastypie.utils.mime import determine_format, build_content_type
61from tastypie.validation import Validation
62from tastypie.compat import get_module_name, atomic_decorator
63
64
65def sanitize(text):
66    # We put the single quotes back, due to their frequent usage in exception
67    # messages.
68    return escape(text).replace(''', "'").replace('"', '"').replace(''', "'")
69
70
71class ResourceOptions(object):
72    """
73    A configuration class for ``Resource``.
74
75    Provides sane defaults and the logic needed to augment these settings with
76    the internal ``class Meta`` used on ``Resource`` subclasses.
77    """
78    serializer = Serializer()
79    authentication = Authentication()
80    authorization = ReadOnlyAuthorization()
81    cache = NoCache()
82    throttle = BaseThrottle()
83    validation = Validation()
84    paginator_class = Paginator
85    allowed_methods = ['get', 'post', 'put', 'delete', 'patch']
86    list_allowed_methods = None
87    detail_allowed_methods = None
88    limit = getattr(settings, 'API_LIMIT_PER_PAGE', 20)
89    max_limit = 1000
90    api_name = None
91    resource_name = None
92    urlconf_namespace = None
93    default_format = 'application/json'
94    filtering = {}
95    ordering = []
96    object_class = None
97    queryset = None
98    fields = None
99    excludes = []
100    include_resource_uri = True
101    include_absolute_url = False
102    always_return_data = False
103    collection_name = 'objects'
104    detail_uri_name = 'pk'
105
106    def __new__(cls, meta=None):
107        overrides = {}
108
109        # Handle overrides.
110        if meta:
111            for override_name in dir(meta):
112                # No internals please.
113                if not override_name.startswith('_'):
114                    overrides[override_name] = getattr(meta, override_name)
115
116        allowed_methods = overrides.get('allowed_methods', ['get', 'post', 'put', 'delete', 'patch'])
117
118        if overrides.get('list_allowed_methods', None) is None:
119            overrides['list_allowed_methods'] = allowed_methods
120
121        if overrides.get('detail_allowed_methods', None) is None:
122            overrides['detail_allowed_methods'] = allowed_methods
123
124        if six.PY3:
125            return object.__new__(type('ResourceOptions', (cls,), overrides))
126        else:
127            return object.__new__(type(b'ResourceOptions', (cls,), overrides))
128
129
130class DeclarativeMetaclass(type):
131    def __new__(cls, name, bases, attrs):
132        attrs['base_fields'] = {}
133        declared_fields = {}
134
135        # Inherit any fields from parent(s).
136        parents = [b for b in bases if issubclass(b, Resource)]
137        # Simulate the MRO.
138        parents.reverse()
139
140        for p in parents:
141            parent_fields = getattr(p, 'base_fields', {})
142
143            for field_name, field_object in parent_fields.items():
144                attrs['base_fields'][field_name] = deepcopy(field_object)
145
146        for field_name, obj in attrs.copy().items():
147            # Look for ``dehydrated_type`` instead of doing ``isinstance``,
148            # which can break down if Tastypie is re-namespaced as something
149            # else.
150            if hasattr(obj, 'dehydrated_type'):
151                field = attrs.pop(field_name)
152                declared_fields[field_name] = field
153
154        attrs['base_fields'].update(declared_fields)
155        attrs['declared_fields'] = declared_fields
156        new_class = super(DeclarativeMetaclass, cls).__new__(cls, name, bases, attrs)
157        opts = getattr(new_class, 'Meta', None)
158        new_class._meta = ResourceOptions(opts)
159        abstract = getattr(new_class._meta, 'abstract', False)
160
161        if not getattr(new_class._meta, 'resource_name', None):
162            # No ``resource_name`` provided. Attempt to auto-name the resource.
163            class_name = new_class.__name__
164            name_bits = [bit for bit in class_name.split('Resource') if bit]
165            resource_name = ''.join(name_bits).lower()
166            new_class._meta.resource_name = resource_name
167
168        if getattr(new_class._meta, 'include_resource_uri', True):
169            if 'resource_uri' not in new_class.base_fields:
170                new_class.base_fields['resource_uri'] = fields.CharField(readonly=True, verbose_name="resource uri")
171        elif 'resource_uri' in new_class.base_fields and 'resource_uri' not in attrs:
172            del(new_class.base_fields['resource_uri'])
173
174        if abstract and 'resource_uri' not in attrs:
175            # abstract classes don't have resource_uris unless explicitly provided
176            if 'resource_uri' in new_class.base_fields:
177                del(new_class.base_fields['resource_uri'])
178
179        for field_name, field_object in new_class.base_fields.items():
180            if hasattr(field_object, 'contribute_to_class'):
181                field_object.contribute_to_class(new_class, field_name)
182
183        return new_class
184
185
186class Resource(six.with_metaclass(DeclarativeMetaclass)):
187    """
188    Handles the data, request dispatch and responding to requests.
189
190    Serialization/deserialization is handled "at the edges" (i.e. at the
191    beginning/end of the request/response cycle) so that everything internally
192    is Python data structures.
193
194    This class tries to be non-model specific, so it can be hooked up to other
195    data sources, such as search results, files, other data, etc.
196    """
197    def __init__(self, api_name=None):
198        # this can cause:
199        # TypeError: object.__new__(method-wrapper) is not safe, use method-wrapper.__new__()
200        # when trying to copy a generator used as a default. Wrap call to
201        # generator in lambda to get around this error.
202        self.fields = {k: copy(v) for k, v in self.base_fields.items()}
203
204        if api_name is not None:
205            self._meta.api_name = api_name
206
207    def __getattr__(self, name):
208        if name == '__setstate__':
209            raise AttributeError(name)
210        try:
211            return self.fields[name]
212        except KeyError:
213            raise AttributeError(name)
214
215    def wrap_view(self, view):
216        """
217        Wraps methods so they can be called in a more functional way as well
218        as handling exceptions better.
219
220        Note that if ``BadRequest`` or an exception with a ``response`` attr
221        are seen, there is special handling to either present a message back
222        to the user or return the response traveling with the exception.
223        """
224        @csrf_exempt
225        def wrapper(request, *args, **kwargs):
226            try:
227                callback = getattr(self, view)
228                response = callback(request, *args, **kwargs)
229
230                # Our response can vary based on a number of factors, use
231                # the cache class to determine what we should ``Vary`` on so
232                # caches won't return the wrong (cached) version.
233                varies = getattr(self._meta.cache, "varies", [])
234
235                if varies:
236                    patch_vary_headers(response, varies)
237
238                if self._meta.cache.cacheable(request, response):
239                    if self._meta.cache.cache_control():
240                        # If the request is cacheable and we have a
241                        # ``Cache-Control`` available then patch the header.
242                        patch_cache_control(response, **self._meta.cache.cache_control())
243
244                if is_ajax(request) and not response.has_header("Cache-Control"):
245                    # IE excessively caches XMLHttpRequests, so we're disabling
246                    # the browser cache here.
247                    # See http://www.enhanceie.com/ie/bugs.asp for details.
248                    patch_cache_control(response, no_cache=True)
249
250                return response
251            except (BadRequest, fields.ApiFieldError) as e:
252                data = {"error": sanitize(e.args[0]) if getattr(e, 'args') else ''}
253                return self.error_response(request, data, response_class=http.HttpBadRequest)
254            except ValidationError as e:
255                data = {"error": sanitize(e.messages)}
256                return self.error_response(request, data, response_class=http.HttpBadRequest)
257            except Exception as e:
258                # Prevent muting non-django's exceptions
259                # i.e. RequestException from 'requests' library
260                if hasattr(e, 'response') and isinstance(e.response, HttpResponse):
261                    return e.response
262
263                # A real, non-expected exception.
264                # Handle the case where the full traceback is more helpful
265                # than the serialized error.
266                if settings.DEBUG and getattr(settings, 'TASTYPIE_FULL_DEBUG', False):
267                    raise
268
269                # Re-raise the error to get a proper traceback when the error
270                # happend during a test case
271                if request.META.get('SERVER_NAME') == 'testserver':
272                    raise
273
274                # Rather than re-raising, we're going to things similar to
275                # what Django does. The difference is returning a serialized
276                # error message.
277                return self._handle_500(request, e)
278
279        return wrapper
280
281    def get_response_class_for_exception(self, request, exception):
282        """
283        Can be overridden to customize response classes used for uncaught
284        exceptions. Should always return a subclass of
285        ``django.http.HttpResponse``.
286        """
287        if isinstance(exception, (NotFound, ObjectDoesNotExist, Http404)):
288            return HttpResponseNotFound
289        elif isinstance(exception, UnsupportedSerializationFormat):
290            return http.HttpNotAcceptable
291        elif isinstance(exception, UnsupportedDeserializationFormat):
292            return http.HttpUnsupportedMediaType
293        elif isinstance(exception, UnsupportedFormat):
294            return http.HttpBadRequest
295
296        return http.HttpApplicationError
297
298    def _handle_500(self, request, exception):
299        the_trace = traceback.format_exception(*sys.exc_info())
300        if six.PY2:
301            the_trace = [
302                six.text_type(line, 'utf-8')
303                for line in the_trace
304            ]
305        the_trace = u'\n'.join(the_trace)
306
307        response_class = self.get_response_class_for_exception(request, exception)
308
309        if settings.DEBUG:
310            data = {
311                "error_message": sanitize(six.text_type(exception)),
312                "traceback": the_trace,
313            }
314        else:
315            data = {
316                "error_message": getattr(settings, 'TASTYPIE_CANNED_ERROR', "Sorry, this request could not be processed. Please try again later."),
317            }
318
319        if response_class.status_code >= 500:
320            log = logging.getLogger('django.request.tastypie')
321            log.error('Internal Server Error: %s' % request.path, exc_info=True,
322                      extra={'status_code': response_class.status_code, 'request': request})
323
324        # Send the signal so other apps are aware of the exception.
325        got_request_exception.send(self.__class__, request=request)
326
327        return self.error_response(request, data, response_class=response_class)
328
329    def _build_reverse_url(self, name, args=None, kwargs=None):
330        """
331        A convenience hook for overriding how URLs are built.
332
333        See ``NamespacedModelResource._build_reverse_url`` for an example.
334        """
335        return reverse(name, args=args, kwargs=kwargs)
336
337    def base_urls(self):
338        """
339        The standard URLs this ``Resource`` should respond to.
340        """
341        return [
342            re_path(r"^(?P<resource_name>%s)%s$" % (self._meta.resource_name, trailing_slash), self.wrap_view('dispatch_list'), name="api_dispatch_list"),
343            re_path(r"^(?P<resource_name>%s)/schema%s$" % (self._meta.resource_name, trailing_slash), self.wrap_view('get_schema'), name="api_get_schema"),
344            re_path(r"^(?P<resource_name>%s)/set/(?P<%s_list>.*?)%s$" % (self._meta.resource_name, self._meta.detail_uri_name, trailing_slash), self.wrap_view('get_multiple'), name="api_get_multiple"),
345            re_path(r"^(?P<resource_name>%s)/(?P<%s>.*?)%s$" % (self._meta.resource_name, self._meta.detail_uri_name, trailing_slash), self.wrap_view('dispatch_detail'), name="api_dispatch_detail"),
346        ]
347
348    def override_urls(self):
349        """
350        Deprecated. Will be removed by v1.0.0. Please use ``prepend_urls`` instead.
351        """
352        return []
353
354    def prepend_urls(self):
355        """
356        A hook for adding your own URLs or matching before the default URLs.
357        """
358        return []
359
360    @property
361    def urls(self):
362        """
363        The endpoints this ``Resource`` responds to.
364
365        Mostly a standard URLconf, this is suitable for either automatic use
366        when registered with an ``Api`` class or for including directly in
367        a URLconf should you choose to.
368        """
369        urls = self.prepend_urls()
370
371        overridden_urls = self.override_urls()
372        if overridden_urls:
373            warnings.warn("'override_urls' is a deprecated method & will be removed by v1.0.0. Please rename your method to ``prepend_urls``.")
374            urls += overridden_urls
375
376        urls += self.base_urls()
377        return urls
378
379    def determine_format(self, request):
380        """
381        Used to determine the desired format.
382
383        Largely relies on ``tastypie.utils.mime.determine_format`` but here
384        as a point of extension.
385        """
386        return determine_format(request, self._meta.serializer, default_format=self._meta.default_format)
387
388    def serialize(self, request, data, format, options=None):
389        """
390        Given a request, data and a desired format, produces a serialized
391        version suitable for transfer over the wire.
392
393        Mostly a hook, this uses the ``Serializer`` from ``Resource._meta``.
394        """
395        options = options or {}
396
397        if 'text/javascript' in format:
398            # get JSONP callback name. default to "callback"
399            callback = request.GET.get('callback', 'callback')
400
401            if not is_valid_jsonp_callback_value(callback):
402                raise BadRequest('JSONP callback name is invalid.')
403
404            options['callback'] = callback
405
406        return self._meta.serializer.serialize(data, format, options)
407
408    def deserialize(self, request, data, format='application/json'):
409        """
410        Given a request, data and a format, deserializes the given data.
411
412        It relies on the request properly sending a ``CONTENT_TYPE`` header,
413        falling back to ``application/json`` if not provided.
414
415        Mostly a hook, this uses the ``Serializer`` from ``Resource._meta``.
416        """
417        deserialized = self._meta.serializer.deserialize(data, format=request.META.get('CONTENT_TYPE', format))
418        return deserialized
419
420    def alter_list_data_to_serialize(self, request, data):
421        """
422        A hook to alter list data just before it gets serialized & sent to the user.
423
424        Useful for restructuring/renaming aspects of the what's going to be
425        sent.
426
427        Should accommodate for a list of objects, generally also including
428        meta data.
429        """
430        return data
431
432    def alter_detail_data_to_serialize(self, request, data):
433        """
434        A hook to alter detail data just before it gets serialized & sent to the user.
435
436        Useful for restructuring/renaming aspects of the what's going to be
437        sent.
438
439        Should accommodate for receiving a single bundle of data.
440        """
441        return data
442
443    def alter_deserialized_list_data(self, request, data):
444        """
445        A hook to alter list data just after it has been received from the user &
446        gets deserialized.
447
448        Useful for altering the user data before any hydration is applied.
449        """
450        return data
451
452    def alter_deserialized_detail_data(self, request, data):
453        """
454        A hook to alter detail data just after it has been received from the user &
455        gets deserialized.
456
457        Useful for altering the user data before any hydration is applied.
458        """
459        return data
460
461    def dispatch_list(self, request, **kwargs):
462        """
463        A view for handling the various HTTP methods (GET/POST/PUT/DELETE) over
464        the entire list of resources.
465
466        Relies on ``Resource.dispatch`` for the heavy-lifting.
467        """
468        return self.dispatch('list', request, **kwargs)
469
470    def dispatch_detail(self, request, **kwargs):
471        """
472        A view for handling the various HTTP methods (GET/POST/PUT/DELETE) on
473        a single resource.
474
475        Relies on ``Resource.dispatch`` for the heavy-lifting.
476        """
477        return self.dispatch('detail', request, **kwargs)
478
479    def dispatch(self, request_type, request, **kwargs):
480        """
481        Handles the common operations (allowed HTTP method, authentication,
482        throttling, method lookup) surrounding most CRUD interactions.
483        """
484        allowed_methods = getattr(self._meta, "%s_allowed_methods" % request_type, None)
485
486        if 'HTTP_X_HTTP_METHOD_OVERRIDE' in request.META:
487            request.method = request.META['HTTP_X_HTTP_METHOD_OVERRIDE']
488
489        request_method = self.method_check(request, allowed=allowed_methods)
490        method = getattr(self, "%s_%s" % (request_method, request_type), None)
491
492        if method is None:
493            raise ImmediateHttpResponse(response=http.HttpNotImplemented())
494
495        self.is_authenticated(request)
496        self.throttle_check(request)
497
498        # All clear. Process the request.
499        request = convert_post_to_put(request)
500        response = method(request, **kwargs)
501
502        # Add the throttled request.
503        self.log_throttled_access(request)
504
505        # If what comes back isn't a ``HttpResponse``, assume that the
506        # request was accepted and that some action occurred. This also
507        # prevents Django from freaking out.
508        if not isinstance(response, HttpResponse):
509            return http.HttpNoContent()
510
511        return response
512
513    def remove_api_resource_names(self, url_dict):
514        """
515        Given a dictionary of regex matches from a URLconf, removes
516        ``api_name`` and/or ``resource_name`` if found.
517
518        This is useful for converting URLconf matches into something suitable
519        for data lookup. For example::
520
521            Model.objects.filter(**self.remove_api_resource_names(matches))
522        """
523        kwargs_subset = url_dict.copy()
524
525        for key in ['api_name', 'resource_name']:
526            try:
527                del(kwargs_subset[key])
528            except KeyError:
529                pass
530
531        return kwargs_subset
532
533    def method_check(self, request, allowed=None):
534        """
535        Ensures that the HTTP method used on the request is allowed to be
536        handled by the resource.
537
538        Takes an ``allowed`` parameter, which should be a list of lowercase
539        HTTP methods to check against. Usually, this looks like::
540
541            # The most generic lookup.
542            self.method_check(request, self._meta.allowed_methods)
543
544            # A lookup against what's allowed for list-type methods.
545            self.method_check(request, self._meta.list_allowed_methods)
546
547            # A useful check when creating a new endpoint that only handles
548            # GET.
549            self.method_check(request, ['get'])
550        """
551        if allowed is None:
552            allowed = []
553
554        request_method = request.method.lower()
555        allows = ','.join([meth.upper() for meth in allowed])
556
557        if request_method == "options":
558            response = HttpResponse(allows)
559            response['Allow'] = allows
560            raise ImmediateHttpResponse(response=response)
561
562        if request_method not in allowed:
563            response = http.HttpMethodNotAllowed(allows)
564            response['Allow'] = allows
565            raise ImmediateHttpResponse(response=response)
566
567        return request_method
568
569    def is_authenticated(self, request):
570        """
571        Handles checking if the user is authenticated and dealing with
572        unauthenticated users.
573
574        Mostly a hook, this uses class assigned to ``authentication`` from
575        ``Resource._meta``.
576        """
577        # Authenticate the request as needed.
578        auth_result = self._meta.authentication.is_authenticated(request)
579
580        if isinstance(auth_result, HttpResponse):
581            raise ImmediateHttpResponse(response=auth_result)
582
583        if auth_result is not True:
584            raise ImmediateHttpResponse(response=http.HttpUnauthorized())
585
586    def throttle_check(self, request):
587        """
588        Handles checking if the user should be throttled.
589
590        Mostly a hook, this uses class assigned to ``throttle`` from
591        ``Resource._meta``.
592        """
593        identifier = self._meta.authentication.get_identifier(request)
594
595        # Check to see if they should be throttled.
596        throttle = self._meta.throttle.should_be_throttled(identifier)
597
598        if throttle:
599            # Throttle limit exceeded.
600
601            response = http.HttpTooManyRequests()
602
603            if isinstance(throttle, int) and not isinstance(throttle, bool):
604                response['Retry-After'] = throttle
605            elif isinstance(throttle, datetime):
606                # change to UTC (GMT) and make naive, to avoid wsgiref also doing an implicit TZ conversion
607                throttle_utc = make_naive_utc(throttle)
608                response['Retry-After'] = format_date_time(mktime(throttle_utc.timetuple()))
609
610            raise ImmediateHttpResponse(response=response)
611
612    def log_throttled_access(self, request):
613        """
614        Handles the recording of the user's access for throttling purposes.
615
616        Mostly a hook, this uses class assigned to ``throttle`` from
617        ``Resource._meta``.
618        """
619        request_method = request.method.lower()
620        self._meta.throttle.accessed(self._meta.authentication.get_identifier(request), url=request.get_full_path(), request_method=request_method)
621
622    def unauthorized_result(self, exception):
623        raise ImmediateHttpResponse(response=http.HttpUnauthorized())
624
625    def authorized_read_list(self, object_list, bundle):
626        """
627        Handles checking of permissions to see if the user has authorization
628        to GET this resource.
629        """
630        try:
631            auth_result = self._meta.authorization.read_list(object_list, bundle)
632        except Unauthorized as e:
633            self.unauthorized_result(e)
634
635        return auth_result
636
637    def authorized_read_detail(self, object_list, bundle):
638        """
639        Handles checking of permissions to see if the user has authorization
640        to GET this resource.
641        """
642        try:
643            auth_result = self._meta.authorization.read_detail(object_list, bundle)
644            if auth_result is not True:
645                raise Unauthorized()
646        except Unauthorized as e:
647            self.unauthorized_result(e)
648
649        return auth_result
650
651    def authorized_create_list(self, object_list, bundle):
652        """
653        Handles checking of permissions to see if the user has authorization
654        to POST this resource.
655        """
656        try:
657            auth_result = self._meta.authorization.create_list(object_list, bundle)
658        except Unauthorized as e:
659            self.unauthorized_result(e)
660
661        return auth_result
662
663    def authorized_create_detail(self, object_list, bundle):
664        """
665        Handles checking of permissions to see if the user has authorization
666        to POST this resource.
667        """
668        try:
669            auth_result = self._meta.authorization.create_detail(object_list, bundle)
670            if auth_result is not True:
671                raise Unauthorized()
672        except Unauthorized as e:
673            self.unauthorized_result(e)
674
675        return auth_result
676
677    def authorized_update_list(self, object_list, bundle):
678        """
679        Handles checking of permissions to see if the user has authorization
680        to PUT this resource.
681        """
682        try:
683            auth_result = self._meta.authorization.update_list(object_list, bundle)
684        except Unauthorized as e:
685            self.unauthorized_result(e)
686
687        return auth_result
688
689    def authorized_update_detail(self, object_list, bundle):
690        """
691        Handles checking of permissions to see if the user has authorization
692        to PUT this resource.
693        """
694        try:
695            auth_result = self._meta.authorization.update_detail(object_list, bundle)
696            if auth_result is not True:
697                raise Unauthorized()
698        except Unauthorized as e:
699            self.unauthorized_result(e)
700
701        return auth_result
702
703    def authorized_delete_list(self, object_list, bundle):
704        """
705        Handles checking of permissions to see if the user has authorization
706        to DELETE this resource.
707        """
708        try:
709            auth_result = self._meta.authorization.delete_list(object_list, bundle)
710        except Unauthorized as e:
711            self.unauthorized_result(e)
712
713        return auth_result
714
715    def authorized_delete_detail(self, object_list, bundle):
716        """
717        Handles checking of permissions to see if the user has authorization
718        to DELETE this resource.
719        """
720        try:
721            auth_result = self._meta.authorization.delete_detail(object_list, bundle)
722            if not auth_result:
723                raise Unauthorized()
724        except Unauthorized as e:
725            self.unauthorized_result(e)
726
727        return auth_result
728
729    def build_bundle(self, obj=None, data=None, request=None, objects_saved=None, via_uri=None):
730        """
731        Given either an object, a data dictionary or both, builds a ``Bundle``
732        for use throughout the ``dehydrate/hydrate`` cycle.
733
734        If no object is provided, an empty object from
735        ``Resource._meta.object_class`` is created so that attempts to access
736        ``bundle.obj`` do not fail.
737        """
738        if obj is None and self._meta.object_class:
739            obj = self._meta.object_class()
740
741        return Bundle(
742            obj=obj,
743            data=data,
744            request=request,
745            objects_saved=objects_saved,
746            via_uri=via_uri
747        )
748
749    def build_filters(self, filters=None, ignore_bad_filters=False):
750        """
751        Allows for the filtering of applicable objects.
752
753        This needs to be implemented at the user level.'
754
755        ``ModelResource`` includes a full working version specific to Django's
756        ``Models``.
757        """
758        return filters
759
760    def apply_sorting(self, obj_list, options=None):
761        """
762        Allows for the sorting of objects being returned.
763
764        This needs to be implemented at the user level.
765
766        ``ModelResource`` includes a full working version specific to Django's
767        ``Models``.
768        """
769        return obj_list
770
771    def get_bundle_detail_data(self, bundle):
772        """
773        Convenience method to return the ``detail_uri_name`` attribute off
774        ``bundle.obj``.
775
776        Usually just accesses ``bundle.obj.pk`` by default.
777        """
778        return getattr(bundle.obj, self._meta.detail_uri_name, None)
779
780    # URL-related methods.
781
782    def detail_uri_kwargs(self, bundle_or_obj):
783        """
784        Given a ``Bundle`` or an object (typically a ``Model`` instance),
785        it returns the extra kwargs needed to generate a detail URI.
786
787        By default, it uses this resource's ``detail_uri_name`` in order to
788        create the URI.
789        """
790        kwargs = {}
791
792        if isinstance(bundle_or_obj, Bundle):
793            bundle_or_obj = bundle_or_obj.obj
794
795        kwargs[self._meta.detail_uri_name] = getattr(bundle_or_obj, self._meta.detail_uri_name)
796
797        return kwargs
798
799    def resource_uri_kwargs(self, bundle_or_obj=None):
800        """
801        Builds a dictionary of kwargs to help generate URIs.
802
803        Automatically provides the ``Resource.Meta.resource_name`` (and
804        optionally the ``Resource.Meta.api_name`` if populated by an ``Api``
805        object).
806
807        If the ``bundle_or_obj`` argument is provided, it calls
808        ``Resource.detail_uri_kwargs`` for additional bits to create
809        """
810        kwargs = {
811            'resource_name': self._meta.resource_name,
812        }
813
814        if self._meta.api_name is not None:
815            kwargs['api_name'] = self._meta.api_name
816
817        if bundle_or_obj is not None:
818            kwargs.update(self.detail_uri_kwargs(bundle_or_obj))
819
820        return kwargs
821
822    def get_resource_uri(self, bundle_or_obj=None, url_name='api_dispatch_list'):
823        """
824        Handles generating a resource URI.
825
826        If the ``bundle_or_obj`` argument is not provided, it builds the URI
827        for the list endpoint.
828
829        If the ``bundle_or_obj`` argument is provided, it builds the URI for
830        the detail endpoint.
831
832        Return the generated URI. If that URI can not be reversed (not found
833        in the URLconf), it will return an empty string.
834        """
835        if bundle_or_obj is not None:
836            url_name = 'api_dispatch_detail'
837
838        try:
839            return self._build_reverse_url(url_name, kwargs=self.resource_uri_kwargs(bundle_or_obj))
840        except NoReverseMatch:
841            return ''
842
843    def get_via_uri(self, uri, request=None):
844        """
845        This pulls apart the salient bits of the URI and populates the
846        resource via a ``obj_get``.
847
848        Optionally accepts a ``request``.
849
850        If you need custom behavior based on other portions of the URI,
851        simply override this method.
852        """
853        prefix = get_script_prefix()
854        chomped_uri = uri
855
856        if prefix and chomped_uri.startswith(prefix):
857            chomped_uri = chomped_uri[len(prefix) - 1:]
858
859        # We know that we are dealing with a "detail" URI
860        # Look for the beginning of object key (last meaningful part of the URI)
861        end_of_resource_name = chomped_uri.rstrip('/').rfind('/')
862        if end_of_resource_name == -1:
863            raise NotFound("An incorrect URL was provided '%s' for the '%s' resource." % (uri, self.__class__.__name__))
864        # We mangle the path a bit further & run URL resolution against *only*
865        # the current class (but up to detail key). This ought to prevent bad
866        # URLs from resolving to incorrect data.
867        split_url = chomped_uri.rstrip('/').rsplit('/', 1)[0]
868        if not split_url.endswith('/' + self._meta.resource_name):
869            raise NotFound("An incorrect URL was provided '%s' for the '%s' resource." % (uri, self.__class__.__name__))
870        found_at = chomped_uri.rfind(self._meta.resource_name, 0, end_of_resource_name)
871        chomped_uri = chomped_uri[found_at:]
872        try:
873            for url_resolver in getattr(self, 'urls', []):
874                result = url_resolver.resolve(chomped_uri)
875
876                if result is not None:
877                    view, args, kwargs = result
878                    break
879            else:
880                raise Resolver404("URI not found in 'self.urls'.")
881        except Resolver404:
882            raise NotFound("The URL provided '%s' was not a link to a valid resource." % uri)
883
884        bundle = self.build_bundle(request=request)
885        return self.obj_get(bundle=bundle, **self.remove_api_resource_names(kwargs))
886
887    # Data preparation.
888
889    def full_dehydrate(self, bundle, for_list=False):
890        """
891        Given a bundle with an object instance, extract the information from it
892        to populate the resource.
893        """
894        data = bundle.data
895
896        api_name = self._meta.api_name
897        resource_name = self._meta.resource_name
898
899        # Dehydrate each field.
900        for field_name, field_object in self.fields.items():
901            # If it's not for use in this mode, skip
902            field_use_in = field_object.use_in
903            if callable(field_use_in):
904                if not field_use_in(bundle):
905                    continue
906            else:
907                if field_use_in not in ['all', 'list' if for_list else 'detail']:
908                    continue
909
910            # A touch leaky but it makes URI resolution work.
911            if field_object.dehydrated_type == 'related':
912                field_object.api_name = api_name
913                field_object.resource_name = resource_name
914
915            data[field_name] = field_object.dehydrate(bundle, for_list=for_list)
916
917            # Check for an optional method to do further dehydration.
918            method = getattr(self, "dehydrate_%s" % field_name, None)
919
920            if method:
921                data[field_name] = method(bundle)
922
923        bundle = self.dehydrate(bundle)
924        return bundle
925
926    def dehydrate(self, bundle):
927        """
928        A hook to allow a final manipulation of data once all fields/methods
929        have built out the dehydrated data.
930
931        Useful if you need to access more than one dehydrated field or want
932        to annotate on additional data.
933
934        Must return the modified bundle.
935        """
936        return bundle
937
938    def full_hydrate(self, bundle):
939        """
940        Given a populated bundle, distill it and turn it back into
941        a full-fledged object instance.
942        """
943        if bundle.obj is None:
944            bundle.obj = self._meta.object_class()
945
946        bundle = self.hydrate(bundle)
947
948        for field_name, field_object in self.fields.items():
949            if field_object.readonly is True:
950                continue
951
952            # Check for an optional method to do further hydration.
953            method = getattr(self, "hydrate_%s" % field_name, None)
954
955            if method:
956                bundle = method(bundle)
957
958            if field_object.attribute:
959                value = field_object.hydrate(bundle)
960
961                # NOTE: We only get back a bundle when it is related field.
962                if isinstance(value, Bundle) and value.errors.get(field_name):
963                    bundle.errors[field_name] = value.errors[field_name]
964
965                if value is not None or field_object.null:
966                    # We need to avoid populating M2M data here as that will
967                    # cause things to blow up.
968                    if not field_object.is_related:
969                        setattr(bundle.obj, field_object.attribute, value)
970                    elif not field_object.is_m2m:
971                        if value is not None:
972                            # NOTE: A bug fix in Django (ticket #18153) fixes incorrect behavior
973                            # which Tastypie was relying on.  To fix this, we store value.obj to
974                            # be saved later in save_related.
975                            try:
976                                setattr(bundle.obj, field_object.attribute, value.obj)
977                            except (ValueError, ObjectDoesNotExist):
978                                bundle.related_objects_to_save[field_object.attribute] = value.obj
979                        # not required, not sent
980                        elif field_object.blank and field_name not in bundle.data:
981                            continue
982                        elif field_object.null:
983                            if not isinstance(getattr(bundle.obj.__class__, field_object.attribute, None), ReverseOneToOneDescriptor):
984                                # only update if not a reverse one to one field
985                                setattr(bundle.obj, field_object.attribute, value)
986
987        return bundle
988
989    def hydrate(self, bundle):
990        """
991        A hook to allow an initial manipulation of data before all methods/fields
992        have built out the hydrated data.
993
994        Useful if you need to access more than one hydrated field or want
995        to annotate on additional data.
996
997        Must return the modified bundle.
998        """
999        return bundle
1000
1001    def hydrate_m2m(self, bundle):
1002        """
1003        Populate the ManyToMany data on the instance.
1004        """
1005        if bundle.obj is None:
1006            raise HydrationError("You must call 'full_hydrate' before attempting to run 'hydrate_m2m' on %r." % self)
1007
1008        for field_name, field_object in self.fields.items():
1009            if not field_object.is_m2m:
1010                continue
1011
1012            if field_object.attribute:
1013                # Note that we only hydrate the data, leaving the instance
1014                # unmodified. It's up to the user's code to handle this.
1015                # The ``ModelResource`` provides a working baseline
1016                # in this regard.
1017                bundle.data[field_name] = field_object.hydrate_m2m(bundle)
1018
1019        for field_name, field_object in self.fields.items():
1020            if not field_object.is_m2m:
1021                continue
1022
1023            method = getattr(self, "hydrate_%s" % field_name, None)
1024
1025            if method:
1026                method(bundle)
1027
1028        return bundle
1029
1030    def build_schema(self):
1031        """
1032        Returns a dictionary of all the fields on the resource and some
1033        properties about those fields.
1034
1035        Used by the ``schema/`` endpoint to describe what will be available.
1036        """
1037        data = {
1038            'fields': {},
1039            'default_format': self._meta.default_format,
1040            'allowed_list_http_methods': self._meta.list_allowed_methods,
1041            'allowed_detail_http_methods': self._meta.detail_allowed_methods,
1042            'default_limit': self._meta.limit,
1043        }
1044
1045        if self._meta.ordering:
1046            data['ordering'] = self._meta.ordering
1047
1048        if self._meta.filtering:
1049            data['filtering'] = self._meta.filtering
1050
1051        # Skip assigning pk_field_name for non-model resources
1052        try:
1053            pk_field_name = self._meta.queryset.model._meta.pk.name
1054        except AttributeError:
1055            pk_field_name = None
1056
1057        for field_name, field_object in self.fields.items():
1058            data['fields'][field_name] = {
1059                'default': field_object.default,
1060                'type': field_object.dehydrated_type,
1061                'nullable': field_object.null,
1062                'blank': field_object.blank,
1063                'readonly': field_object.readonly,
1064                'help_text': field_object.help_text,
1065                'unique': field_object.unique,
1066                'primary_key': True if field_name == pk_field_name else False,
1067                'verbose_name': field_object.verbose_name or field_name.replace("_", " "),
1068            }
1069
1070            if field_object.dehydrated_type == 'related':
1071                if field_object.is_m2m:
1072                    related_type = 'to_many'
1073                else:
1074                    related_type = 'to_one'
1075                data['fields'][field_name]['related_type'] = related_type
1076                try:
1077                    uri = self._build_reverse_url('api_get_schema', kwargs={
1078                        'api_name': self._meta.api_name,
1079                        'resource_name': field_object.to_class()._meta.resource_name
1080                    })
1081                except NoReverseMatch:
1082                    uri = ''
1083                data['fields'][field_name]['related_schema'] = uri
1084
1085        return data
1086
1087    def dehydrate_resource_uri(self, bundle):
1088        """
1089        For the automatically included ``resource_uri`` field, dehydrate
1090        the URI for the given bundle.
1091
1092        Returns empty string if no URI can be generated.
1093        """
1094        try:
1095            return self.get_resource_uri(bundle)
1096        except NotImplementedError:
1097            return ''
1098        except NoReverseMatch:
1099            return ''
1100
1101    def generate_cache_key(self, *args, **kwargs):
1102        """
1103        Creates a unique-enough cache key.
1104
1105        This is based off the current api_name/resource_name/args/kwargs.
1106        """
1107        smooshed = ["%s=%s" % (key, value) for key, value in kwargs.items()]
1108
1109        # Use a list plus a ``.join()`` because it's faster than concatenation.
1110        return "%s:%s:%s:%s" % (self._meta.api_name, self._meta.resource_name, ':'.join(args), ':'.join(sorted(smooshed)))
1111
1112    # Data access methods.
1113
1114    def get_object_list(self, request):
1115        """
1116        A hook to allow making returning the list of available objects.
1117
1118        This needs to be implemented at the user level.
1119
1120        ``ModelResource`` includes a full working version specific to Django's
1121        ``Models``.
1122        """
1123        raise NotImplementedError()
1124
1125    def can_create(self):
1126        """
1127        Checks to ensure ``post`` is within ``allowed_methods``.
1128        """
1129        allowed = set(self._meta.list_allowed_methods + self._meta.detail_allowed_methods)
1130        return 'post' in allowed
1131
1132    def can_update(self):
1133        """
1134        Checks to ensure ``put`` is within ``allowed_methods``.
1135
1136        Used when hydrating related data.
1137        """
1138        allowed = set(self._meta.list_allowed_methods + self._meta.detail_allowed_methods)
1139        return 'put' in allowed
1140
1141    def can_delete(self):
1142        """
1143        Checks to ensure ``delete`` is within ``allowed_methods``.
1144        """
1145        allowed = set(self._meta.list_allowed_methods + self._meta.detail_allowed_methods)
1146        return 'delete' in allowed
1147
1148    def apply_filters(self, request, applicable_filters):
1149        """
1150        A hook to alter how the filters are applied to the object list.
1151
1152        This needs to be implemented at the user level.
1153
1154        ``ModelResource`` includes a full working version specific to Django's
1155        ``Models``.
1156        """
1157        raise NotImplementedError()
1158
1159    def obj_get_list(self, bundle, **kwargs):
1160        """
1161        Fetches the list of objects available on the resource.
1162
1163        This needs to be implemented at the user level.
1164
1165        ``ModelResource`` includes a full working version specific to Django's
1166        ``Models``.
1167        """
1168        raise NotImplementedError()
1169
1170    def cached_obj_get_list(self, bundle, **kwargs):
1171        """
1172        A version of ``obj_get_list`` that uses the cache as a means to get
1173        commonly-accessed data faster.
1174        """
1175        cache_key = self.generate_cache_key('list', **kwargs)
1176        obj_list = self._meta.cache.get(cache_key)
1177
1178        if obj_list is None:
1179            obj_list = self.obj_get_list(bundle=bundle, **kwargs)
1180            self._meta.cache.set(cache_key, obj_list)
1181
1182        return obj_list
1183
1184    def obj_get(self, bundle, **kwargs):
1185        """
1186        Fetches an individual object on the resource.
1187
1188        This needs to be implemented at the user level. If the object can not
1189        be found, this should raise a ``NotFound`` exception.
1190
1191        ``ModelResource`` includes a full working version specific to Django's
1192        ``Models``.
1193        """
1194        raise NotImplementedError()
1195
1196    def cached_obj_get(self, bundle, **kwargs):
1197        """
1198        A version of ``obj_get`` that uses the cache as a means to get
1199        commonly-accessed data faster.
1200        """
1201        cache_key = self.generate_cache_key('detail', **kwargs)
1202        cached_bundle = self._meta.cache.get(cache_key)
1203
1204        if cached_bundle is None:
1205            cached_bundle = self.obj_get(bundle=bundle, **kwargs)
1206            self._meta.cache.set(cache_key, cached_bundle)
1207
1208        return cached_bundle
1209
1210    def obj_create(self, bundle, **kwargs):
1211        """
1212        Creates a new object based on the provided data.
1213
1214        This needs to be implemented at the user level.
1215
1216        ``ModelResource`` includes a full working version specific to Django's
1217        ``Models``.
1218        """
1219        raise NotImplementedError()
1220
1221    def obj_update(self, bundle, **kwargs):
1222        """
1223        Updates an existing object (or creates a new object) based on the
1224        provided data.
1225
1226        This needs to be implemented at the user level.
1227
1228        ``ModelResource`` includes a full working version specific to Django's
1229        ``Models``.
1230        """
1231        raise NotImplementedError()
1232
1233    def obj_delete_list(self, bundle, **kwargs):
1234        """
1235        Deletes an entire list of objects.
1236
1237        This needs to be implemented at the user level.
1238
1239        ``ModelResource`` includes a full working version specific to Django's
1240        ``Models``.
1241        """
1242        raise NotImplementedError()
1243
1244    def obj_delete_list_for_update(self, bundle, **kwargs):
1245        """
1246        Deletes an entire list of objects, specific to PUT list.
1247
1248        This needs to be implemented at the user level.
1249
1250        ``ModelResource`` includes a full working version specific to Django's
1251        ``Models``.
1252        """
1253        raise NotImplementedError()
1254
1255    def obj_delete(self, bundle, **kwargs):
1256        """
1257        Deletes a single object.
1258
1259        This needs to be implemented at the user level.
1260
1261        ``ModelResource`` includes a full working version specific to Django's
1262        ``Models``.
1263        """
1264        raise NotImplementedError()
1265
1266    def create_response(self, request, data, response_class=HttpResponse, **response_kwargs):
1267        """
1268        Extracts the common "which-format/serialize/return-response" cycle.
1269
1270        Mostly a useful shortcut/hook.
1271        """
1272        desired_format = self.determine_format(request)
1273        serialized = self.serialize(request, data, desired_format)
1274        return response_class(content=serialized, content_type=build_content_type(desired_format), **response_kwargs)
1275
1276    def error_response(self, request, errors, response_class=None):
1277        """
1278        Extracts the common "which-format/serialize/return-error-response"
1279        cycle.
1280
1281        Should be used as much as possible to return errors.
1282        """
1283        if response_class is None:
1284            response_class = http.HttpBadRequest
1285
1286        desired_format = None
1287
1288        if request:
1289            if request.GET.get('callback', None) is None:
1290                try:
1291                    desired_format = self.determine_format(request)
1292                except BadRequest:
1293                    pass  # Fall through to default handler below
1294            else:
1295                # JSONP can cause extra breakage.
1296                desired_format = 'application/json'
1297
1298        if not desired_format:
1299            desired_format = self._meta.default_format
1300
1301        try:
1302            serialized = self.serialize(request, errors, desired_format)
1303        except BadRequest as e:
1304            error = "Additional errors occurred, but serialization of those errors failed."
1305
1306            if settings.DEBUG:
1307                error += " %s" % e
1308
1309            return response_class(content=error, content_type='text/plain')
1310
1311        return response_class(content=serialized, content_type=build_content_type(desired_format))
1312
1313    def is_valid(self, bundle):
1314        """
1315        Handles checking if the data provided by the user is valid.
1316
1317        Mostly a hook, this uses class assigned to ``validation`` from
1318        ``Resource._meta``.
1319
1320        If validation fails, an error is raised with the error messages
1321        serialized inside it.
1322        """
1323        errors = self._meta.validation.is_valid(bundle, bundle.request)
1324
1325        if errors:
1326            bundle.errors[self._meta.resource_name] = errors
1327            return False
1328
1329        return True
1330
1331    def rollback(self, bundles):
1332        """
1333        Given the list of bundles, delete all objects pertaining to those
1334        bundles.
1335
1336        This needs to be implemented at the user level. No exceptions should
1337        be raised if possible.
1338
1339        ``ModelResource`` includes a full working version specific to Django's
1340        ``Models``.
1341        """
1342        raise NotImplementedError()
1343
1344    # Views.
1345
1346    def get_list(self, request, **kwargs):
1347        """
1348        Returns a serialized list of resources.
1349
1350        Calls ``obj_get_list`` to provide the data, then handles that result
1351        set and serializes it.
1352
1353        Should return a HttpResponse (200 OK).
1354        """
1355        # TODO: Uncached for now. Invalidation that works for everyone may be
1356        #       impossible.
1357        base_bundle = self.build_bundle(request=request)
1358        objects = self.obj_get_list(bundle=base_bundle, **self.remove_api_resource_names(kwargs))
1359        sorted_objects = self.apply_sorting(objects, options=request.GET)
1360
1361        paginator = self._meta.paginator_class(request.GET, sorted_objects, resource_uri=self.get_resource_uri(), limit=self._meta.limit, max_limit=self._meta.max_limit, collection_name=self._meta.collection_name)
1362        to_be_serialized = paginator.page()
1363
1364        # Dehydrate the bundles in preparation for serialization.
1365        bundles = [
1366            self.full_dehydrate(self.build_bundle(obj=obj, request=request), for_list=True)
1367            for obj in to_be_serialized[self._meta.collection_name]
1368        ]
1369
1370        to_be_serialized[self._meta.collection_name] = bundles
1371        to_be_serialized = self.alter_list_data_to_serialize(request, to_be_serialized)
1372        return self.create_response(request, to_be_serialized)
1373
1374    def get_detail(self, request, **kwargs):
1375        """
1376        Returns a single serialized resource.
1377
1378        Calls ``cached_obj_get/obj_get`` to provide the data, then handles that result
1379        set and serializes it.
1380
1381        Should return a HttpResponse (200 OK).
1382        """
1383        basic_bundle = self.build_bundle(request=request)
1384
1385        try:
1386            obj = self.cached_obj_get(bundle=basic_bundle, **self.remove_api_resource_names(kwargs))
1387        except ObjectDoesNotExist:
1388            return http.HttpNotFound()
1389        except MultipleObjectsReturned:
1390            return http.HttpMultipleChoices("More than one resource is found at this URI.")
1391
1392        bundle = self.build_bundle(obj=obj, request=request)
1393        bundle = self.full_dehydrate(bundle)
1394        bundle = self.alter_detail_data_to_serialize(request, bundle)
1395        return self.create_response(request, bundle)
1396
1397    def post_list(self, request, **kwargs):
1398        """
1399        Creates a new resource/object with the provided data.
1400
1401        Calls ``obj_create`` with the provided data and returns a response
1402        with the new resource's location.
1403
1404        If a new resource is created, return ``HttpCreated`` (201 Created).
1405        If ``Meta.always_return_data = True``, there will be a populated body
1406        of serialized data.
1407        """
1408        deserialized = self.deserialize(request, request.body, format=request.META.get('CONTENT_TYPE', 'application/json'))
1409        deserialized = self.alter_deserialized_detail_data(request, deserialized)
1410        bundle = self.build_bundle(data=dict_strip_unicode_keys(deserialized), request=request)
1411        updated_bundle = self.obj_create(bundle, **self.remove_api_resource_names(kwargs))
1412        location = self.get_resource_uri(updated_bundle)
1413
1414        if not self._meta.always_return_data:
1415            return http.HttpCreated(location=location)
1416        else:
1417            updated_bundle = self.full_dehydrate(updated_bundle)
1418            updated_bundle = self.alter_detail_data_to_serialize(request, updated_bundle)
1419            return self.create_response(request, updated_bundle, response_class=http.HttpCreated, location=location)
1420
1421    def post_detail(self, request, **kwargs):
1422        """
1423        Creates a new subcollection of the resource under a resource.
1424
1425        This is not implemented by default because most people's data models
1426        aren't self-referential.
1427
1428        If a new resource is created, return ``HttpCreated`` (201 Created).
1429        """
1430        return http.HttpNotImplemented()
1431
1432    def put_list(self, request, **kwargs):
1433        """
1434        Replaces a collection of resources with another collection.
1435
1436        Calls ``delete_list`` to clear out the collection then ``obj_create``
1437        with the provided the data to create the new collection.
1438
1439        Return ``HttpNoContent`` (204 No Content) if
1440        ``Meta.always_return_data = False`` (default).
1441
1442        Return ``HttpResponse`` (200 OK) with new data if
1443        ``Meta.always_return_data = True``.
1444        """
1445        deserialized = self.deserialize(request, request.body, format=request.META.get('CONTENT_TYPE', 'application/json'))
1446        deserialized = self.alter_deserialized_list_data(request, deserialized)
1447
1448        if self._meta.collection_name not in deserialized:
1449            raise BadRequest("Invalid data sent: missing '%s'" % self._meta.collection_name)
1450
1451        basic_bundle = self.build_bundle(request=request)
1452        self.obj_delete_list_for_update(bundle=basic_bundle, **self.remove_api_resource_names(kwargs))
1453        bundles_seen = []
1454
1455        for object_data in deserialized[self._meta.collection_name]:
1456            bundle = self.build_bundle(data=dict_strip_unicode_keys(object_data), request=request)
1457
1458            # Attempt to be transactional, deleting any previously created
1459            # objects if validation fails.
1460            try:
1461                self.obj_create(bundle=bundle, **self.remove_api_resource_names(kwargs))
1462                bundles_seen.append(bundle)
1463            except ImmediateHttpResponse:
1464                self.rollback(bundles_seen)
1465                raise
1466
1467        if not self._meta.always_return_data:
1468            return http.HttpNoContent()
1469        else:
1470            to_be_serialized = {
1471                self._meta.collection_name: [
1472                    self.full_dehydrate(b, for_list=True)
1473                    for b in bundles_seen
1474                ]
1475            }
1476            to_be_serialized = self.alter_list_data_to_serialize(request, to_be_serialized)
1477            return self.create_response(request, to_be_serialized)
1478
1479    def put_detail(self, request, **kwargs):
1480        """
1481        Either updates an existing resource or creates a new one with the
1482        provided data.
1483
1484        Calls ``obj_update`` with the provided data first, but falls back to
1485        ``obj_create`` if the object does not already exist.
1486
1487        If a new resource is created, return ``HttpCreated`` (201 Created).
1488        If ``Meta.always_return_data = True``, there will be a populated body
1489        of serialized data.
1490
1491        If an existing resource is modified and
1492        ``Meta.always_return_data = False`` (default), return ``HttpNoContent``
1493        (204 No Content).
1494        If an existing resource is modified and
1495        ``Meta.always_return_data = True``, return ``HttpAccepted`` (200
1496        OK).
1497        """
1498        deserialized = self.deserialize(request, request.body, format=request.META.get('CONTENT_TYPE', 'application/json'))
1499        deserialized = self.alter_deserialized_detail_data(request, deserialized)
1500        bundle = self.build_bundle(data=dict_strip_unicode_keys(deserialized), request=request)
1501
1502        try:
1503            updated_bundle = self.obj_update(bundle=bundle, **self.remove_api_resource_names(kwargs))
1504
1505            if not self._meta.always_return_data:
1506                return http.HttpNoContent()
1507            else:
1508                # Invalidate prefetched_objects_cache for bundled object
1509                # because we might have changed a prefetched field
1510                updated_bundle.obj._prefetched_objects_cache = {}
1511                updated_bundle = self.full_dehydrate(updated_bundle)
1512                updated_bundle = self.alter_detail_data_to_serialize(request, updated_bundle)
1513                return self.create_response(request, updated_bundle)
1514        except (NotFound, MultipleObjectsReturned):
1515            updated_bundle = self.obj_create(bundle=bundle, **self.remove_api_resource_names(kwargs))
1516            location = self.get_resource_uri(updated_bundle)
1517
1518            if not self._meta.always_return_data:
1519                return http.HttpCreated(location=location)
1520            else:
1521                updated_bundle = self.full_dehydrate(updated_bundle)
1522                updated_bundle = self.alter_detail_data_to_serialize(request, updated_bundle)
1523                return self.create_response(request, updated_bundle, response_class=http.HttpCreated, location=location)
1524
1525    def delete_list(self, request, **kwargs):
1526        """
1527        Destroys a collection of resources/objects.
1528
1529        Calls ``obj_delete_list``.
1530
1531        If the resources are deleted, return ``HttpNoContent`` (204 No Content).
1532        """
1533        bundle = self.build_bundle(request=request)
1534        self.obj_delete_list(bundle=bundle, request=request, **self.remove_api_resource_names(kwargs))
1535        return http.HttpNoContent()
1536
1537    def delete_detail(self, request, **kwargs):
1538        """
1539        Destroys a single resource/object.
1540
1541        Calls ``obj_delete``.
1542
1543        If the resource is deleted, return ``HttpNoContent`` (204 No Content).
1544        If the resource did not exist, return ``Http404`` (404 Not Found).
1545        """
1546        # Manually construct the bundle here, since we don't want to try to
1547        # delete an empty instance.
1548        bundle = Bundle(request=request)
1549
1550        try:
1551            self.obj_delete(bundle=bundle, **self.remove_api_resource_names(kwargs))
1552            return http.HttpNoContent()
1553        except NotFound:
1554            return http.HttpNotFound()
1555
1556    def patch_list(self, request, **kwargs):
1557        """
1558        Updates a collection in-place.
1559
1560        The exact behavior of ``PATCH`` to a list resource is still the matter of
1561        some debate in REST circles, and the ``PATCH`` RFC isn't standard. So the
1562        behavior this method implements (described below) is something of a
1563        stab in the dark. It's mostly cribbed from GData, with a smattering
1564        of ActiveResource-isms and maybe even an original idea or two.
1565
1566        The ``PATCH`` format is one that's similar to the response returned from
1567        a ``GET`` on a list resource::
1568
1569            {
1570              "objects": [{object}, {object}, ...],
1571              "deleted_objects": ["URI", "URI", "URI", ...],
1572            }
1573
1574        For each object in ``objects``:
1575
1576            * If the dict does not have a ``resource_uri`` key then the item is
1577              considered "new" and is handled like a ``POST`` to the resource list.
1578
1579            * If the dict has a ``resource_uri`` key and the ``resource_uri`` refers
1580              to an existing resource then the item is a update; it's treated
1581              like a ``PATCH`` to the corresponding resource detail.
1582
1583            * If the dict has a ``resource_uri`` but the resource *doesn't* exist,
1584              then this is considered to be a create-via-``PUT``.
1585
1586        Each entry in ``deleted_objects`` referes to a resource URI of an existing
1587        resource to be deleted; each is handled like a ``DELETE`` to the relevent
1588        resource.
1589
1590        In any case:
1591
1592            * If there's a resource URI it *must* refer to a resource of this
1593              type. It's an error to include a URI of a different resource.
1594
1595            * ``PATCH`` is all or nothing. If a single sub-operation fails, the
1596              entire request will fail and all resources will be rolled back.
1597
1598          * For ``PATCH`` to work, you **must** have ``put`` in your
1599            :ref:`detail-allowed-methods` setting.
1600
1601          * To delete objects via ``deleted_objects`` in a ``PATCH`` request you
1602            **must** have ``delete`` in your :ref:`detail-allowed-methods`
1603            setting.
1604
1605        Substitute appropriate names for ``objects`` and
1606        ``deleted_objects`` if ``Meta.collection_name`` is set to something
1607        other than ``objects`` (default).
1608        """
1609        request = convert_post_to_patch(request)
1610        deserialized = self.deserialize(request, request.body, format=request.META.get('CONTENT_TYPE', 'application/json'))
1611
1612        collection_name = self._meta.collection_name
1613        deleted_collection_name = 'deleted_%s' % collection_name
1614        if collection_name not in deserialized:
1615            raise BadRequest("Invalid data sent: missing '%s'" % collection_name)
1616
1617        if len(deserialized[collection_name]) and 'put' not in self._meta.detail_allowed_methods:
1618            raise ImmediateHttpResponse(response=http.HttpMethodNotAllowed())
1619
1620        bundles_seen = []
1621
1622        for data in deserialized[collection_name]:
1623            # If there's a resource_uri then this is either an
1624            # update-in-place or a create-via-PUT.
1625            if "resource_uri" in data:
1626                uri = data.pop('resource_uri')
1627
1628                try:
1629                    obj = self.get_via_uri(uri, request=request)
1630
1631                    # The object does exist, so this is an update-in-place.
1632                    bundle = self.build_bundle(obj=obj, request=request)
1633                    bundle = self.full_dehydrate(bundle, for_list=True)
1634                    bundle = self.alter_detail_data_to_serialize(request, bundle)
1635                    self.update_in_place(request, bundle, data)
1636                except (ObjectDoesNotExist, MultipleObjectsReturned):
1637                    # The object referenced by resource_uri doesn't exist,
1638                    # so this is a create-by-PUT equivalent.
1639                    data = self.alter_deserialized_detail_data(request, data)
1640                    bundle = self.build_bundle(data=dict_strip_unicode_keys(data), request=request)
1641                    self.obj_create(bundle=bundle)
1642            else:
1643                # There's no resource URI, so this is a create call just
1644                # like a POST to the list resource.
1645                data = self.alter_deserialized_detail_data(request, data)
1646                bundle = self.build_bundle(data=dict_strip_unicode_keys(data), request=request)
1647                self.obj_create(bundle=bundle)
1648
1649            bundles_seen.append(bundle)
1650
1651        deleted_collection = deserialized.get(deleted_collection_name, [])
1652
1653        if deleted_collection:
1654            if 'delete' not in self._meta.detail_allowed_methods:
1655                raise ImmediateHttpResponse(response=http.HttpMethodNotAllowed())
1656
1657            for uri in deleted_collection:
1658                obj = self.get_via_uri(uri, request=request)
1659                bundle = self.build_bundle(obj=obj, request=request)
1660                self.obj_delete(bundle=bundle)
1661
1662        if not self._meta.always_return_data:
1663            return http.HttpAccepted()
1664        else:
1665            to_be_serialized = {
1666                'objects': [
1667                    self.full_dehydrate(b, for_list=True)
1668                    for b in bundles_seen
1669                ]
1670            }
1671            to_be_serialized = self.alter_list_data_to_serialize(request, to_be_serialized)
1672            return self.create_response(request, to_be_serialized, response_class=http.HttpAccepted)
1673
1674    def patch_detail(self, request, **kwargs):
1675        """
1676        Updates a resource in-place.
1677
1678        Calls ``obj_update``.
1679
1680        If the resource is updated, return ``HttpAccepted`` (202 Accepted).
1681        If the resource did not exist, return ``HttpNotFound`` (404 Not Found).
1682        """
1683        request = convert_post_to_patch(request)
1684        basic_bundle = self.build_bundle(request=request)
1685
1686        # We want to be able to validate the update, but we can't just pass
1687        # the partial data into the validator since all data needs to be
1688        # present. Instead, we basically simulate a PUT by pulling out the
1689        # original data and updating it in-place.
1690        # So first pull out the original object. This is essentially
1691        # ``get_detail``.
1692        try:
1693            obj = self.cached_obj_get(bundle=basic_bundle, **self.remove_api_resource_names(kwargs))
1694        except ObjectDoesNotExist:
1695            return http.HttpNotFound()
1696        except MultipleObjectsReturned:
1697            return http.HttpMultipleChoices("More than one resource is found at this URI.")
1698
1699        bundle = self.build_bundle(obj=obj, request=request)
1700        bundle = self.full_dehydrate(bundle)
1701        bundle = self.alter_detail_data_to_serialize(request, bundle)
1702
1703        # Now update the bundle in-place.
1704        deserialized = self.deserialize(request, request.body, format=request.META.get('CONTENT_TYPE', 'application/json'))
1705        self.update_in_place(request, bundle, deserialized)
1706
1707        if not self._meta.always_return_data:
1708            return http.HttpAccepted()
1709        else:
1710            # Invalidate prefetched_objects_cache for bundled object
1711            # because we might have changed a prefetched field
1712            bundle.obj._prefetched_objects_cache = {}
1713            bundle = self.full_dehydrate(bundle)
1714            bundle = self.alter_detail_data_to_serialize(request, bundle)
1715            return self.create_response(request, bundle, response_class=http.HttpAccepted)
1716
1717    def update_in_place(self, request, original_bundle, new_data):
1718        """
1719        Update the object in original_bundle in-place using new_data.
1720        """
1721        original_bundle.data.update(**dict_strip_unicode_keys(new_data))
1722
1723        # Now we've got a bundle with the new data sitting in it and we're
1724        # we're basically in the same spot as a PUT request. SO the rest of this
1725        # function is cribbed from put_detail.
1726        self.alter_deserialized_detail_data(request, original_bundle.data)
1727        kwargs = {
1728            self._meta.detail_uri_name: self.get_bundle_detail_data(original_bundle),
1729            'request': request,
1730        }
1731        return self.obj_update(bundle=original_bundle, **kwargs)
1732
1733    def get_schema(self, request, **kwargs):
1734        """
1735        Returns a serialized form of the schema of the resource.
1736
1737        Calls ``build_schema`` to generate the data. This method only responds
1738        to HTTP GET.
1739
1740        Should return a HttpResponse (200 OK).
1741        """
1742        self.method_check(request, allowed=['get'])
1743        self.is_authenticated(request)
1744        self.throttle_check(request)
1745        self.log_throttled_access(request)
1746        bundle = self.build_bundle(request=request)
1747        self.authorized_read_detail(self.get_object_list(bundle.request), bundle)
1748        return self.create_response(request, self.build_schema())
1749
1750    def get_multiple(self, request, **kwargs):
1751        """
1752        Returns a serialized list of resources based on the identifiers
1753        from the URL.
1754
1755        Calls ``obj_get_list`` to fetch only the objects requests in
1756        a single query. This method only responds to HTTP GET.
1757
1758        For backward compatibility the method ``obj_get`` is used if
1759        ``obj_get_list`` is not implemented.
1760
1761        Should return a HttpResponse (200 OK).
1762        """
1763        self.method_check(request, allowed=['get'])
1764        self.is_authenticated(request)
1765        self.throttle_check(request)
1766
1767        # Rip apart the list then iterate.
1768        kwarg_name = '%s_list' % self._meta.detail_uri_name
1769        obj_identifiers = kwargs.get(kwarg_name, '').split(';')
1770        objects = []
1771        not_found = []
1772        base_bundle = self.build_bundle(request=request)
1773
1774        # We will try to get a queryset from obj_get_list.
1775        queryset = None
1776
1777        try:
1778            queryset = self.obj_get_list(bundle=base_bundle).filter(
1779                **{self._meta.detail_uri_name + '__in': obj_identifiers})
1780        except NotImplementedError:
1781            pass
1782
1783        if queryset is not None:
1784            # Fetch the objects from the queryset to a dictionary.
1785            objects_dict = {}
1786            for obj in queryset:
1787                objects_dict[str(getattr(obj, self._meta.detail_uri_name))] = obj
1788
1789            # Walk the list of identifiers in order and get the objects or feed the not_found list.
1790            for identifier in obj_identifiers:
1791                if identifier in objects_dict:
1792                    bundle = self.build_bundle(obj=objects_dict[identifier], request=request)
1793                    bundle = self.full_dehydrate(bundle, for_list=True)
1794                    objects.append(bundle)
1795                else:
1796                    not_found.append(identifier)
1797        else:
1798            # Use the old way.
1799            for identifier in obj_identifiers:
1800                try:
1801                    obj = self.obj_get(bundle=base_bundle, **{self._meta.detail_uri_name: identifier})
1802                    bundle = self.build_bundle(obj=obj, request=request)
1803                    bundle = self.full_dehydrate(bundle, for_list=True)
1804                    objects.append(bundle)
1805                except (ObjectDoesNotExist, Unauthorized):
1806                    not_found.append(identifier)
1807
1808        object_list = {
1809            self._meta.collection_name: objects,
1810        }
1811
1812        if len(not_found):
1813            object_list['not_found'] = not_found
1814
1815        self.log_throttled_access(request)
1816        return self.create_response(request, object_list)
1817
1818
1819class ModelDeclarativeMetaclass(DeclarativeMetaclass):
1820    def __new__(cls, name, bases, attrs):
1821        meta = attrs.get('Meta')
1822        if getattr(meta, 'abstract', False):
1823            # abstract base classes do nothing on declaration
1824            new_class = super(ModelDeclarativeMetaclass, cls).__new__(cls, name, bases, attrs)
1825            return new_class
1826
1827        # Sanity check: ModelResource needs either a queryset or object_class:
1828        if meta and not hasattr(meta, 'queryset') and not hasattr(meta, 'object_class'):
1829            msg = "ModelResource (%s) requires Meta.object_class or Meta.queryset"
1830            raise ImproperlyConfigured(msg % name)
1831
1832        if hasattr(meta, 'queryset') and not hasattr(meta, 'object_class'):
1833            setattr(meta, 'object_class', meta.queryset.model)
1834
1835        new_class = super(ModelDeclarativeMetaclass, cls).__new__(cls, name, bases, attrs)
1836        specified_fields = getattr(new_class._meta, 'fields', None)
1837        excludes = getattr(new_class._meta, 'excludes', [])
1838        field_names = list(new_class.base_fields.keys())
1839
1840        include_fields = specified_fields
1841
1842        if include_fields is None:
1843            if meta and meta.object_class:
1844                include_fields = [f.name for f in meta.object_class._meta.fields]
1845            else:
1846                include_fields = []
1847
1848        for field_name in field_names:
1849            if field_name == 'resource_uri':
1850                continue
1851            if field_name in new_class.declared_fields:
1852                continue
1853            if specified_fields is not None and field_name not in include_fields:
1854                del(new_class.base_fields[field_name])
1855            if field_name in excludes:
1856                del(new_class.base_fields[field_name])
1857
1858        # Add in the new fields.
1859        new_class.base_fields.update(new_class.get_fields(include_fields, excludes))
1860
1861        if getattr(new_class._meta, 'include_absolute_url', True):
1862            if 'absolute_url' not in new_class.base_fields:
1863                new_class.base_fields['absolute_url'] = fields.CharField(attribute='get_absolute_url', readonly=True)
1864        elif 'absolute_url' in new_class.base_fields and 'absolute_url' not in attrs:
1865            del(new_class.base_fields['absolute_url'])
1866
1867        return new_class
1868
1869
1870class BaseModelResource(Resource):
1871    """
1872    A subclass of ``Resource`` designed to work with Django's ``Models``.
1873
1874    This class will introspect a given ``Model`` and build a field list based
1875    on the fields found on the model (excluding relational fields).
1876
1877    Given that it is aware of Django's ORM, it also handles the CRUD data
1878    operations of the resource.
1879    """
1880    @classmethod
1881    def should_skip_field(cls, field):
1882        """
1883        Given a Django model field, return if it should be included in the
1884        contributed ApiFields.
1885        """
1886        if isinstance(field, ForeignKey):
1887            return True
1888        # Ignore certain fields (related fields).
1889        if hasattr(field, 'remote_field'):
1890            if field.remote_field:
1891                return True
1892        elif getattr(field, 'rel'):
1893            return True
1894
1895        return False
1896
1897    @classmethod
1898    def api_field_from_django_field(cls, f, default=fields.CharField):
1899        """
1900        Returns the field type that would likely be associated with each
1901        Django type.
1902        """
1903        result = default
1904        internal_type = f.get_internal_type()
1905
1906        if internal_type == 'DateField':
1907            result = fields.DateField
1908        elif internal_type == 'DateTimeField':
1909            result = fields.DateTimeField
1910        elif internal_type in ('BooleanField', 'NullBooleanField'):
1911            result = fields.BooleanField
1912        elif internal_type in ('FloatField',):
1913            result = fields.FloatField
1914        elif internal_type in ('DecimalField',):
1915            result = fields.DecimalField
1916        elif internal_type in ('IntegerField', 'PositiveIntegerField', 'PositiveSmallIntegerField', 'SmallIntegerField', 'AutoField', 'BigIntegerField', 'BigAutoField'):
1917            result = fields.IntegerField
1918        elif internal_type in ('FileField', 'ImageField'):
1919            result = fields.FileField
1920        elif internal_type == 'TimeField':
1921            result = fields.TimeField
1922        # TODO: Perhaps enable these via introspection. The reason they're not enabled
1923        #       by default is the very different ``__init__`` they have over
1924        #       the other fields.
1925        # elif internal_type == 'ForeignKey':
1926        #     result = ForeignKey
1927        # elif internal_type == 'ManyToManyField':
1928        #     result = ManyToManyField
1929
1930        return result
1931
1932    @classmethod
1933    def get_fields(cls, fields=None, excludes=None):
1934        """
1935        Given any explicit fields to include and fields to exclude, add
1936        additional fields based on the associated model.
1937        """
1938        final_fields = {}
1939        fields = fields or []
1940        excludes = excludes or []
1941
1942        if not cls._meta.object_class:
1943            return final_fields
1944
1945        for f in cls._meta.object_class._meta.fields:
1946            # If the field name is already present, skip
1947            if f.name in cls.base_fields:
1948                continue
1949
1950            # If field is not present in explicit field listing, skip
1951            if f.name not in fields:
1952                continue
1953
1954            # If field is in exclude list, skip
1955            if f.name in excludes:
1956                continue
1957
1958            if cls.should_skip_field(f):
1959                continue
1960
1961            api_field_class = cls.api_field_from_django_field(f)
1962
1963            kwargs = {
1964                'attribute': f.name,
1965                'help_text': f.help_text,
1966                'verbose_name': f.verbose_name,
1967            }
1968
1969            if f.null is True:
1970                kwargs['null'] = True
1971
1972            kwargs['unique'] = f.unique
1973
1974            if not f.null and f.blank is True:
1975                kwargs['default'] = ''
1976                kwargs['blank'] = True
1977
1978            if f.get_internal_type() == 'TextField':
1979                kwargs['default'] = ''
1980
1981            if f.has_default():
1982                kwargs['default'] = f.default
1983
1984            if getattr(f, 'auto_now', False):
1985                kwargs['default'] = f.auto_now
1986
1987            if getattr(f, 'auto_now_add', False):
1988                kwargs['default'] = f.auto_now_add
1989
1990            final_fields[f.name] = api_field_class(**kwargs)
1991            final_fields[f.name].instance_name = f.name
1992
1993        return final_fields
1994
1995    def check_filtering(self, field_name, filter_type='exact', filter_bits=None):
1996        """
1997        Given a field name, a optional filter type and an optional list of
1998        additional relations, determine if a field can be filtered on.
1999
2000        If a filter does not meet the needed conditions, it should raise an
2001        ``InvalidFilterError``.
2002
2003        If the filter meets the conditions, a list of attribute names (not
2004        field names) will be returned.
2005        """
2006        if filter_bits is None:
2007            filter_bits = []
2008
2009        if field_name not in self._meta.filtering:
2010            raise InvalidFilterError("The '%s' field does not allow filtering." % field_name)
2011
2012        # Check to see if it's an allowed lookup type.
2013        if self._meta.filtering[field_name] not in (ALL, ALL_WITH_RELATIONS):
2014            # Must be an explicit whitelist.
2015            if filter_type not in self._meta.filtering[field_name]:
2016                raise InvalidFilterError("'%s' is not an allowed filter on the '%s' field." % (filter_type, field_name))
2017
2018        if self.fields[field_name].attribute is None:
2019            raise InvalidFilterError("The '%s' field has no 'attribute' for searching with." % field_name)
2020
2021        # Check to see if it's a relational lookup and if that's allowed.
2022        if len(filter_bits):
2023            if not getattr(self.fields[field_name], 'is_related', False):
2024                raise InvalidFilterError("The '%s' field does not support relations." % field_name)
2025
2026            if not self._meta.filtering[field_name] == ALL_WITH_RELATIONS:
2027                raise InvalidFilterError("Lookups are not allowed more than one level deep on the '%s' field." % field_name)
2028
2029            # Recursively descend through the remaining lookups in the filter,
2030            # if any. We should ensure that all along the way, we're allowed
2031            # to filter on that field by the related resource.
2032            related_resource = self.fields[field_name].get_related_resource(None)
2033            return [self.fields[field_name].attribute] + related_resource.check_filtering(filter_bits[0], filter_type, filter_bits[1:])
2034
2035        return [self.fields[field_name].attribute]
2036
2037    def filter_value_to_python(self, value, field_name, filters, filter_expr,
2038            filter_type):
2039        """
2040        Turn the string ``value`` into a python object.
2041        """
2042        # Simple values
2043        value = string_to_python(value)
2044
2045        # Split on ',' if not empty string and either an in or range filter.
2046        if filter_type in ('in', 'range') and len(value):
2047            if hasattr(filters, 'getlist'):
2048                value = []
2049
2050                for part in filters.getlist(filter_expr):
2051                    value.extend(part.split(','))
2052            else:
2053                value = value.split(',')
2054
2055        return value
2056
2057    def build_filters(self, filters=None, ignore_bad_filters=False):
2058        """
2059        Given a dictionary of filters, create the necessary ORM-level filters.
2060
2061        Keys should be resource fields, **NOT** model fields.
2062
2063        Valid values are either a list of Django filter types (i.e.
2064        ``['startswith', 'exact', 'lte']``), the ``ALL`` constant or the
2065        ``ALL_WITH_RELATIONS`` constant.
2066        """
2067        # At the declarative level:
2068        #     filtering = {
2069        #         'resource_field_name': ['exact', 'startswith', 'endswith', 'contains'],
2070        #         'resource_field_name_2': ['exact', 'gt', 'gte', 'lt', 'lte', 'range'],
2071        #         'resource_field_name_3': ALL,
2072        #         'resource_field_name_4': ALL_WITH_RELATIONS,
2073        #         ...
2074        #     }
2075        # Accepts the filters as a dict. None by default, meaning no filters.
2076        if filters is None:
2077            filters = {}
2078
2079        qs_filters = {}
2080
2081        for filter_expr, value in filters.items():
2082            filter_bits = filter_expr.split(LOOKUP_SEP)
2083            field_name = filter_bits.pop(0)
2084            filter_type = 'exact'
2085
2086            if field_name not in self.fields:
2087                # It's not a field we know about. Move along citizen.
2088                continue
2089
2090            # Validate filter types other than 'exact' that are supported by the field type
2091            try:
2092                django_field_name = self.fields[field_name].attribute
2093                django_field = self._meta.object_class._meta.get_field(django_field_name)
2094                if hasattr(django_field, 'field'):
2095                    django_field = django_field.field  # related field
2096            except FieldDoesNotExist:
2097                raise InvalidFilterError("The '%s' field is not a valid field name" % field_name)
2098
2099            query_terms = django_field.get_lookups().keys()
2100            if len(filter_bits) and filter_bits[-1] in query_terms:
2101                filter_type = filter_bits.pop()
2102
2103            try:
2104                lookup_bits = self.check_filtering(field_name, filter_type, filter_bits)
2105            except InvalidFilterError:
2106                if ignore_bad_filters:
2107                    continue
2108                else:
2109                    raise
2110            value = self.filter_value_to_python(value, field_name, filters, filter_expr, filter_type)
2111
2112            db_field_name = LOOKUP_SEP.join(lookup_bits)
2113            qs_filter = "%s%s%s" % (db_field_name, LOOKUP_SEP, filter_type)
2114            qs_filters[qs_filter] = value
2115
2116        return dict_strip_unicode_keys(qs_filters)
2117
2118    def apply_sorting(self, obj_list, options=None):
2119        """
2120        Given a dictionary of options, apply some ORM-level sorting to the
2121        provided ``QuerySet``.
2122
2123        Looks for the ``order_by`` key and handles either ascending (just the
2124        field name) or descending (the field name with a ``-`` in front).
2125
2126        The field name should be the resource field, **NOT** model field.
2127        """
2128        if options is None:
2129            options = {}
2130
2131        parameter_name = 'order_by'
2132
2133        if 'order_by' not in options:
2134            if 'sort_by' not in options:
2135                # Nothing to alter the order. Return what we've got.
2136                return obj_list
2137            else:
2138                warnings.warn("'sort_by' is a deprecated parameter. Please use 'order_by' instead.")
2139                parameter_name = 'sort_by'
2140
2141        order_by_args = []
2142
2143        if hasattr(options, 'getlist'):
2144            order_bits = options.getlist(parameter_name)
2145        else:
2146            order_bits = options.get(parameter_name)
2147
2148            if not isinstance(order_bits, (list, tuple)):
2149                order_bits = [order_bits]
2150
2151        for order_by in order_bits:
2152            order_by_bits = order_by.split(LOOKUP_SEP)
2153
2154            field_name = order_by_bits[0]
2155            order = ''
2156
2157            if order_by_bits[0].startswith('-'):
2158                field_name = order_by_bits[0][1:]
2159                order = '-'
2160
2161            if field_name not in self.fields:
2162                # It's not a field we know about. Move along citizen.
2163                raise InvalidSortError("No matching '%s' field for ordering on." % field_name)
2164
2165            if field_name not in self._meta.ordering:
2166                raise InvalidSortError("The '%s' field does not allow ordering." % field_name)
2167
2168            if self.fields[field_name].attribute is None:
2169                raise InvalidSortError("The '%s' field has no 'attribute' for ordering with." % field_name)
2170
2171            order_by_args.append("%s%s" % (order, LOOKUP_SEP.join([self.fields[field_name].attribute] + order_by_bits[1:])))
2172
2173        return obj_list.order_by(*order_by_args)
2174
2175    def apply_filters(self, request, applicable_filters):
2176        """
2177        An ORM-specific implementation of ``apply_filters``.
2178
2179        The default simply applies the ``applicable_filters`` as ``**kwargs``,
2180        but should make it possible to do more advanced things.
2181        """
2182        return self.get_object_list(request).filter(**applicable_filters)
2183
2184    def get_object_list(self, request):
2185        """
2186        An ORM-specific implementation of ``get_object_list``.
2187
2188        Returns a queryset that may have been limited by other overrides.
2189        """
2190        return self._meta.queryset._clone()
2191
2192    def obj_get_list(self, bundle, **kwargs):
2193        """
2194        A ORM-specific implementation of ``obj_get_list``.
2195
2196        ``GET`` dictionary of bundle.request can be used to narrow the query.
2197        """
2198        filters = {}
2199
2200        if hasattr(bundle.request, 'GET'):
2201            # Grab a mutable copy.
2202            filters = bundle.request.GET.copy()
2203
2204        # Update with the provided kwargs.
2205        filters.update(kwargs)
2206        applicable_filters = self.build_filters(filters=filters)
2207
2208        try:
2209            objects = self.apply_filters(bundle.request, applicable_filters)
2210            return self.authorized_read_list(objects, bundle)
2211        except ValueError:
2212            raise BadRequest("Invalid resource lookup data provided (mismatched type).")
2213
2214    def obj_get(self, bundle, **kwargs):
2215        """
2216        A ORM-specific implementation of ``obj_get``.
2217
2218        Takes optional ``kwargs``, which are used to narrow the query to find
2219        the instance.
2220        """
2221        # Use ignore_bad_filters=True. `obj_get_list` filters based on
2222        # request.GET, but `obj_get` usually filters based on `detail_uri_name`
2223        # or data from a related field, so we don't want to raise errors if
2224        # something doesn't explicitly match a configured filter.
2225        applicable_filters = self.build_filters(filters=kwargs, ignore_bad_filters=True)
2226        if self._meta.detail_uri_name in kwargs:
2227            applicable_filters[self._meta.detail_uri_name] = kwargs[self._meta.detail_uri_name]
2228
2229        try:
2230            object_list = self.apply_filters(bundle.request, applicable_filters)
2231            stringified_kwargs = ', '.join(["%s=%s" % (k, v) for k, v in applicable_filters.items()])
2232
2233            if len(object_list) <= 0:
2234                raise self._meta.object_class.DoesNotExist("Couldn't find an instance of '%s' which matched '%s'." % (self._meta.object_class.__name__, stringified_kwargs))
2235            elif len(object_list) > 1:
2236                raise MultipleObjectsReturned("More than one '%s' matched '%s'." % (self._meta.object_class.__name__, stringified_kwargs))
2237
2238            bundle.obj = object_list[0]
2239            self.authorized_read_detail(object_list, bundle)
2240            return bundle.obj
2241        except ValueError:
2242            raise NotFound("Invalid resource lookup data provided (mismatched type).")
2243
2244    def obj_create(self, bundle, **kwargs):
2245        """
2246        A ORM-specific implementation of ``obj_create``.
2247        """
2248        bundle.obj = self._meta.object_class()
2249
2250        for key, value in kwargs.items():
2251            setattr(bundle.obj, key, value)
2252
2253        bundle = self.full_hydrate(bundle)
2254        return self.save(bundle)
2255
2256    def lookup_kwargs_with_identifiers(self, bundle, kwargs):
2257        """
2258        Kwargs here represent uri identifiers Ex: /repos/<user_id>/<repo_name>/
2259        We need to turn those identifiers into Python objects for generating
2260        lookup parameters that can find them in the DB
2261        """
2262        lookup_kwargs = {}
2263
2264        # Handle detail_uri_name specially
2265        if self._meta.detail_uri_name in kwargs:
2266            lookup_kwargs[self._meta.detail_uri_name] = kwargs.pop(self._meta.detail_uri_name)
2267
2268        bundle.obj = self.get_object_list(bundle.request).model()
2269        # Override data values, we rely on uri identifiers
2270        bundle.data.update(kwargs)
2271        # We're going to manually hydrate, as opposed to calling
2272        # ``full_hydrate``, to ensure we don't try to flesh out related
2273        # resources & keep things speedy.
2274        bundle = self.hydrate(bundle)
2275
2276        for identifier in kwargs:
2277            field_object = self.fields[identifier]
2278
2279            # Skip readonly or related fields.
2280            if field_object.readonly or field_object.is_related or\
2281                    not field_object.attribute:
2282                continue
2283
2284            # Check for an optional method to do further hydration.
2285            method = getattr(self, "hydrate_%s" % identifier, None)
2286
2287            if method:
2288                bundle = method(bundle)
2289
2290            lookup_kwargs[identifier] = field_object.hydrate(bundle)
2291
2292        return lookup_kwargs
2293
2294    def obj_update(self, bundle, skip_errors=False, **kwargs):
2295        """
2296        A ORM-specific implementation of ``obj_update``.
2297        """
2298        bundle_detail_data = self.get_bundle_detail_data(bundle)
2299        arg_detail_data = kwargs.get(self._meta.detail_uri_name)
2300
2301        if bundle_detail_data is None or (arg_detail_data is not None and str(bundle_detail_data) != str(arg_detail_data)):
2302            try:
2303                lookup_kwargs = self.lookup_kwargs_with_identifiers(bundle, kwargs)
2304            except:  # noqa
2305                # if there is trouble hydrating the data, fall back to just
2306                # using kwargs by itself (usually it only contains a "pk" key
2307                # and this will work fine.
2308                lookup_kwargs = kwargs
2309
2310            try:
2311                bundle.obj = self.obj_get(bundle=bundle, **lookup_kwargs)
2312            except ObjectDoesNotExist:
2313                raise NotFound("A model instance matching the provided arguments could not be found.")
2314
2315        bundle = self.full_hydrate(bundle)
2316        return self.save(bundle, skip_errors=skip_errors)
2317
2318    def obj_delete_list(self, bundle, **kwargs):
2319        """
2320        A ORM-specific implementation of ``obj_delete_list``.
2321        """
2322        objects_to_delete = self.obj_get_list(bundle=bundle, **kwargs)
2323        deletable_objects = self.authorized_delete_list(objects_to_delete, bundle)
2324
2325        if hasattr(deletable_objects, 'delete'):
2326            # It's likely a ``QuerySet``. Call ``.delete()`` for efficiency.
2327            deletable_objects.delete()
2328        else:
2329            for authed_obj in deletable_objects:
2330                authed_obj.delete()
2331
2332    def obj_delete_list_for_update(self, bundle, **kwargs):
2333        """
2334        A ORM-specific implementation of ``obj_delete_list_for_update``.
2335        """
2336        objects_to_delete = self.obj_get_list(bundle=bundle, **kwargs)
2337        deletable_objects = self.authorized_update_list(objects_to_delete, bundle)
2338
2339        if hasattr(deletable_objects, 'delete'):
2340            # It's likely a ``QuerySet``. Call ``.delete()`` for efficiency.
2341            deletable_objects.delete()
2342        else:
2343            for authed_obj in deletable_objects:
2344                authed_obj.delete()
2345
2346    def obj_delete(self, bundle, **kwargs):
2347        """
2348        A ORM-specific implementation of ``obj_delete``.
2349
2350        Takes optional ``kwargs``, which are used to narrow the query to find
2351        the instance.
2352        """
2353        if not hasattr(bundle.obj, 'delete'):
2354            try:
2355                bundle.obj = self.obj_get(bundle=bundle, **kwargs)
2356            except ObjectDoesNotExist:
2357                raise NotFound("A model instance matching the provided arguments could not be found.")
2358
2359        self.authorized_delete_detail(self.get_object_list(bundle.request), bundle)
2360        bundle.obj.delete()
2361
2362    @atomic_decorator()
2363    def patch_list(self, request, **kwargs):
2364        """
2365        An ORM-specific implementation of ``patch_list``.
2366
2367        Necessary because PATCH should be atomic (all-success or all-fail)
2368        and the only way to do this neatly is at the database level.
2369        """
2370        return super(BaseModelResource, self).patch_list(request, **kwargs)
2371
2372    def rollback(self, bundles):
2373        """
2374        A ORM-specific implementation of ``rollback``.
2375
2376        Given the list of bundles, delete all models pertaining to those
2377        bundles.
2378        """
2379        for bundle in bundles:
2380            if bundle.obj and self.get_bundle_detail_data(bundle):
2381                bundle.obj.delete()
2382
2383    def create_identifier(self, obj):
2384        return u"%s.%s.%s" % (obj._meta.app_label, get_module_name(obj._meta), obj.pk)
2385
2386    def save(self, bundle, skip_errors=False):
2387        if bundle.via_uri:
2388            return bundle
2389
2390        self.is_valid(bundle)
2391
2392        if bundle.errors and not skip_errors:
2393            raise ImmediateHttpResponse(response=self.error_response(bundle.request, bundle.errors))
2394
2395        # Check if they're authorized.
2396        if bundle.obj.pk:
2397            self.authorized_update_detail(self.get_object_list(bundle.request), bundle)
2398        else:
2399            self.authorized_create_detail(self.get_object_list(bundle.request), bundle)
2400
2401        # Save FKs just in case.
2402        self.save_related(bundle)
2403
2404        # Save the main object.
2405        obj_id = self.create_identifier(bundle.obj)
2406
2407        if obj_id not in bundle.objects_saved or bundle.obj._state.adding:
2408            bundle.obj.save()
2409            bundle.objects_saved.add(obj_id)
2410
2411        # Now pick up the M2M bits.
2412        m2m_bundle = self.hydrate_m2m(bundle)
2413        self.save_m2m(m2m_bundle)
2414        return bundle
2415
2416    def save_related(self, bundle):
2417        """
2418        Handles the saving of related non-M2M data.
2419
2420        Calling assigning ``child.parent = parent`` & then calling
2421        ``Child.save`` isn't good enough to make sure the ``parent``
2422        is saved.
2423
2424        To get around this, we go through all our related fields &
2425        call ``save`` on them if they have related, non-M2M data.
2426        M2M data is handled by the ``ModelResource.save_m2m`` method.
2427        """
2428        for field_name, field_object in self.fields.items():
2429            if not field_object.is_related:
2430                continue
2431
2432            if field_object.is_m2m:
2433                continue
2434
2435            if not field_object.attribute:
2436                continue
2437
2438            if field_object.readonly:
2439                continue
2440
2441            if field_object.blank and field_name not in bundle.data:
2442                continue
2443
2444            # Get the object.
2445            try:
2446                related_obj = getattr(bundle.obj, field_object.attribute)
2447            except ObjectDoesNotExist:
2448                # Django 1.8: unset related objects default to None, no error
2449                related_obj = None
2450
2451            # We didn't get it, so maybe we created it but haven't saved it
2452            if related_obj is None:
2453                related_obj = bundle.related_objects_to_save.get(field_object.attribute, None)
2454
2455            if related_obj and field_object.related_name:
2456                # this might be a reverse relation, so we need to save this
2457                # model, attach it to the related object, and save the related
2458                # object.
2459                if not self.get_bundle_detail_data(bundle):
2460                    bundle.obj.save()
2461
2462                setattr(related_obj, field_object.related_name, bundle.obj)
2463
2464            related_resource = field_object.get_related_resource(related_obj)
2465
2466            # Before we build the bundle & try saving it, let's make sure we
2467            # haven't already saved it.
2468            if related_obj:
2469                obj_id = self.create_identifier(related_obj)
2470
2471                if obj_id in bundle.objects_saved:
2472                    # It's already been saved. We're done here.
2473                    continue
2474
2475            if bundle.data.get(field_name):
2476                if hasattr(bundle.data[field_name], 'keys'):
2477                    # Only build & save if there's data, not just a URI.
2478                    related_bundle = related_resource.build_bundle(
2479                        obj=related_obj,
2480                        data=bundle.data.get(field_name),
2481                        request=bundle.request,
2482                        objects_saved=bundle.objects_saved
2483                    )
2484                    related_resource.full_hydrate(related_bundle)
2485                    related_resource.save(related_bundle)
2486                    related_obj = related_bundle.obj
2487                elif field_object.related_name:
2488                    # This condition probably means a URI for a reverse
2489                    # relation was provided.
2490                    related_bundle = related_resource.build_bundle(
2491                        obj=related_obj,
2492                        request=bundle.request,
2493                        objects_saved=bundle.objects_saved
2494                    )
2495                    related_resource.save(related_bundle)
2496                    related_obj = related_bundle.obj
2497
2498            if related_obj:
2499                setattr(bundle.obj, field_object.attribute, related_obj)
2500
2501    def save_m2m(self, bundle):
2502        """
2503        Handles the saving of related M2M data.
2504
2505        Due to the way Django works, the M2M data must be handled after the
2506        main instance, which is why this isn't a part of the main ``save`` bits.
2507
2508        Currently slightly inefficient in that it will clear out the whole
2509        relation and recreate the related data as needed.
2510        """
2511        for field_name, field_object in self.fields.items():
2512            if not field_object.is_m2m:
2513                continue
2514
2515            if not field_object.attribute:
2516                continue
2517
2518            if field_object.readonly:
2519                continue
2520
2521            # Get the manager.
2522            related_mngr = None
2523
2524            if isinstance(field_object.attribute, six.string_types):
2525                related_mngr = getattr(bundle.obj, field_object.attribute)
2526            elif callable(field_object.attribute):
2527                related_mngr = field_object.attribute(bundle)
2528
2529            if not related_mngr:
2530                continue
2531
2532            if hasattr(related_mngr, 'clear'):
2533                # FIXME: Dupe the original bundle, copy in the new object &
2534                #        check the perms on that (using the related resource)?
2535
2536                # Clear it out, just to be safe.
2537                related_mngr.clear()
2538
2539            related_objs = []
2540
2541            for related_bundle in bundle.data[field_name]:
2542                related_resource = field_object.get_related_resource(bundle.obj)
2543
2544                # Only build & save if there's data, not just a URI.
2545                updated_related_bundle = related_resource.build_bundle(
2546                    obj=related_bundle.obj,
2547                    data=related_bundle.data,
2548                    request=bundle.request,
2549                    objects_saved=bundle.objects_saved,
2550                    via_uri=related_bundle.via_uri,
2551                )
2552
2553                related_resource.save(updated_related_bundle)
2554                related_objs.append(updated_related_bundle.obj)
2555
2556            related_mngr.add(*related_objs)
2557
2558
2559class ModelResource(six.with_metaclass(ModelDeclarativeMetaclass, BaseModelResource)):
2560    pass
2561
2562
2563class NamespacedModelResource(ModelResource):
2564    """
2565    A ModelResource subclass that respects Django namespaces.
2566    """
2567    def _build_reverse_url(self, name, args=None, kwargs=None):
2568        namespaced = "%s:%s" % (self._meta.urlconf_namespace, name)
2569        return reverse(namespaced, args=args, kwargs=kwargs)
2570
2571
2572# Based off of ``piston.utils.coerce_put_post``. Similarly BSD-licensed.
2573# And no, the irony is not lost on me.
2574def convert_post_to_VERB(request, verb):
2575    """
2576    Force Django to process the VERB.
2577    """
2578    if request.method == verb:
2579        if not hasattr(request, '_read_started'):
2580            request._read_started = False
2581
2582        if hasattr(request, '_post'):
2583            del request._post
2584            del request._files
2585
2586        try:
2587            request.method = "POST"
2588            request._load_post_and_files()
2589            request.method = verb
2590        except AttributeError:
2591            request.META['REQUEST_METHOD'] = 'POST'
2592            request._load_post_and_files()
2593            request.META['REQUEST_METHOD'] = verb
2594        setattr(request, verb, request.POST)
2595
2596    return request
2597
2598
2599def convert_post_to_put(request):
2600    return convert_post_to_VERB(request, verb='PUT')
2601
2602
2603def convert_post_to_patch(request):
2604    return convert_post_to_VERB(request, verb='PATCH')
2605