aboutsummaryrefslogtreecommitdiffstats
path: root/djangorestframework/utils
diff options
context:
space:
mode:
authorTom Christie2011-06-02 12:58:10 +0100
committerTom Christie2011-06-02 12:58:10 +0100
commitb50492853f537a2473bb0a9eea86c8b0ed6b8824 (patch)
treed289d39aacf187a8a0696a4c1c863aabe1472c3a /djangorestframework/utils
parent7ee9adbe5c03c29cd4a894dd476548f7fe73b5e4 (diff)
parentfc1640de75511006e89f033c9270ec91a9f1e4d4 (diff)
downloaddjango-rest-framework-b50492853f537a2473bb0a9eea86c8b0ed6b8824.tar.bz2
pull in -dev as 0.2.0
Diffstat (limited to 'djangorestframework/utils')
-rw-r--r--djangorestframework/utils/__init__.py171
-rw-r--r--djangorestframework/utils/breadcrumbs.py33
-rw-r--r--djangorestframework/utils/description.py91
-rw-r--r--djangorestframework/utils/mediatypes.py137
-rw-r--r--djangorestframework/utils/staticviews.py65
5 files changed, 497 insertions, 0 deletions
diff --git a/djangorestframework/utils/__init__.py b/djangorestframework/utils/__init__.py
new file mode 100644
index 00000000..99f9724c
--- /dev/null
+++ b/djangorestframework/utils/__init__.py
@@ -0,0 +1,171 @@
+from django.utils.encoding import smart_unicode
+from django.utils.xmlutils import SimplerXMLGenerator
+from django.core.urlresolvers import resolve
+from django.conf import settings
+
+from djangorestframework.compat import StringIO
+
+import re
+import xml.etree.ElementTree as ET
+
+
+#def admin_media_prefix(request):
+# """Adds the ADMIN_MEDIA_PREFIX to the request context."""
+# return {'ADMIN_MEDIA_PREFIX': settings.ADMIN_MEDIA_PREFIX}
+
+from mediatypes import media_type_matches, is_form_media_type
+from mediatypes import add_media_type_param, get_media_type_params, order_by_precedence
+
+MSIE_USER_AGENT_REGEX = re.compile(r'^Mozilla/[0-9]+\.[0-9]+ \([^)]*; MSIE [0-9]+\.[0-9]+[a-z]?;[^)]*\)(?!.* Opera )')
+
+def as_tuple(obj):
+ """
+ Given an object which may be a list/tuple, another object, or None,
+ return that object in list form.
+
+ IE:
+ If the object is already a list/tuple just return it.
+ If the object is not None, return it in a list with a single element.
+ If the object is None return an empty list.
+ """
+ if obj is None:
+ return ()
+ elif isinstance(obj, list):
+ return tuple(obj)
+ elif isinstance(obj, tuple):
+ return obj
+ return (obj,)
+
+
+def url_resolves(url):
+ """
+ Return True if the given URL is mapped to a view in the urlconf, False otherwise.
+ """
+ try:
+ resolve(url)
+ except:
+ return False
+ return True
+
+
+# From http://www.koders.com/python/fidB6E125C586A6F49EAC38992CF3AFDAAE35651975.aspx?s=mdef:xml
+#class object_dict(dict):
+# """object view of dict, you can
+# >>> a = object_dict()
+# >>> a.fish = 'fish'
+# >>> a['fish']
+# 'fish'
+# >>> a['water'] = 'water'
+# >>> a.water
+# 'water'
+# >>> a.test = {'value': 1}
+# >>> a.test2 = object_dict({'name': 'test2', 'value': 2})
+# >>> a.test, a.test2.name, a.test2.value
+# (1, 'test2', 2)
+# """
+# def __init__(self, initd=None):
+# if initd is None:
+# initd = {}
+# dict.__init__(self, initd)
+#
+# def __getattr__(self, item):
+# d = self.__getitem__(item)
+# # if value is the only key in object, you can omit it
+# if isinstance(d, dict) and 'value' in d and len(d) == 1:
+# return d['value']
+# else:
+# return d
+#
+# def __setattr__(self, item, value):
+# self.__setitem__(item, value)
+
+
+# From xml2dict
+class XML2Dict(object):
+
+ def __init__(self):
+ pass
+
+ def _parse_node(self, node):
+ node_tree = {}
+ # Save attrs and text, hope there will not be a child with same name
+ if node.text:
+ node_tree = node.text
+ for (k,v) in node.attrib.items():
+ k,v = self._namespace_split(k, v)
+ node_tree[k] = v
+ #Save childrens
+ for child in node.getchildren():
+ tag, tree = self._namespace_split(child.tag, self._parse_node(child))
+ if tag not in node_tree: # the first time, so store it in dict
+ node_tree[tag] = tree
+ continue
+ old = node_tree[tag]
+ if not isinstance(old, list):
+ node_tree.pop(tag)
+ node_tree[tag] = [old] # multi times, so change old dict to a list
+ node_tree[tag].append(tree) # add the new one
+
+ return node_tree
+
+
+ def _namespace_split(self, tag, value):
+ """
+ Split the tag '{http://cs.sfsu.edu/csc867/myscheduler}patients'
+ ns = http://cs.sfsu.edu/csc867/myscheduler
+ name = patients
+ """
+ result = re.compile("\{(.*)\}(.*)").search(tag)
+ if result:
+ value.namespace, tag = result.groups()
+ return (tag, value)
+
+ def parse(self, file):
+ """parse a xml file to a dict"""
+ f = open(file, 'r')
+ return self.fromstring(f.read())
+
+ def fromstring(self, s):
+ """parse a string"""
+ t = ET.fromstring(s)
+ unused_root_tag, root_tree = self._namespace_split(t.tag, self._parse_node(t))
+ return root_tree
+
+
+def xml2dict(input):
+ return XML2Dict().fromstring(input)
+
+
+# Piston:
+class XMLRenderer():
+ def _to_xml(self, xml, data):
+ if isinstance(data, (list, tuple)):
+ for item in data:
+ xml.startElement("list-item", {})
+ self._to_xml(xml, item)
+ xml.endElement("list-item")
+
+ elif isinstance(data, dict):
+ for key, value in data.iteritems():
+ xml.startElement(key, {})
+ self._to_xml(xml, value)
+ xml.endElement(key)
+
+ else:
+ xml.characters(smart_unicode(data))
+
+ def dict2xml(self, data):
+ stream = StringIO.StringIO()
+
+ xml = SimplerXMLGenerator(stream, "utf-8")
+ xml.startDocument()
+ xml.startElement("root", {})
+
+ self._to_xml(xml, data)
+
+ xml.endElement("root")
+ xml.endDocument()
+ return stream.getvalue()
+
+def dict2xml(input):
+ return XMLRenderer().dict2xml(input)
diff --git a/djangorestframework/utils/breadcrumbs.py b/djangorestframework/utils/breadcrumbs.py
new file mode 100644
index 00000000..0b043c78
--- /dev/null
+++ b/djangorestframework/utils/breadcrumbs.py
@@ -0,0 +1,33 @@
+from django.core.urlresolvers import resolve
+from djangorestframework.utils.description import get_name
+
+def get_breadcrumbs(url):
+ """Given a url returns a list of breadcrumbs, which are each a tuple of (name, url)."""
+
+ from djangorestframework.views import View
+
+ def breadcrumbs_recursive(url, breadcrumbs_list):
+ """Add tuples of (name, url) to the breadcrumbs list, progressively chomping off parts of the url."""
+
+ try:
+ (view, unused_args, unused_kwargs) = resolve(url)
+ except:
+ pass
+ else:
+ # Check if this is a REST framework view, and if so add it to the breadcrumbs
+ if isinstance(getattr(view, 'cls_instance', None), View):
+ breadcrumbs_list.insert(0, (get_name(view), url))
+
+ if url == '':
+ # All done
+ return breadcrumbs_list
+
+ elif url.endswith('/'):
+ # Drop trailing slash off the end and continue to try to resolve more breadcrumbs
+ return breadcrumbs_recursive(url.rstrip('/'), breadcrumbs_list)
+
+ # Drop trailing non-slash off the end and continue to try to resolve more breadcrumbs
+ return breadcrumbs_recursive(url[:url.rfind('/') + 1], breadcrumbs_list)
+
+ return breadcrumbs_recursive(url, [])
+
diff --git a/djangorestframework/utils/description.py b/djangorestframework/utils/description.py
new file mode 100644
index 00000000..25bef80b
--- /dev/null
+++ b/djangorestframework/utils/description.py
@@ -0,0 +1,91 @@
+"""
+Get a descriptive name and description for a view.
+"""
+import re
+from djangorestframework.resources import Resource, FormResource, ModelResource
+
+
+# These a a bit Grungy, but they do the job.
+
+def get_name(view):
+ """
+ Return a name for the view.
+
+ If view has a name attribute, use that, otherwise use the view's class name, with 'CamelCaseNames' converted to 'Camel Case Names'.
+ """
+
+ # If we're looking up the name of a view callable, as found by reverse,
+ # grok the class instance that we stored when as_view was called.
+ if getattr(view, 'cls_instance', None):
+ view = view.cls_instance
+
+ # If this view has a resource that's been overridden, then use that resource for the name
+ if getattr(view, 'resource', None) not in (None, Resource, FormResource, ModelResource):
+ name = view.resource.__name__
+
+ # Chomp of any non-descriptive trailing part of the resource class name
+ if name.endswith('Resource') and name != 'Resource':
+ name = name[:-len('Resource')]
+
+ # If the view has a descriptive suffix, eg '*** List', '*** Instance'
+ if getattr(view, '_suffix', None):
+ name += view._suffix
+
+ # Otherwise if it's a function view use the function's name
+ elif getattr(view, '__name__', None) is not None:
+ name = view.__name__
+
+ # If it's a view class with no resource then grok the name from the class name
+ elif getattr(view, '__class__', None) is not None:
+ name = view.__class__.__name__
+
+ # Chomp of any non-descriptive trailing part of the view class name
+ if name.endswith('View') and name != 'View':
+ name = name[:-len('View')]
+
+ # I ain't got nuthin fo' ya
+ else:
+ return ''
+
+ return re.sub('(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))', ' \\1', name).strip()
+
+
+
+def get_description(view):
+ """
+ Provide a description for the view.
+
+ By default this is the view's docstring with nice unindention applied.
+ """
+
+ # If we're looking up the name of a view callable, as found by reverse,
+ # grok the class instance that we stored when as_view was called.
+ if getattr(view, 'cls_instance', None):
+ view = view.cls_instance
+
+
+ # If this view has a resource that's been overridden, then use the resource's doctring
+ if getattr(view, 'resource', None) not in (None, Resource, FormResource, ModelResource):
+ doc = view.resource.__doc__
+
+ # Otherwise use the view doctring
+ elif getattr(view, '__doc__', None):
+ doc = view.__doc__
+
+ # I ain't got nuthin fo' ya
+ else:
+ return ''
+
+ if not doc:
+ return ''
+
+ whitespace_counts = [len(line) - len(line.lstrip(' ')) for line in doc.splitlines()[1:] if line.lstrip()]
+
+ # unindent the docstring if needed
+ if whitespace_counts:
+ whitespace_pattern = '^' + (' ' * min(whitespace_counts))
+ return re.sub(re.compile(whitespace_pattern, re.MULTILINE), '', doc)
+
+ # otherwise return it as-is
+ return doc
+
diff --git a/djangorestframework/utils/mediatypes.py b/djangorestframework/utils/mediatypes.py
new file mode 100644
index 00000000..ae734e62
--- /dev/null
+++ b/djangorestframework/utils/mediatypes.py
@@ -0,0 +1,137 @@
+"""
+Handling of media types, as found in HTTP Content-Type and Accept headers.
+
+See http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7
+"""
+
+from django.http.multipartparser import parse_header
+
+
+def media_type_matches(lhs, rhs):
+ """
+ Returns ``True`` if the media type in the first argument <= the
+ media type in the second argument. The media types are strings
+ as described by the HTTP spec.
+
+ Valid media type strings include:
+
+ 'application/json; indent=4'
+ 'application/json'
+ 'text/*'
+ '*/*'
+ """
+ lhs = _MediaType(lhs)
+ rhs = _MediaType(rhs)
+ return lhs.match(rhs)
+
+
+def is_form_media_type(media_type):
+ """
+ Return True if the media type is a valid form media type as defined by the HTML4 spec.
+ (NB. HTML5 also adds text/plain to the list of valid form media types, but we don't support this here)
+ """
+ media_type = _MediaType(media_type)
+ return media_type.full_type == 'application/x-www-form-urlencoded' or \
+ media_type.full_type == 'multipart/form-data'
+
+
+def add_media_type_param(media_type, key, val):
+ """
+ Add a key, value parameter to a media type string, and return the new media type string.
+ """
+ media_type = _MediaType(media_type)
+ media_type.params[key] = val
+ return str(media_type)
+
+
+def get_media_type_params(media_type):
+ """
+ Return a dictionary of the parameters on the given media type.
+ """
+ return _MediaType(media_type).params
+
+
+def order_by_precedence(media_type_lst):
+ """
+ Returns a list of lists of media type strings, ordered by precedence.
+ Precedence is determined by how specific a media type is:
+
+ 3. 'type/subtype; param=val'
+ 2. 'type/subtype'
+ 1. 'type/*'
+ 0. '*/*'
+ """
+ ret = [[],[],[],[]]
+ for media_type in media_type_lst:
+ precedence = _MediaType(media_type).precedence
+ ret[3-precedence].append(media_type)
+ return ret
+
+
+class _MediaType(object):
+ def __init__(self, media_type_str):
+ if media_type_str is None:
+ media_type_str = ''
+ self.orig = media_type_str
+ self.full_type, self.params = parse_header(media_type_str)
+ self.main_type, sep, self.sub_type = self.full_type.partition('/')
+
+ def match(self, other):
+ """Return true if this MediaType satisfies the given MediaType."""
+ for key in self.params.keys():
+ if key != 'q' and other.params.get(key, None) != self.params.get(key, None):
+ return False
+
+ if self.sub_type != '*' and other.sub_type != '*' and other.sub_type != self.sub_type:
+ return False
+
+ if self.main_type != '*' and other.main_type != '*' and other.main_type != self.main_type:
+ return False
+
+ return True
+
+ @property
+ def precedence(self):
+ """
+ Return a precedence level from 0-3 for the media type given how specific it is.
+ """
+ if self.main_type == '*':
+ return 0
+ elif self.sub_type == '*':
+ return 1
+ elif not self.params or self.params.keys() == ['q']:
+ return 2
+ return 3
+
+ #def quality(self):
+ # """
+ # Return a quality level for the media type.
+ # """
+ # try:
+ # return Decimal(self.params.get('q', '1.0'))
+ # except:
+ # return Decimal(0)
+
+ #def score(self):
+ # """
+ # Return an overall score for a given media type given it's quality and precedence.
+ # """
+ # # NB. quality values should only have up to 3 decimal points
+ # # http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.9
+ # return self.quality * 10000 + self.precedence
+
+ #def as_tuple(self):
+ # return (self.main_type, self.sub_type, self.params)
+
+ #def __repr__(self):
+ # return "<MediaType %s>" % (self.as_tuple(),)
+
+ def __str__(self):
+ return unicode(self).encode('utf-8')
+
+ def __unicode__(self):
+ ret = "%s/%s" % (self.main_type, self.sub_type)
+ for key, val in self.params.items():
+ ret += "; %s=%s" % (key, val)
+ return ret
+
diff --git a/djangorestframework/utils/staticviews.py b/djangorestframework/utils/staticviews.py
new file mode 100644
index 00000000..de2cb5d8
--- /dev/null
+++ b/djangorestframework/utils/staticviews.py
@@ -0,0 +1,65 @@
+from django.contrib.auth.views import *
+from django.conf import settings
+from django.http import HttpResponse
+import base64
+
+def deny_robots(request):
+ return HttpResponse('User-agent: *\nDisallow: /', mimetype='text/plain')
+
+def favicon(request):
+ data = 'AAABAAEAEREAAAEAIADwBAAAFgAAACgAAAARAAAAIgAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADLy8tLy8vL3svLy1QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAy8vLBsvLywkAAAAATkZFS1xUVPqhn57/y8vL0gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJmVlQ/GxcXiy8vL88vLy4FdVlXzTkZF/2RdXP/Ly8vty8vLtMvLy5DLy8vty8vLxgAAAAAAAAAAAAAAAAAAAABORkUJTkZF4lNMS/+Lh4f/cWtq/05GRf9ORkX/Vk9O/3JtbP+Ef3//Vk9O/2ljYv/Ly8v5y8vLCQAAAAAAAAAAAAAAAE5GRQlORkX2TkZF/05GRf9ORkX/TkZF/05GRf9ORkX/TkZF/05GRf9ORkX/UElI/8PDw5cAAAAAAAAAAAAAAAAAAAAAAAAAAE5GRZZORkX/TkZF/05GRf9ORkX/TkZF/05GRf9ORkX/TkZF/05GRf+Cfn3/y8vLvQAAAAAAAAAAAAAAAAAAAADLy8tIaWNi805GRf9ORkX/YVpZ/396eV7Ly8t7qaen9lZOTu5ORkX/TkZF/25oZ//Ly8v/y8vLycvLy0gAAAAATkZFSGNcXPpORkX/TkZF/05GRf+ysLDzTkZFe1NLSv6Oior/raur805GRf9ORkX/TkZF/2hiYf+npaX/y8vL5wAAAABORkXnTkZF/05GRf9ORkX/VU1M/8vLy/9PR0b1TkZF/1VNTP/Ly8uQT0dG+E5GRf9ORkX/TkZF/1hRUP3Ly8tmAAAAAE5GRWBORkXkTkZF/05GRf9ORkX/t7a2/355eOpORkX/TkZFkISAf1BORkX/TkZF/05GRf9XT075TkZFZgAAAAAAAAAAAAAAAAAAAABORkXDTkZF/05GRf9lX17/ubi4/8vLy/+2tbT/Yltb/05GRf9ORkX/a2Vk/8vLy5MAAAAAAAAAAAAAAAAAAAAAAAAAAFNLSqNORkX/TkZF/05GRf9ORkX/TkZF/05GRf9ORkX/TkZF/05GRf+Cfn3/y8vL+cvLyw8AAAAAAAAAAAAAAABORkUSTkZF+U5GRf9ORkX/TkZF/05GRf9ORkX/TkZF/05GRf9ORkX/TkZF/1BJSP/CwsLmy8vLDwAAAAAAAAAAAAAAAE5GRRJORkXtTkZF9FFJSJ1ORkXJTkZF/05GRf9ORkX/ZF5d9k5GRZ9ORkXtTkZF5HFsaxUAAAAAAAAAAAAAAAAAAAAAAAAAAE5GRQxORkUJAAAAAAAAAABORkXhTkZF/2JbWv7Ly8tgAAAAAAAAAABORkUGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE5GRWBORkX2TkZFYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//+AAP9/gAD+P4AA4AOAAMADgADAA4AAwAOAAMMBgACCAIAAAAGAAIBDgADAA4AAwAOAAMADgADAB4AA/H+AAP7/gAA='
+ return HttpResponse(base64.b64decode(data), mimetype='image/vnd.microsoft.icon')
+
+# BLERGH
+# Replicate django.contrib.auth.views.login simply so we don't have get users to update TEMPLATE_CONTEXT_PROCESSORS
+# to add ADMIN_MEDIA_PREFIX to the RequestContext. I don't like this but really really want users to not have to
+# be making settings changes in order to accomodate django-rest-framework
+@csrf_protect
+@never_cache
+def api_login(request, template_name='api_login.html',
+ redirect_field_name=REDIRECT_FIELD_NAME,
+ authentication_form=AuthenticationForm):
+ """Displays the login form and handles the login action."""
+
+ redirect_to = request.REQUEST.get(redirect_field_name, '')
+
+ if request.method == "POST":
+ form = authentication_form(data=request.POST)
+ if form.is_valid():
+ # Light security check -- make sure redirect_to isn't garbage.
+ if not redirect_to or ' ' in redirect_to:
+ redirect_to = settings.LOGIN_REDIRECT_URL
+
+ # Heavier security check -- redirects to http://example.com should
+ # not be allowed, but things like /view/?param=http://example.com
+ # should be allowed. This regex checks if there is a '//' *before* a
+ # question mark.
+ elif '//' in redirect_to and re.match(r'[^\?]*//', redirect_to):
+ redirect_to = settings.LOGIN_REDIRECT_URL
+
+ # Okay, security checks complete. Log the user in.
+ auth_login(request, form.get_user())
+
+ if request.session.test_cookie_worked():
+ request.session.delete_test_cookie()
+
+ return HttpResponseRedirect(redirect_to)
+
+ else:
+ form = authentication_form(request)
+
+ request.session.set_test_cookie()
+
+ #current_site = get_current_site(request)
+
+ return render_to_response(template_name, {
+ 'form': form,
+ redirect_field_name: redirect_to,
+ #'site': current_site,
+ #'site_name': current_site.name,
+ 'ADMIN_MEDIA_PREFIX': settings.ADMIN_MEDIA_PREFIX,
+ }, context_instance=RequestContext(request))
+
+
+def api_logout(request, next_page=None, template_name='api_login.html', redirect_field_name=REDIRECT_FIELD_NAME):
+ return logout(request, next_page, template_name, redirect_field_name)