diff options
| author | Tom Christie | 2014-09-02 15:07:56 +0100 | 
|---|---|---|
| committer | Tom Christie | 2014-09-02 15:07:56 +0100 | 
| commit | ec096a1caceff6a4f5c75a152dd1c7bea9ed281d (patch) | |
| tree | faa662483b23c5a1c4133a385b13080ebb6116af | |
| parent | 4ac4676a40b121d27cfd1173ff548d96b8d3de2f (diff) | |
| download | django-rest-framework-ec096a1caceff6a4f5c75a152dd1c7bea9ed281d.tar.bz2 | |
Add relations and get tests running
| -rw-r--r-- | rest_framework/fields.py | 30 | ||||
| -rw-r--r-- | rest_framework/mixins.py | 1 | ||||
| -rw-r--r-- | rest_framework/relations.py | 111 | ||||
| -rw-r--r-- | rest_framework/renderers.py | 14 | ||||
| -rw-r--r-- | rest_framework/serializers.py | 8 | ||||
| -rw-r--r-- | rest_framework/utils/html.py | 2 | ||||
| -rw-r--r-- | tests/test_serializer.py | 4 | 
7 files changed, 151 insertions, 19 deletions
| diff --git a/rest_framework/fields.py b/rest_framework/fields.py index a83bf94c..3e0f7ca4 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -68,7 +68,7 @@ class Field(object):      def __init__(self, read_only=False, write_only=False,                   required=None, default=empty, initial=None, source=None, -                 label=None, style=None): +                 label=None, style=None, error_messages=None):          self._creation_counter = Field._creation_counter          Field._creation_counter += 1 @@ -216,9 +216,11 @@ class CharField(Field):          'blank': 'This field may not be blank.'      } -    def __init__(self, *args, **kwargs): +    def __init__(self, **kwargs):          self.allow_blank = kwargs.pop('allow_blank', False) -        super(CharField, self).__init__(*args, **kwargs) +        self.max_length = kwargs.pop('max_length', None) +        self.min_length = kwargs.pop('min_length', None) +        super(CharField, self).__init__(**kwargs)      def to_native(self, data):          if data == '' and not self.allow_blank: @@ -233,7 +235,7 @@ class ChoiceField(Field):      }      coerce_to_type = str -    def __init__(self, *args, **kwargs): +    def __init__(self, **kwargs):          choices = kwargs.pop('choices')          assert choices, '`choices` argument is required and may not be empty' @@ -257,7 +259,7 @@ class ChoiceField(Field):              str(key): key for key in self.choices.keys()          } -        super(ChoiceField, self).__init__(*args, **kwargs) +        super(ChoiceField, self).__init__(**kwargs)      def to_native(self, data):          try: @@ -296,6 +298,24 @@ class IntegerField(Field):          return data +class EmailField(CharField): +    pass  # TODO + + +class RegexField(CharField): +    def __init__(self, **kwargs): +        self.regex = kwargs.pop('regex') +        super(CharField, self).__init__(**kwargs) + + +class DateTimeField(CharField): +    pass  # TODO + + +class FileField(Field): +    pass  # TODO + +  class MethodField(Field):      def __init__(self, **kwargs):          kwargs['source'] = '*' diff --git a/rest_framework/mixins.py b/rest_framework/mixins.py index ee01cabc..3e9c9bb3 100644 --- a/rest_framework/mixins.py +++ b/rest_framework/mixins.py @@ -6,7 +6,6 @@ which allows mixin classes to be composed in interesting ways.  """  from __future__ import unicode_literals -from django.core.exceptions import ValidationError  from django.http import Http404  from rest_framework import status  from rest_framework.response import Response diff --git a/rest_framework/relations.py b/rest_framework/relations.py index e69de29b..42d2c121 100644 --- a/rest_framework/relations.py +++ b/rest_framework/relations.py @@ -0,0 +1,111 @@ +from rest_framework.fields import Field +from django.core.exceptions import ObjectDoesNotExist +from django.core.urlresolvers import resolve, get_script_prefix +from rest_framework.compat import urlparse + + +def get_default_queryset(serializer_class, field_name): +    manager = getattr(serializer_class.opts.model, field_name) +    if hasattr(manager, 'related'): +        # Forward relationships +        return manager.related.model._default_manager.all() +    # Reverse relationships +    return manager.field.rel.to._default_manager.all() + + +class RelatedField(Field): +    def __init__(self, **kwargs): +        self.queryset = kwargs.pop('queryset', None) +        self.many = kwargs.pop('many', False) +        super(RelatedField, self).__init__(**kwargs) + +    def bind(self, field_name, parent, root): +        super(RelatedField, self).bind(field_name, parent, root) +        if self.queryset is None and not self.read_only: +            self.queryset = get_default_queryset(parent, self.source) + + +class PrimaryKeyRelatedField(RelatedField): +    MESSAGES = { +        'required': 'This field is required.', +        'does_not_exist': "Invalid pk '{pk_value}' - object does not exist.", +        'incorrect_type': 'Incorrect type.  Expected pk value, received {data_type}.', +    } + +    def from_native(self, data): +        try: +            return self.queryset.get(pk=data) +        except ObjectDoesNotExist: +            self.fail('does_not_exist', pk_value=data) +        except (TypeError, ValueError): +            self.fail('incorrect_type', data_type=type(data).__name__) + + +class HyperlinkedRelatedField(RelatedField): +    lookup_field = 'pk' + +    MESSAGES = { +        'required': 'This field is required.', +        'no_match': 'Invalid hyperlink - No URL match', +        'incorrect_match': 'Invalid hyperlink - Incorrect URL match.', +        'does_not_exist': "Invalid hyperlink - Object does not exist.", +        'incorrect_type': 'Incorrect type.  Expected URL string, received {data_type}.', +    } + +    def __init__(self, **kwargs): +        self.view_name = kwargs.pop('view_name') +        self.lookup_field = kwargs.pop('lookup_field', self.lookup_field) +        self.lookup_url_kwarg = kwargs.pop('lookup_url_kwarg', self.lookup_field) +        super(HyperlinkedRelatedField, self).__init__(**kwargs) + +    def get_object(self, view_name, view_args, view_kwargs): +        """ +        Return the object corresponding to a matched URL. + +        Takes the matched URL conf arguments, and should return an +        object instance, or raise an `ObjectDoesNotExist` exception. +        """ +        lookup_value = view_kwargs[self.lookup_url_kwarg] +        lookup_kwargs = {self.lookup_field: lookup_value} +        return self.queryset.get(**lookup_kwargs) + +    def from_native(self, value): +        try: +            http_prefix = value.startswith(('http:', 'https:')) +        except AttributeError: +            self.fail('incorrect_type', type(value).__name__) + +        if http_prefix: +            # If needed convert absolute URLs to relative path +            value = urlparse.urlparse(value).path +            prefix = get_script_prefix() +            if value.startswith(prefix): +                value = '/' + value[len(prefix):] + +        try: +            match = resolve(value) +        except Exception: +            self.fail('no_match') + +        if match.view_name != self.view_name: +            self.fail('incorrect_match') + +        try: +            return self.get_object(match.view_name, match.args, match.kwargs) +        except (ObjectDoesNotExist, TypeError, ValueError): +            self.fail('does_not_exist') + + +class HyperlinkedIdentityField(RelatedField): +    lookup_field = 'pk' + +    def __init__(self, **kwargs): +        self.view_name = kwargs.pop('view_name') +        self.lookup_field = kwargs.pop('lookup_field', self.lookup_field) +        self.lookup_url_kwarg = kwargs.pop('lookup_url_kwarg', self.lookup_field) +        super(HyperlinkedIdentityField, self).__init__(**kwargs) + + +class SlugRelatedField(RelatedField): +    def __init__(self, **kwargs): +        self.slug_field = kwargs.pop('slug_field', None) diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index e8935b01..dfc5a39f 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -436,13 +436,13 @@ class BrowsableAPIRenderer(BaseRenderer):          if request.method == method:              try:                  data = request.DATA -                files = request.FILES +                # files = request.FILES              except ParseError:                  data = None -                files = None +                # files = None          else:              data = None -            files = None +            # files = None          with override_method(view, request, method) as request:              obj = getattr(view, 'object', None) @@ -579,10 +579,10 @@ class BrowsableAPIRenderer(BaseRenderer):              'available_formats': [renderer_cls.format for renderer_cls in view.renderer_classes],              'response_headers': response_headers, -            #'put_form': self.get_rendered_html_form(view, 'PUT', request), -            #'post_form': self.get_rendered_html_form(view, 'POST', request), -            #'delete_form': self.get_rendered_html_form(view, 'DELETE', request), -            #'options_form': self.get_rendered_html_form(view, 'OPTIONS', request), +            # 'put_form': self.get_rendered_html_form(view, 'PUT', request), +            # 'post_form': self.get_rendered_html_form(view, 'POST', request), +            # 'delete_form': self.get_rendered_html_form(view, 'DELETE', request), +            # 'options_form': self.get_rendered_html_form(view, 'OPTIONS', request),              'raw_data_put_form': raw_data_put_form,              'raw_data_post_form': raw_data_post_form, diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index d121812d..2f23b4d9 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -477,8 +477,8 @@ class ModelSerializer(Serializer):          if model_field:              kwargs['required'] = not(model_field.null or model_field.blank) -            # if model_field.help_text is not None: -            #     kwargs['help_text'] = model_field.help_text +            #  if model_field.help_text is not None: +            #      kwargs['help_text'] = model_field.help_text              if model_field.verbose_name is not None:                  kwargs['label'] = model_field.verbose_name              if not model_field.editable: @@ -566,8 +566,8 @@ class HyperlinkedModelSerializerOptions(ModelSerializerOptions):  class HyperlinkedModelSerializer(ModelSerializer):      _options_class = HyperlinkedModelSerializerOptions      _default_view_name = '%(model_name)s-detail' -    #_hyperlink_field_class = HyperlinkedRelatedField -    #_hyperlink_identify_field_class = HyperlinkedIdentityField +    # _hyperlink_field_class = HyperlinkedRelatedField +    # _hyperlink_identify_field_class = HyperlinkedIdentityField      def get_default_fields(self):          fields = super(HyperlinkedModelSerializer, self).get_default_fields() diff --git a/rest_framework/utils/html.py b/rest_framework/utils/html.py index bf17050d..edc591e9 100644 --- a/rest_framework/utils/html.py +++ b/rest_framework/utils/html.py @@ -1,6 +1,8 @@  """  Helpers for dealing with HTML input.  """ +import re +  def is_html_input(dictionary):      # MultiDict type datastructures are used to represent HTML form input, diff --git a/tests/test_serializer.py b/tests/test_serializer.py index 90f37cf2..fa5cafcf 100644 --- a/tests/test_serializer.py +++ b/tests/test_serializer.py @@ -1795,8 +1795,8 @@ class DefaultValuesOnAutogeneratedFieldsTests(TestCase):  class MetadataSerializer(serializers.Serializer): -    field1 = serializers.CharField(3, required=True) -    field2 = serializers.CharField(10, required=False) +    field1 = serializers.CharField(max_length=3, required=True) +    field2 = serializers.CharField(max_length=10, required=False)  class MetadataSerializerTestCase(TestCase): | 
