diff options
| author | Tom Christie | 2012-09-20 13:06:27 +0100 |
|---|---|---|
| committer | Tom Christie | 2012-09-20 13:06:27 +0100 |
| commit | 4b691c402707775c3048a90531024f3bc5be6f91 (patch) | |
| tree | 3adfc54b0d8b70e4ea78edf7091f7827fa68f47b /rest_framework/utils | |
| parent | a1bcfbfe926621820832e32b0427601e1140b4f7 (diff) | |
| download | django-rest-framework-4b691c402707775c3048a90531024f3bc5be6f91.tar.bz2 | |
Change package name: djangorestframework -> rest_framework
Diffstat (limited to 'rest_framework/utils')
| -rw-r--r-- | rest_framework/utils/__init__.py | 101 | ||||
| -rw-r--r-- | rest_framework/utils/breadcrumbs.py | 34 | ||||
| -rw-r--r-- | rest_framework/utils/encoders.py | 38 | ||||
| -rw-r--r-- | rest_framework/utils/mediatypes.py | 113 |
4 files changed, 286 insertions, 0 deletions
diff --git a/rest_framework/utils/__init__.py b/rest_framework/utils/__init__.py new file mode 100644 index 00000000..a59fff45 --- /dev/null +++ b/rest_framework/utils/__init__.py @@ -0,0 +1,101 @@ +from django.utils.encoding import smart_unicode +from django.utils.xmlutils import SimplerXMLGenerator +from rest_framework.compat import StringIO + +import re +import xml.etree.ElementTree as ET + + +# 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) + + elif data is None: + # Don't output any value + pass + + 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/rest_framework/utils/breadcrumbs.py b/rest_framework/utils/breadcrumbs.py new file mode 100644 index 00000000..672d32a3 --- /dev/null +++ b/rest_framework/utils/breadcrumbs.py @@ -0,0 +1,34 @@ +from django.core.urlresolvers import resolve, get_script_prefix + + +def get_breadcrumbs(url): + """Given a url returns a list of breadcrumbs, which are each a tuple of (name, url).""" + + from rest_framework.views import APIView + + def breadcrumbs_recursive(url, breadcrumbs_list, prefix): + """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 Exception: + 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), APIView): + breadcrumbs_list.insert(0, (view.cls_instance.get_name(), prefix + 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, prefix) + + # Drop trailing non-slash off the end and continue to try to resolve more breadcrumbs + return breadcrumbs_recursive(url[:url.rfind('/') + 1], breadcrumbs_list, prefix) + + prefix = get_script_prefix().rstrip('/') + url = url[len(prefix):] + return breadcrumbs_recursive(url, [], prefix) diff --git a/rest_framework/utils/encoders.py b/rest_framework/utils/encoders.py new file mode 100644 index 00000000..6655acbc --- /dev/null +++ b/rest_framework/utils/encoders.py @@ -0,0 +1,38 @@ +""" +Helper classes for parsers. +""" +import datetime +import decimal +from django.utils import simplejson as json +from rest_framework.compat import timezone + + +class JSONEncoder(json.JSONEncoder): + """ + JSONEncoder subclass that knows how to encode date/time, + decimal types, and generators. + """ + def default(self, o): + # For Date Time string spec, see ECMA 262 + # http://ecma-international.org/ecma-262/5.1/#sec-15.9.1.15 + if isinstance(o, datetime.datetime): + r = o.isoformat() + if o.microsecond: + r = r[:23] + r[26:] + if r.endswith('+00:00'): + r = r[:-6] + 'Z' + return r + elif isinstance(o, datetime.date): + return o.isoformat() + elif isinstance(o, datetime.time): + if timezone and timezone.is_aware(o): + raise ValueError("JSON can't represent timezone-aware times.") + r = o.isoformat() + if o.microsecond: + r = r[:12] + return r + elif isinstance(o, decimal.Decimal): + return str(o) + elif hasattr(o, '__iter__'): + return [i for i in o] + return super(JSONEncoder, self).default(o) diff --git a/rest_framework/utils/mediatypes.py b/rest_framework/utils/mediatypes.py new file mode 100644 index 00000000..5eba7fb2 --- /dev/null +++ b/rest_framework/utils/mediatypes.py @@ -0,0 +1,113 @@ +""" +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 sets 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 = [set(), set(), set(), set()] + for media_type in media_type_lst: + precedence = _MediaType(media_type).precedence + ret[3 - precedence].add(media_type) + return [media_types for media_types in ret if media_types] + + +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 __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 |
