diff options
Diffstat (limited to 'rest_framework/utils')
| -rw-r--r-- | rest_framework/utils/humanize_datetime.py | 47 | ||||
| -rw-r--r-- | rest_framework/utils/modelinfo.py | 97 | ||||
| -rw-r--r-- | rest_framework/utils/representation.py | 72 |
3 files changed, 216 insertions, 0 deletions
diff --git a/rest_framework/utils/humanize_datetime.py b/rest_framework/utils/humanize_datetime.py new file mode 100644 index 00000000..649f2abc --- /dev/null +++ b/rest_framework/utils/humanize_datetime.py @@ -0,0 +1,47 @@ +""" +Helper functions that convert strftime formats into more readable representations. +""" +from rest_framework import ISO_8601 + + +def datetime_formats(formats): + format = ', '.join(formats).replace( + ISO_8601, + 'YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HH:MM|-HH:MM|Z]' + ) + return humanize_strptime(format) + + +def date_formats(formats): + format = ', '.join(formats).replace(ISO_8601, 'YYYY[-MM[-DD]]') + return humanize_strptime(format) + + +def time_formats(formats): + format = ', '.join(formats).replace(ISO_8601, 'hh:mm[:ss[.uuuuuu]]') + return humanize_strptime(format) + + +def humanize_strptime(format_string): + # Note that we're missing some of the locale specific mappings that + # don't really make sense. + mapping = { + "%Y": "YYYY", + "%y": "YY", + "%m": "MM", + "%b": "[Jan-Dec]", + "%B": "[January-December]", + "%d": "DD", + "%H": "hh", + "%I": "hh", # Requires '%p' to differentiate from '%H'. + "%M": "mm", + "%S": "ss", + "%f": "uuuuuu", + "%a": "[Mon-Sun]", + "%A": "[Monday-Sunday]", + "%p": "[AM|PM]", + "%z": "[+HHMM|-HHMM]" + } + for key, val in mapping.items(): + format_string = format_string.replace(key, val) + return format_string diff --git a/rest_framework/utils/modelinfo.py b/rest_framework/utils/modelinfo.py new file mode 100644 index 00000000..c0513886 --- /dev/null +++ b/rest_framework/utils/modelinfo.py @@ -0,0 +1,97 @@ +""" +Helper functions for returning the field information that is associated +with a model class. +""" +from collections import namedtuple, OrderedDict +from django.db import models +from django.utils import six +import inspect + +FieldInfo = namedtuple('FieldResult', ['pk', 'fields', 'forward_relations', 'reverse_relations']) +RelationInfo = namedtuple('RelationInfo', ['field', 'related', 'to_many', 'has_through_model']) + + +def _resolve_model(obj): + """ + Resolve supplied `obj` to a Django model class. + + `obj` must be a Django model class itself, or a string + representation of one. Useful in situtations like GH #1225 where + Django may not have resolved a string-based reference to a model in + another model's foreign key definition. + + String representations should have the format: + 'appname.ModelName' + """ + if isinstance(obj, six.string_types) and len(obj.split('.')) == 2: + app_name, model_name = obj.split('.') + return models.get_model(app_name, model_name) + elif inspect.isclass(obj) and issubclass(obj, models.Model): + return obj + raise ValueError("{0} is not a Django model".format(obj)) + + +def get_field_info(model): + """ + Given a model class, returns a `FieldInfo` instance containing metadata + about the various field types on the model. + """ + opts = model._meta.concrete_model._meta + + # Deal with the primary key. + pk = opts.pk + while pk.rel and pk.rel.parent_link: + # If model is a child via multitable inheritance, use parent's pk. + pk = pk.rel.to._meta.pk + + # Deal with regular fields. + fields = OrderedDict() + for field in [field for field in opts.fields if field.serialize and not field.rel]: + fields[field.name] = field + + # Deal with forward relationships. + forward_relations = OrderedDict() + for field in [field for field in opts.fields if field.serialize and field.rel]: + forward_relations[field.name] = RelationInfo( + field=field, + related=_resolve_model(field.rel.to), + to_many=False, + has_through_model=False + ) + + # Deal with forward many-to-many relationships. + for field in [field for field in opts.many_to_many if field.serialize]: + forward_relations[field.name] = RelationInfo( + field=field, + related=_resolve_model(field.rel.to), + to_many=True, + has_through_model=( + not field.rel.through._meta.auto_created + ) + ) + + # Deal with reverse relationships. + reverse_relations = OrderedDict() + for relation in opts.get_all_related_objects(): + accessor_name = relation.get_accessor_name() + reverse_relations[accessor_name] = RelationInfo( + field=None, + related=relation.model, + to_many=relation.field.rel.multiple, + has_through_model=False + ) + + # Deal with reverse many-to-many relationships. + for relation in opts.get_all_related_many_to_many_objects(): + accessor_name = relation.get_accessor_name() + reverse_relations[accessor_name] = RelationInfo( + field=None, + related=relation.model, + to_many=True, + has_through_model=( + hasattr(relation.field.rel, 'through') and + not relation.field.rel.through._meta.auto_created + ) + ) + + return FieldInfo(pk, fields, forward_relations, reverse_relations) diff --git a/rest_framework/utils/representation.py b/rest_framework/utils/representation.py new file mode 100644 index 00000000..1de21597 --- /dev/null +++ b/rest_framework/utils/representation.py @@ -0,0 +1,72 @@ +""" +Helper functions for creating user-friendly representations +of serializer classes and serializer fields. +""" +import re + + +def smart_repr(value): + value = repr(value) + + # Representations like u'help text' + # should simply be presented as 'help text' + if value.startswith("u'") and value.endswith("'"): + return value[1:] + + # Representations like + # <django.core.validators.RegexValidator object at 0x1047af050> + # Should be presented as + # <django.core.validators.RegexValidator object> + value = re.sub(' at 0x[0-9a-f]{8,10}>', '>', value) + + return value + + +def field_repr(field, force_many=False): + kwargs = field._kwargs + if force_many: + kwargs = kwargs.copy() + kwargs['many'] = True + kwargs.pop('child', None) + + arg_string = ', '.join([smart_repr(val) for val in field._args]) + kwarg_string = ', '.join([ + '%s=%s' % (key, smart_repr(val)) + for key, val in sorted(kwargs.items()) + ]) + if arg_string and kwarg_string: + arg_string += ', ' + + if force_many: + class_name = force_many.__class__.__name__ + else: + class_name = field.__class__.__name__ + + return "%s(%s%s)" % (class_name, arg_string, kwarg_string) + + +def serializer_repr(serializer, indent, force_many=None): + ret = field_repr(serializer, force_many) + ':' + indent_str = ' ' * indent + + if force_many: + fields = force_many.fields + else: + fields = serializer.fields + + for field_name, field in fields.items(): + ret += '\n' + indent_str + field_name + ' = ' + if hasattr(field, 'fields'): + ret += serializer_repr(field, indent + 1) + elif hasattr(field, 'child'): + ret += list_repr(field, indent + 1) + else: + ret += field_repr(field) + return ret + + +def list_repr(serializer, indent): + child = serializer.child + if hasattr(child, 'fields'): + return serializer_repr(serializer, indent, force_many=child) + return field_repr(serializer) |
