aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTom Christie2014-09-02 15:07:56 +0100
committerTom Christie2014-09-02 15:07:56 +0100
commitec096a1caceff6a4f5c75a152dd1c7bea9ed281d (patch)
treefaa662483b23c5a1c4133a385b13080ebb6116af
parent4ac4676a40b121d27cfd1173ff548d96b8d3de2f (diff)
downloaddjango-rest-framework-ec096a1caceff6a4f5c75a152dd1c7bea9ed281d.tar.bz2
Add relations and get tests running
-rw-r--r--rest_framework/fields.py30
-rw-r--r--rest_framework/mixins.py1
-rw-r--r--rest_framework/relations.py111
-rw-r--r--rest_framework/renderers.py14
-rw-r--r--rest_framework/serializers.py8
-rw-r--r--rest_framework/utils/html.py2
-rw-r--r--tests/test_serializer.py4
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):