Add relations and get tests running

This commit is contained in:
Tom Christie 2014-09-02 15:07:56 +01:00
parent 4ac4676a40
commit ec096a1cac
7 changed files with 151 additions and 19 deletions

View File

@ -68,7 +68,7 @@ class Field(object):
def __init__(self, read_only=False, write_only=False, def __init__(self, read_only=False, write_only=False,
required=None, default=empty, initial=None, source=None, 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 self._creation_counter = Field._creation_counter
Field._creation_counter += 1 Field._creation_counter += 1
@ -216,9 +216,11 @@ class CharField(Field):
'blank': 'This field may not be blank.' 'blank': 'This field may not be blank.'
} }
def __init__(self, *args, **kwargs): def __init__(self, **kwargs):
self.allow_blank = kwargs.pop('allow_blank', False) 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): def to_native(self, data):
if data == '' and not self.allow_blank: if data == '' and not self.allow_blank:
@ -233,7 +235,7 @@ class ChoiceField(Field):
} }
coerce_to_type = str coerce_to_type = str
def __init__(self, *args, **kwargs): def __init__(self, **kwargs):
choices = kwargs.pop('choices') choices = kwargs.pop('choices')
assert choices, '`choices` argument is required and may not be empty' 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() 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): def to_native(self, data):
try: try:
@ -296,6 +298,24 @@ class IntegerField(Field):
return data 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): class MethodField(Field):
def __init__(self, **kwargs): def __init__(self, **kwargs):
kwargs['source'] = '*' kwargs['source'] = '*'

View File

@ -6,7 +6,6 @@ which allows mixin classes to be composed in interesting ways.
""" """
from __future__ import unicode_literals from __future__ import unicode_literals
from django.core.exceptions import ValidationError
from django.http import Http404 from django.http import Http404
from rest_framework import status from rest_framework import status
from rest_framework.response import Response from rest_framework.response import Response

View File

@ -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)

View File

@ -436,13 +436,13 @@ class BrowsableAPIRenderer(BaseRenderer):
if request.method == method: if request.method == method:
try: try:
data = request.DATA data = request.DATA
files = request.FILES # files = request.FILES
except ParseError: except ParseError:
data = None data = None
files = None # files = None
else: else:
data = None data = None
files = None # files = None
with override_method(view, request, method) as request: with override_method(view, request, method) as request:
obj = getattr(view, 'object', None) obj = getattr(view, 'object', None)

View File

@ -1,6 +1,8 @@
""" """
Helpers for dealing with HTML input. Helpers for dealing with HTML input.
""" """
import re
def is_html_input(dictionary): def is_html_input(dictionary):
# MultiDict type datastructures are used to represent HTML form input, # MultiDict type datastructures are used to represent HTML form input,

View File

@ -1795,8 +1795,8 @@ class DefaultValuesOnAutogeneratedFieldsTests(TestCase):
class MetadataSerializer(serializers.Serializer): class MetadataSerializer(serializers.Serializer):
field1 = serializers.CharField(3, required=True) field1 = serializers.CharField(max_length=3, required=True)
field2 = serializers.CharField(10, required=False) field2 = serializers.CharField(max_length=10, required=False)
class MetadataSerializerTestCase(TestCase): class MetadataSerializerTestCase(TestCase):