mirror of
				https://github.com/encode/django-rest-framework.git
				synced 2025-10-31 16:07:38 +03:00 
			
		
		
		
	content type tunneling
This commit is contained in:
		
						commit
						9adb965126
					
				|  | @ -13,14 +13,14 @@ OBJECT_STORE_DIR = os.path.join(settings.MEDIA_ROOT, 'objectstore') | |||
| class ObjectStoreRoot(Resource): | ||||
|     """Root of the Object Store API. | ||||
|     Allows the client to get a complete list of all the stored objects, or to create a new stored object.""" | ||||
|     allowed_methods = ('GET', 'POST') | ||||
|     allowed_methods = anon_allowed_methods = ('GET', 'POST') | ||||
| 
 | ||||
|     def get(self, request): | ||||
|     def get(self, request, auth): | ||||
|         """Return a list of all the stored object URLs.""" | ||||
|         keys = sorted(os.listdir(OBJECT_STORE_DIR)) | ||||
|         return [self.reverse(StoredObject, key=key) for key in keys] | ||||
|      | ||||
|     def post(self, request, content): | ||||
|     def post(self, request, auth, content): | ||||
|         """Create a new stored object, with a unique key.""" | ||||
|         key = str(uuid.uuid1()) | ||||
|         pathname = os.path.join(OBJECT_STORE_DIR, key) | ||||
|  | @ -31,22 +31,22 @@ class ObjectStoreRoot(Resource): | |||
| class StoredObject(Resource): | ||||
|     """Represents a stored object. | ||||
|     The object may be any picklable content.""" | ||||
|     allowed_methods = ('GET', 'PUT', 'DELETE') | ||||
|     allowed_methods = anon_allowed_methods = ('GET', 'PUT', 'DELETE') | ||||
| 
 | ||||
|     def get(self, request, key): | ||||
|     def get(self, request, auth, key): | ||||
|         """Return a stored object, by unpickling the contents of a locally stored file.""" | ||||
|         pathname = os.path.join(OBJECT_STORE_DIR, key) | ||||
|         if not os.path.exists(pathname): | ||||
|             return Response(status.HTTP_404_NOT_FOUND) | ||||
|         return pickle.load(open(pathname, 'rb')) | ||||
| 
 | ||||
|     def put(self, request, content, key): | ||||
|     def put(self, request, auth, content, key): | ||||
|         """Update/create a stored object, by pickling the request content to a locally stored file.""" | ||||
|         pathname = os.path.join(OBJECT_STORE_DIR, key) | ||||
|         pickle.dump(content, open(pathname, 'wb')) | ||||
|         return content | ||||
| 
 | ||||
|     def delete(self, request, key): | ||||
|     def delete(self, request, auth, key): | ||||
|         """Delete a stored object, by removing it's pickled file.""" | ||||
|         pathname = os.path.join(OBJECT_STORE_DIR, key) | ||||
|         if not os.path.exists(pathname): | ||||
|  |  | |||
|  | @ -4,9 +4,15 @@ from django.contrib import admin | |||
| admin.autodiscover() | ||||
| 
 | ||||
| urlpatterns = patterns('', | ||||
| <<<<<<< local | ||||
|     (r'^blog-post-api/', include('blogpost.urls')), | ||||
|     (r'^object-store-api/', include('objectstore.urls')), | ||||
|     (r'^pygments-api/', include('pygments_api.urls')), | ||||
| ======= | ||||
|     (r'pygments-example/', include('pygmentsapi.urls')), | ||||
|     (r'^blog-post-example/', include('blogpost.urls')), | ||||
|     (r'^object-store-example/', include('objectstore.urls')), | ||||
| >>>>>>> other | ||||
|     (r'^admin/doc/', include('django.contrib.admindocs.urls')), | ||||
|     (r'^admin/', include(admin.site.urls)), | ||||
| ) | ||||
|  |  | |||
|  | @ -1,8 +1,10 @@ | |||
| from django.template import RequestContext, loader | ||||
| from django import forms | ||||
| 
 | ||||
| from flywheel.response import NoContent | ||||
| 
 | ||||
| from utils import dict2xml | ||||
| import string | ||||
| try: | ||||
|     import json | ||||
| except ImportError: | ||||
|  | @ -33,25 +35,31 @@ class TemplateEmitter(BaseEmitter): | |||
|         return self.template.render(Context(output)) | ||||
|          | ||||
| 
 | ||||
| from django import forms | ||||
| class JSONForm(forms.Form): | ||||
|     _contenttype = forms.CharField(max_length=256, initial='application/json', label='Content Type') | ||||
|     _content = forms.CharField(label='Content', widget=forms.Textarea) | ||||
| 
 | ||||
| 
 | ||||
| class DocumentingTemplateEmitter(BaseEmitter): | ||||
|     """Emitter used to self-document the API""" | ||||
|     template = None | ||||
| 
 | ||||
|     def emit(self, output=NoContent): | ||||
|         resource = self.resource | ||||
|     def _get_content(self, resource, output): | ||||
|         """Get the content as if it had been emitted by a non-documenting emitter. | ||||
| 
 | ||||
|         # Find the first valid emitter and emit the content. (Don't another documenting emitter.) | ||||
|         (Typically this will be the content as it would have been if the Resource had been | ||||
|         requested with an 'Accept: */*' header, although with verbose style formatting if appropriate.)""" | ||||
| 
 | ||||
|         # Find the first valid emitter and emit the content. (Don't use another documenting emitter.) | ||||
|         emitters = [emitter for emitter in resource.emitters if not isinstance(emitter, DocumentingTemplateEmitter)] | ||||
|         if not emitters: | ||||
|             content = 'No emitters were found' | ||||
|         else: | ||||
|             content = emitters[0](resource).emit(output, verbose=True) | ||||
|             return '[No emitters were found]' | ||||
|          | ||||
|         content = emitters[0](resource).emit(output, verbose=True) | ||||
|         if not all(char in string.printable for char in content): | ||||
|             return '[%d bytes of binary content]' | ||||
|              | ||||
|         return content | ||||
|              | ||||
| 
 | ||||
|     def _get_form_instance(self, resource): | ||||
|         # Get the form instance if we have one bound to the input | ||||
|         form_instance = resource.form_instance | ||||
|          | ||||
|  | @ -70,8 +78,45 @@ class DocumentingTemplateEmitter(BaseEmitter): | |||
|             except: | ||||
|                 pass | ||||
| 
 | ||||
|         # If we still don't have a form instance then try to get an unbound form which can tunnel arbitrary content types | ||||
|         if not form_instance: | ||||
|             form_instance = JSONForm() | ||||
|             form_instance = self._get_generic_content_form(resource) | ||||
|          | ||||
|         return form_instance | ||||
| 
 | ||||
| 
 | ||||
|     def _get_generic_content_form(self, resource): | ||||
|         """Returns a form that allows for arbitrary content types to be tunneled via standard HTML forms | ||||
|         (Which are typically application/x-www-form-urlencoded)""" | ||||
| 
 | ||||
|         # NB. http://jacobian.org/writing/dynamic-form-generation/ | ||||
|         class GenericContentForm(forms.Form): | ||||
|             def __init__(self, resource): | ||||
|                 """We don't know the names of the fields we want to set until the point the form is instantiated, | ||||
|                 as they are determined by the Resource the form is being created against. | ||||
|                 Add the fields dynamically.""" | ||||
|                 super(GenericContentForm, self).__init__() | ||||
| 
 | ||||
|                 contenttype_choices = [(media_type, media_type) for media_type in resource.parsed_media_types] | ||||
|                 initial_contenttype = resource.default_parser.media_type | ||||
| 
 | ||||
|                 self.fields[resource.CONTENTTYPE_PARAM] = forms.ChoiceField(label='Content Type', | ||||
|                                                                             choices=contenttype_choices, | ||||
|                                                                             initial=initial_contenttype) | ||||
|                 self.fields[resource.CONTENT_PARAM] = forms.CharField(label='Content', | ||||
|                                                                       widget=forms.Textarea) | ||||
| 
 | ||||
|         # If either of these reserved parameters are turned off then content tunneling is not possible | ||||
|         if self.resource.CONTENTTYPE_PARAM is None or self.resource.CONTENT_PARAM is None: | ||||
|             return None | ||||
| 
 | ||||
|         # Okey doke, let's do it | ||||
|         return GenericContentForm(resource) | ||||
| 
 | ||||
| 
 | ||||
|     def emit(self, output=NoContent): | ||||
|         content = self._get_content(self.resource, output) | ||||
|         form_instance = self._get_form_instance(self.resource) | ||||
| 
 | ||||
|         template = loader.get_template(self.template) | ||||
|         context = RequestContext(self.resource.request, { | ||||
|  |  | |||
|  | @ -11,7 +11,7 @@ class BaseParser(object): | |||
|     """All parsers should extend BaseParser, specifing a media_type attribute, | ||||
|     and overriding the parse() method.""" | ||||
| 
 | ||||
|     media_types = () | ||||
|     media_type = None | ||||
| 
 | ||||
|     def __init__(self, resource): | ||||
|         """Initialise the parser with the Resource instance as state, | ||||
|  | @ -26,7 +26,7 @@ class BaseParser(object): | |||
| 
 | ||||
| 
 | ||||
| class JSONParser(BaseParser): | ||||
|     media_types = ('application/xml',) | ||||
|     media_type = 'application/json' | ||||
| 
 | ||||
|     def parse(self, input): | ||||
|         try: | ||||
|  | @ -36,7 +36,7 @@ class JSONParser(BaseParser): | |||
| 
 | ||||
| 
 | ||||
| class XMLParser(BaseParser): | ||||
|     media_types = ('application/xml',) | ||||
|     media_type = 'application/xml' | ||||
| 
 | ||||
| 
 | ||||
| class FormParser(BaseParser): | ||||
|  | @ -44,7 +44,7 @@ class FormParser(BaseParser): | |||
|     Return a dict containing a single value for each non-reserved parameter. | ||||
|     """ | ||||
|      | ||||
|     media_types = ('application/x-www-form-urlencoded',) | ||||
|     media_type = 'application/x-www-form-urlencoded' | ||||
| 
 | ||||
|     def parse(self, input): | ||||
|         # The FormParser doesn't parse the input as other parsers would, since Django's already done the | ||||
|  |  | |||
|  | @ -120,14 +120,16 @@ class Resource(object): | |||
|         (This emitter is used if the client does not send and Accept: header, or sends Accept: */*)""" | ||||
|         return self.emitters[0] | ||||
| 
 | ||||
|     #@property | ||||
|     #def parsed_media_types(self): | ||||
|     #    """Return an list of all the media types that this resource can emit.""" | ||||
|     #    return [parser.media_type for parser in self.parsers] | ||||
|     # | ||||
|     #@property | ||||
|     #def default_parser(self): | ||||
|     #    return self.parsers[0] | ||||
|     @property | ||||
|     def parsed_media_types(self): | ||||
|         """Return an list of all the media types that this resource can emit.""" | ||||
|         return [parser.media_type for parser in self.parsers] | ||||
|      | ||||
|     @property | ||||
|     def default_parser(self): | ||||
|         """Return the resource's most prefered emitter. | ||||
|         (This has no behavioural effect, but is may be used by documenting emitters)"""         | ||||
|         return self.parsers[0] | ||||
| 
 | ||||
| 
 | ||||
|     def get(self, request, auth, *args, **kwargs): | ||||
|  | @ -285,19 +287,28 @@ class Resource(object): | |||
|         """Return the appropriate parser for the input, given the client's 'Content-Type' header, | ||||
|         and the content types that this Resource knows how to parse.""" | ||||
|         content_type = request.META.get('CONTENT_TYPE', 'application/x-www-form-urlencoded') | ||||
|         raw_content = request.raw_post_data | ||||
|      | ||||
|         split = content_type.split(';', 1) | ||||
|         if len(split) > 1: | ||||
|             content_type = split[0] | ||||
|         content_type = content_type.strip() | ||||
|          | ||||
|         # If CONTENTTYPE_PARAM is turned on, and this is a standard POST form then allow the content type to be overridden | ||||
|         if (content_type == 'application/x-www-form-urlencoded' and | ||||
|             request.method == 'POST' and | ||||
|             self.CONTENTTYPE_PARAM and | ||||
|             self.CONTENT_PARAM and | ||||
|             request.POST.get(self.CONTENTTYPE_PARAM, None) and | ||||
|             request.POST.get(self.CONTENT_PARAM, None)): | ||||
|             raw_content = request.POST[self.CONTENT_PARAM] | ||||
|             content_type = request.POST[self.CONTENTTYPE_PARAM] | ||||
| 
 | ||||
|         # Create a list of list of (media_type, Parser) tuples | ||||
|         media_type_parser_tuples = [[(media_type, parser) for media_type in parser.media_types] for parser in self.parsers] | ||||
|          | ||||
|         # Flatten the list and turn it into a media_type -> Parser dict | ||||
|         media_type_to_parser = dict(chain.from_iterable(media_type_parser_tuples)) | ||||
|         media_type_to_parser = dict([(parser.media_type, parser) for parser in self.parsers]) | ||||
| 
 | ||||
|         try: | ||||
|             return media_type_to_parser[content_type] | ||||
|             return (media_type_to_parser[content_type], raw_content) | ||||
|         except KeyError: | ||||
|             raise ResponseException(status.HTTP_415_UNSUPPORTED_MEDIA_TYPE, | ||||
|                                     {'detail': 'Unsupported media type \'%s\'' % content_type}) | ||||
|  | @ -402,8 +413,8 @@ class Resource(object): | |||
|             # Either generate the response data, deserializing and validating any request data | ||||
|             # TODO: Add support for message bodys on other HTTP methods, as it is valid. | ||||
|             if method in ('PUT', 'POST'): | ||||
|                 parser = self.determine_parser(request) | ||||
|                 data = parser(self).parse(request.raw_post_data) | ||||
|                 (parser, raw_content) = self.determine_parser(request) | ||||
|                 data = parser(self).parse(raw_content) | ||||
|                 self.form_instance = self.get_form(data) | ||||
|                 data = self.cleanup_request(data, self.form_instance) | ||||
|                 response = func(request, auth_context, data, *args, **kwargs) | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	Block a user