1212import textwrap
1313import pytz
1414import json
15+ import logging
16+ import warnings
1517
1618from django .core .exceptions import ImproperlyConfigured , ObjectDoesNotExist
1719from django .conf import settings
18- from django .http import HttpResponse
20+ from django .http import HttpResponse , Http404
1921from django .utils .timezone import is_naive
2022
21- from tastypie .exceptions import (UnsupportedFormat ,
22- ImmediateHttpResponse , NotFound )
23- from tastypie .http import HttpNotImplemented , HttpApplicationError
23+ # Django 1.5 has moved this constant up one level.
24+ try :
25+ from django .db .models .constants import LOOKUP_SEP
26+ except ImportError :
27+ from django .db .models .sql .constants import LOOKUP_SEP
28+
29+ import tastypie
30+ from tastypie .exceptions import (UnsupportedFormat , NotFound , BadRequest ,
31+ InvalidFilterError , InvalidSortError )
32+ from tastypie .http import HttpApplicationError , HttpNotImplemented
33+ from tastypie import http
34+ from tastypie .resources import ALL , ALL_WITH_RELATIONS
2435
2536try :
2637 from tastypie .http import HttpNotFound
@@ -33,6 +44,9 @@ class HttpNotFound(HttpResponse):
3344from tastypie .serializers import Serializer
3445from tastypie .bundle import Bundle
3546
47+ LOG = logging .getLogger (__name__ )
48+ logging .basicConfig (level = logging .WARNING )
49+
3650
3751def my_handler (obj ):
3852 """
@@ -112,15 +126,7 @@ def serialize(self, bundle, format='application/json', options={}):
112126 try :
113127 return super (MySerializer , self ).serialize (bundle , format , options )
114128 except (ImproperlyConfigured , UnsupportedFormat ):
115- raise ImmediateHttpResponse (
116- HttpNotImplemented (settings .HTTP_NOT_IMPLEMENTED_ERROR )
117- )
118- except ImmediateHttpResponse :
119- raise
120- except Exception :
121- raise ImmediateHttpResponse (
122- HttpApplicationError (settings .HTTP_APPLICATION_ERROR )
123- )
129+ raise HttpNotImplemented (settings .HTTP_NOT_IMPLEMENTED_ERROR )
124130
125131
126132class RawModelResource (ModelResource ):
@@ -181,33 +187,249 @@ def build_schema(self):
181187 del schema ['fields' ][key ]['default' ]
182188 return schema
183189
184- def obj_get (self , ** kwargs ):
185- try :
186- return super (RawModelResource , self ).obj_get (** kwargs )
187- except (NotFound , ObjectDoesNotExist ):
188- raise ImmediateHttpResponse (
189- HttpNotFound (settings .HTTP_NOT_FOUND )
190- )
191- except ImmediateHttpResponse :
192- raise
193- except Exception :
194- raise ImmediateHttpResponse (
195- HttpApplicationError (settings .HTTP_APPLICATION_ERROR )
196- )
197-
198- def obj_get_list (self , ** kwargs ):
199- try :
200- return super (RawModelResource , self ).obj_get_list (** kwargs )
201- except (NotFound , ObjectDoesNotExist ):
202- raise ImmediateHttpResponse (
203- HttpNotFound (settings .HTTP_NOT_FOUND )
204- )
205- except ImmediateHttpResponse :
206- raise
207- except Exception :
208- raise ImmediateHttpResponse (
209- HttpApplicationError (settings .HTTP_APPLICATION_ERROR )
210- )
190+ # overwrite some method to have the same 'json' schema return for all
191+ # kind of error
192+
193+ def _handle_500 (self , request , exception ):
194+ response_class = HttpApplicationError
195+ response_code = 500
196+ if isinstance (exception , (NotFound , ObjectDoesNotExist , Http404 )):
197+ response_class = HttpResponseNotFound
198+ response_code = 404
199+
200+ LOG .error ('Internal Server Error: %s' % request .path , exc_info = True ,
201+ extra = {'status_code' : response_code , 'request' : request })
202+
203+ if settings .DEBUG :
204+ import traceback
205+ import sys
206+ tb = '\n ' .join (traceback .format_exception (* (sys .exc_info ())))
207+ data = {
208+ "error" : unicode (exception ),
209+ "traceback" : tb ,
210+ }
211+ return self .error_response (request , data ,
212+ response_class = response_class )
213+
214+ msg = "Sorry, this request could not be processed. " \
215+ "Please try again later."
216+ data = {
217+ 'error' : getattr (settings , 'HTTP_APPLICATION_ERROR' , msg ),
218+ }
219+ return self .error_response (request , data ,
220+ response_class = response_class )
221+
222+ def error_response (self , request , errors , response_class = None ):
223+ desired_format = self ._meta .default_format
224+ if request :
225+ try :
226+ desired_format = self .determine_format (request )
227+ except BadRequest :
228+ pass
229+
230+ if response_class is None :
231+ response_class = http .HttpBadRequest
232+
233+ if isinstance (errors , dict ) and 'error' in errors :
234+ errors = errors ['error' ]
235+ if isinstance (errors , basestring ):
236+ errors = {'error' : errors }
237+
238+ serialized = self .serialize (request , errors , desired_format )
239+ return response_class (content = serialized , content_type = desired_format )
240+
241+ def check_filtering (self , field_name , filter_type = 'exact' ,
242+ filter_bits = None ):
243+ """
244+ Given a field name, a optional filter type and an optional list of
245+ additional relations, determine if a field can be filtered on.
246+
247+ If a filter does not meet the needed conditions, it should raise an
248+ ``InvalidFilterError``.
249+
250+ If the filter meets the conditions, a list of attribute names (not
251+ field names) will be returned.
252+ """
253+ if filter_bits is None :
254+ filter_bits = []
255+
256+ if field_name not in self ._meta .filtering :
257+ raise InvalidFilterError ({
258+ 'error' : ("Filtering on '{}' is not allowed."
259+ "" .format (field_name )),
260+ 'field' : field_name ,
261+ })
262+
263+ # Check to see if it's an allowed lookup type.
264+ if not self ._meta .filtering [field_name ] in (ALL , ALL_WITH_RELATIONS ):
265+ # Must be an explicit whitelist.
266+ if filter_type not in self ._meta .filtering [field_name ]:
267+ raise InvalidFilterError ({
268+ 'error' : ("Filter '{}' is not allowed on field '{}'"
269+ "" .format (filter_type , field_name )),
270+ 'field' : field_name ,
271+ })
272+ if self .fields [field_name ].attribute is None :
273+ raise InvalidFilterError ({
274+ 'error' : ("The '{}' field has no 'attribute' for "
275+ "searching with." .format (field_name )),
276+ 'field' : field_name ,
277+ })
278+
279+ # Check to see if it's a relational lookup and if that's allowed.
280+ if len (filter_bits ):
281+ if not getattr (self .fields [field_name ], 'is_related' , False ):
282+ raise InvalidFilterError ({
283+ 'error' : ("The '{}' field does not support relations."
284+ "" .format (field_name )),
285+ 'field' : field_name ,
286+ })
287+
288+ if not self ._meta .filtering [field_name ] == ALL_WITH_RELATIONS :
289+ raise InvalidFilterError ({
290+ 'error' : ("Lookups are not allowed more than one level "
291+ "deep on the '{}' field." .format (field_name )),
292+ 'field' : field_name ,
293+ })
294+
295+ # Recursively descend through the remaining lookups in the filter,
296+ # if any. We should ensure that all along the way, we're allowed
297+ # to filter on that field by the related resource.
298+ resource = self .fields [field_name ]
299+ related_resource = resource .get_related_resource (None )
300+ return [resource .attribute ] + \
301+ related_resource .check_filtering (filter_bits [0 ], filter_type ,
302+ filter_bits [1 :])
303+
304+ return [self .fields [field_name ].attribute ]
305+
306+ def apply_sorting (self , obj_list , options = None ):
307+ """
308+ Given a dictionary of options, apply some ORM-level sorting to the
309+ provided ``QuerySet``.
310+
311+ Looks for the ``order_by`` key and handles either ascending (just the
312+ field name) or descending (the field name with a ``-`` in front).
313+
314+ The field name should be the resource field, **NOT** model field.
315+ """
316+ if options is None :
317+ options = {}
318+
319+ parameter_name = 'order_by'
320+
321+ if 'order_by' not in options :
322+ if 'sort_by' not in options :
323+ # Nothing to alter the order. Return what we've got.
324+ return obj_list
325+ else :
326+ warnings .warn ("'sort_by' is a deprecated parameter. "
327+ "Please use 'order_by' instead." )
328+ parameter_name = 'sort_by'
329+
330+ order_by_args = []
331+
332+ if hasattr (options , 'getlist' ):
333+ order_bits = options .getlist (parameter_name )
334+ else :
335+ order_bits = options .get (parameter_name )
336+
337+ if not isinstance (order_bits , (list , tuple )):
338+ order_bits = [order_bits ]
339+
340+ for order_by in order_bits :
341+ order_by_bits = order_by .split (LOOKUP_SEP )
342+
343+ field_name = order_by_bits [0 ]
344+ order = ''
345+
346+ if order_by_bits [0 ].startswith ('-' ):
347+ field_name = order_by_bits [0 ][1 :]
348+ order = '-'
349+
350+ if field_name not in self .fields :
351+ # It's not a field we know about. Move along citizen.
352+ raise InvalidSortError ({
353+ 'error' : ("No matching '{}' field for ordering on."
354+ "" .format (field_name )),
355+ 'field' : field_name ,
356+ })
357+
358+ if field_name not in self ._meta .ordering :
359+ raise InvalidSortError ({
360+ 'error' : ("The '{}' field does not allow ordering."
361+ "" .format (field_name )),
362+ 'field' : field_name ,
363+ })
364+
365+ if self .fields [field_name ].attribute is None :
366+ raise InvalidSortError ({
367+ 'error' : ("The '{}' field has no 'attribute' for "
368+ "ordering with." .format (field_name )),
369+ 'field' : field_name ,
370+ })
371+
372+ order_by_args .append ("%s%s" % (order , LOOKUP_SEP .join ([
373+ self .fields [field_name ].attribute ] + order_by_bits [1 :])))
374+
375+ return obj_list .order_by (* order_by_args )
376+
377+ if tastypie .__version__ < (0 , 9 , 12 ):
378+
379+ # this in need to hanble the error for thoes versions
380+
381+ def wrap_view (self , view ):
382+
383+ try :
384+ from django .views .decorators .csrf import csrf_exempt
385+ except ImportError :
386+ def csrf_exempt (func ):
387+ return func
388+
389+ @csrf_exempt
390+ def wrapper (request , * args , ** kwargs ):
391+ from django .utils .cache import patch_cache_control
392+ from tastypie .fields import ApiFieldError
393+ from django .core .exceptions import ValidationError
394+
395+ try :
396+ callback = getattr (self , view )
397+ response = callback (request , * args , ** kwargs )
398+
399+ if request .is_ajax ():
400+ patch_cache_control (response , no_cache = True )
401+
402+ return response
403+ except (BadRequest , ApiFieldError , InvalidSortError ) as e :
404+ data = {"error" : e .args [0 ] if getattr (e , 'args' ) else '' }
405+ return self .error_response (
406+ request , data , response_class = http .HttpBadRequest )
407+ except ValidationError , e :
408+ data = {"error" : e .messages }
409+ return self .error_response (
410+ request , data , response_class = http .HttpBadRequest )
411+ except Exception , e :
412+ if hasattr (e , 'response' ):
413+ return e .response
414+
415+ # A real, non-expected exception.
416+ # Handle the case where the full traceback is more helpful
417+ # than the serialized error.
418+ if settings .DEBUG and getattr (
419+ settings , 'TASTYPIE_FULL_DEBUG' , False ):
420+ raise
421+
422+ # Re-raise the error to get a proper traceback when the
423+ # error happend during a test case
424+ if request .META .get ('SERVER_NAME' ) == 'testserver' :
425+ raise
426+
427+ # Rather than re-raising, we're going to things similar to
428+ # what Django does. The difference is returning a
429+ # serialized error message.
430+ return self ._handle_500 (request , e )
431+
432+ return wrapper
211433
212434 class Meta :
213435 max_limit = None
0 commit comments