aboutsummaryrefslogtreecommitdiffstats
path: root/djangorestframework/utils
diff options
context:
space:
mode:
authorTom Christie2011-04-29 14:32:56 +0100
committerTom Christie2011-04-29 14:32:56 +0100
commitb358fbdbe9cbd4ce644c4b2c7b9b4cec0811e14e (patch)
tree601e5576995809b74f914cafcee8a7a8cd9c6937 /djangorestframework/utils
parent93aa065fa92f64472a3ee80564020a81776be742 (diff)
downloaddjango-rest-framework-b358fbdbe9cbd4ce644c4b2c7b9b4cec0811e14e.tar.bz2
More refactoring - move various less core stuff into utils etc
Diffstat (limited to 'djangorestframework/utils')
-rw-r--r--djangorestframework/utils/__init__.py158
-rw-r--r--djangorestframework/utils/breadcrumbs.py30
-rw-r--r--djangorestframework/utils/description.py37
-rw-r--r--djangorestframework/utils/mediatypes.py78
4 files changed, 303 insertions, 0 deletions
diff --git a/djangorestframework/utils/__init__.py b/djangorestframework/utils/__init__.py
new file mode 100644
index 00000000..9dc769be
--- /dev/null
+++ b/djangorestframework/utils/__init__.py
@@ -0,0 +1,158 @@
+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}
+
+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 obj return a tuple"""
+ 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..1e604efc
--- /dev/null
+++ b/djangorestframework/utils/breadcrumbs.py
@@ -0,0 +1,30 @@
+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)."""
+
+ 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:
+ if callable(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..f7145c0f
--- /dev/null
+++ b/djangorestframework/utils/description.py
@@ -0,0 +1,37 @@
+"""Get a descriptive name and description for a view,
+based on class name and docstring, and override-able by 'name' and 'description' attributes"""
+import re
+
+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 getattr(view, 'name', None) is not None:
+ return view.name
+
+ if getattr(view, '__name__', None) is not None:
+ name = view.__name__
+ elif getattr(view, '__class__', None) is not None: # TODO: should be able to get rid of this case once refactoring to 1.3 class views is complete
+ name = view.__class__.__name__
+ 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 getattr(view, 'description', None) is not None:
+ return getattr(view, 'description')
+
+ if getattr(view, '__doc__', None) is not None:
+ whitespace_counts = [len(line) - len(line.lstrip(' ')) for line in view.__doc__.splitlines()[1:] if line.lstrip()]
+
+ if whitespace_counts:
+ whitespace_pattern = '^' + (' ' * min(whitespace_counts))
+ return re.sub(re.compile(whitespace_pattern, re.MULTILINE), '', view.__doc__)
+
+ return view.__doc__
+
+ return '' \ No newline at end of file
diff --git a/djangorestframework/utils/mediatypes.py b/djangorestframework/utils/mediatypes.py
new file mode 100644
index 00000000..92d9264c
--- /dev/null
+++ b/djangorestframework/utils/mediatypes.py
@@ -0,0 +1,78 @@
+"""
+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
+
+
+class MediaType(object):
+ def __init__(self, media_type_str):
+ self.orig = media_type_str
+ self.media_type, self.params = parse_header(media_type_str)
+ self.main_type, sep, self.sub_type = self.media_type.partition('/')
+
+ def match(self, other):
+ """Return true if this MediaType satisfies the constraint of the given MediaType."""
+ for key in other.params.keys():
+ if key != 'q' and other.params[key] != self.params.get(key, None):
+ return False
+
+ if other.sub_type != '*' and other.sub_type != self.sub_type:
+ return False
+
+ if other.main_type != '*' and other.main_type != self.main_type:
+ return False
+
+ return True
+
+ def precedence(self):
+ """
+ Return a precedence level for the media type given how specific it is.
+ """
+ if self.main_type == '*':
+ return 1
+ elif self.sub_type == '*':
+ return 2
+ elif not self.params or self.params.keys() == ['q']:
+ return 3
+ return 4
+
+ 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 is_form(self):
+ """
+ Return True if the MediaType 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)
+ """
+ return self.media_type == 'application/x-www-form-urlencoded' or \
+ self.media_type == 'multipart/form-data'
+
+ 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):
+ return self.orig
+