mirror of
https://github.com/encode/django-rest-framework.git
synced 2024-11-26 11:33:59 +03:00
Merge remote-tracking branch 'reference/master' into p3k
This commit is contained in:
commit
5fad46d7e2
23
README.md
23
README.md
|
@ -58,6 +58,29 @@ To run the tests.
|
|||
|
||||
# Changelog
|
||||
|
||||
## 2.1.6
|
||||
|
||||
**Date**: 23rd Nov 2012
|
||||
|
||||
* Bugfix: Unfix DjangoModelPermissions. (I am a doofus.)
|
||||
|
||||
## 2.1.5
|
||||
|
||||
**Date**: 23rd Nov 2012
|
||||
|
||||
* Bugfix: Fix DjangoModelPermissions.
|
||||
|
||||
## 2.1.4
|
||||
|
||||
**Date**: 22nd Nov 2012
|
||||
|
||||
* Support for partial updates with serializers.
|
||||
* Added `RegexField`.
|
||||
* Added `SerializerMethodField`.
|
||||
* Serializer performance improvements.
|
||||
* Added `obtain_token_view` to get tokens when using `TokenAuthentication`.
|
||||
* Bugfix: Django 1.5 configurable user support for `TokenAuthentication`.
|
||||
|
||||
## 2.1.3
|
||||
|
||||
**Date**: 16th Nov 2012
|
||||
|
|
|
@ -116,7 +116,7 @@ When using `TokenAuthentication`, you may want to provide a mechanism for client
|
|||
REST framework provides a built-in view to provide this behavior. To use it, add the `obtain_auth_token` view to your URLconf:
|
||||
|
||||
urlpatterns += patterns('',
|
||||
url(r'^api-token-auth/', 'rest_framework.authtoken.obtain_auth_token')
|
||||
url(r'^api-token-auth/', 'rest_framework.authtoken.views.obtain_auth_token')
|
||||
)
|
||||
|
||||
Note that the URL part of the pattern can be whatever you want to use.
|
||||
|
|
|
@ -77,6 +77,10 @@ When deserializing data, we can either create a new instance, or update an exist
|
|||
serializer = CommentSerializer(data=data) # Create new instance
|
||||
serializer = CommentSerializer(comment, data=data) # Update `instance`
|
||||
|
||||
By default, serializers must be passed values for all required fields or they will throw validation errors. You can use the `partial` argument in order to allow partial updates.
|
||||
|
||||
serializer = CommentSerializer(comment, data={'content': u'foo bar'}, partial=True) # Update `instance` with partial data
|
||||
|
||||
## Validation
|
||||
|
||||
When deserializing data, you always need to call `is_valid()` before attempting to access the deserialized object. If any validation errors occur, the `.errors` and `.non_field_errors` properties will contain the resulting error messages.
|
||||
|
|
|
@ -19,6 +19,10 @@ Using the `APIView` class is pretty much the same as using a regular `View` clas
|
|||
|
||||
For example:
|
||||
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import authentication, permissions
|
||||
|
||||
class ListUsers(APIView):
|
||||
"""
|
||||
View to list all users in the system.
|
||||
|
|
|
@ -64,6 +64,11 @@ The following people have helped make REST framework great.
|
|||
* Eugene Mechanism - [mechanism]
|
||||
* Jonas Liljestrand - [jonlil]
|
||||
* Justin Davis - [irrelative]
|
||||
* Dustin Bachrach - [dbachrach]
|
||||
* Mark Shirley - [maspwr]
|
||||
* Olivier Aubert - [oaubert]
|
||||
* Yuri Prezument - [yprez]
|
||||
* Fabian Buechler - [fabianbuechler]
|
||||
|
||||
Many thanks to everyone who's contributed to the project.
|
||||
|
||||
|
@ -163,3 +168,8 @@ To contact the author directly:
|
|||
[mechanism]: https://github.com/mechanism
|
||||
[jonlil]: https://github.com/jonlil
|
||||
[irrelative]: https://github.com/irrelative
|
||||
[dbachrach]: https://github.com/dbachrach
|
||||
[maspwr]: https://github.com/maspwr
|
||||
[oaubert]: https://github.com/oaubert
|
||||
[yprez]: https://github.com/yprez
|
||||
[fabianbuechler]: https://github.com/fabianbuechler
|
||||
|
|
|
@ -4,8 +4,23 @@
|
|||
>
|
||||
> — Eric S. Raymond, [The Cathedral and the Bazaar][cite].
|
||||
|
||||
## Master
|
||||
## 2.1.6
|
||||
|
||||
**Date**: 23rd Nov 2012
|
||||
|
||||
* Bugfix: Unfix DjangoModelPermissions. (I am a doofus.)
|
||||
|
||||
## 2.1.5
|
||||
|
||||
**Date**: 23rd Nov 2012
|
||||
|
||||
* Bugfix: Fix DjangoModelPermissions.
|
||||
|
||||
## 2.1.4
|
||||
|
||||
**Date**: 22nd Nov 2012
|
||||
|
||||
* Support for partial updates with serializers.
|
||||
* Added `RegexField`.
|
||||
* Added `SerializerMethodField`.
|
||||
* Serializer performance improvements.
|
||||
|
|
|
@ -41,8 +41,8 @@ We don't need our `JSONResponse` class anymore, so go ahead and delete that. On
|
|||
from rest_framework import status
|
||||
from rest_framework.decorators import api_view
|
||||
from rest_framework.response import Response
|
||||
from snippet.models import Snippet
|
||||
from snippet.serializers import SnippetSerializer
|
||||
from snippets.models import Snippet
|
||||
from snippets.serializers import SnippetSerializer
|
||||
|
||||
|
||||
@api_view(['GET', 'POST'])
|
||||
|
@ -113,7 +113,7 @@ Now update the `urls.py` file slightly, to append a set of `format_suffix_patter
|
|||
from django.conf.urls import patterns, url
|
||||
from rest_framework.urlpatterns import format_suffix_patterns
|
||||
|
||||
urlpatterns = patterns('snippet.views',
|
||||
urlpatterns = patterns('snippets.views',
|
||||
url(r'^snippets/$', 'snippet_list'),
|
||||
url(r'^snippets/(?P<pk>[0-9]+)$', 'snippet_detail')
|
||||
)
|
||||
|
|
|
@ -6,8 +6,8 @@ We can also write our API views using class based views, rather than function ba
|
|||
|
||||
We'll start by rewriting the root view as a class based view. All this involves is a little bit of refactoring.
|
||||
|
||||
from snippet.models import Snippet
|
||||
from snippet.serializers import SnippetSerializer
|
||||
from snippets.models import Snippet
|
||||
from snippets.serializers import SnippetSerializer
|
||||
from django.http import Http404
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.response import Response
|
||||
|
@ -66,7 +66,7 @@ We'll also need to refactor our URLconf slightly now we're using class based vie
|
|||
|
||||
from django.conf.urls import patterns, url
|
||||
from rest_framework.urlpatterns import format_suffix_patterns
|
||||
from snippetpost import views
|
||||
from snippets import views
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^snippets/$', views.SnippetList.as_view()),
|
||||
|
@ -85,8 +85,8 @@ The create/retrieve/update/delete operations that we've been using so far are go
|
|||
|
||||
Let's take a look at how we can compose our views by using the mixin classes.
|
||||
|
||||
from snippet.models import Snippet
|
||||
from snippet.serializers import SnippetSerializer
|
||||
from snippets.models import Snippet
|
||||
from snippets.serializers import SnippetSerializer
|
||||
from rest_framework import mixins
|
||||
from rest_framework import generics
|
||||
|
||||
|
@ -128,8 +128,8 @@ Pretty similar. This time we're using the `SingleObjectBaseView` class to provi
|
|||
|
||||
Using the mixin classes we've rewritten the views to use slightly less code than before, but we can go one step further. REST framework provides a set of already mixed-in generic views that we can use.
|
||||
|
||||
from snippet.models import Snippet
|
||||
from snippet.serializers import SnippetSerializer
|
||||
from snippets.models import Snippet
|
||||
from snippets.serializers import SnippetSerializer
|
||||
from rest_framework import generics
|
||||
|
||||
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
markdown>=2.1.0
|
||||
PyYAML>=3.10
|
||||
-e git+https://github.com/alex/django-filter.git@0e4b3d703b31574922ab86fc78a86164aad0c1d0#egg=django-filter
|
||||
django-filter>=0.5.4
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
__version__ = '2.1.3'
|
||||
__version__ = '2.1.6'
|
||||
|
||||
VERSION = __version__ # synonym
|
||||
|
|
|
@ -18,7 +18,7 @@ class ObtainAuthToken(APIView):
|
|||
if serializer.is_valid():
|
||||
token, created = Token.objects.get_or_create(user=serializer.object['user'])
|
||||
return Response({'token': token.key})
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
return Response(serializer.errors, status=status.HTTP_401_UNAUTHORIZED)
|
||||
|
||||
|
||||
obtain_auth_token = ObtainAuthToken.as_view()
|
||||
|
|
|
@ -12,6 +12,7 @@ from django.core import validators
|
|||
from django.core.exceptions import ObjectDoesNotExist, ValidationError
|
||||
from django.core.urlresolvers import resolve, get_script_prefix
|
||||
from django.conf import settings
|
||||
from django import forms
|
||||
from django.forms import widgets
|
||||
from django.forms.models import ModelChoiceIterator
|
||||
from django.utils.encoding import is_protected_type
|
||||
|
@ -45,6 +46,7 @@ class Field(object):
|
|||
empty = ''
|
||||
type_name = None
|
||||
_use_files = None
|
||||
form_field_class = forms.CharField
|
||||
|
||||
def __init__(self, source=None):
|
||||
self.parent = None
|
||||
|
@ -64,6 +66,8 @@ class Field(object):
|
|||
self.parent = parent
|
||||
self.root = parent.root or parent
|
||||
self.context = self.root.context
|
||||
if self.root.partial:
|
||||
self.required = False
|
||||
|
||||
def field_from_native(self, data, files, field_name, into):
|
||||
"""
|
||||
|
@ -402,6 +406,7 @@ class PrimaryKeyRelatedField(RelatedField):
|
|||
Represents a to-one relationship as a pk value.
|
||||
"""
|
||||
default_read_only = False
|
||||
form_field_class = forms.ChoiceField
|
||||
|
||||
# TODO: Remove these field hacks...
|
||||
def prepare_value(self, obj):
|
||||
|
@ -448,6 +453,7 @@ class ManyPrimaryKeyRelatedField(ManyRelatedField):
|
|||
Represents a to-many relationship as a pk value.
|
||||
"""
|
||||
default_read_only = False
|
||||
form_field_class = forms.MultipleChoiceField
|
||||
|
||||
def prepare_value(self, obj):
|
||||
return self.to_native(obj.pk)
|
||||
|
@ -491,6 +497,7 @@ class ManyPrimaryKeyRelatedField(ManyRelatedField):
|
|||
|
||||
class SlugRelatedField(RelatedField):
|
||||
default_read_only = False
|
||||
form_field_class = forms.ChoiceField
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.slug_field = kwargs.pop('slug_field', None)
|
||||
|
@ -512,7 +519,7 @@ class SlugRelatedField(RelatedField):
|
|||
|
||||
|
||||
class ManySlugRelatedField(ManyRelatedMixin, SlugRelatedField):
|
||||
pass
|
||||
form_field_class = forms.MultipleChoiceField
|
||||
|
||||
|
||||
### Hyperlinked relationships
|
||||
|
@ -525,6 +532,7 @@ class HyperlinkedRelatedField(RelatedField):
|
|||
slug_field = 'slug'
|
||||
slug_url_kwarg = None # Defaults to same as `slug_field` unless overridden
|
||||
default_read_only = False
|
||||
form_field_class = forms.ChoiceField
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
try:
|
||||
|
@ -624,7 +632,7 @@ class ManyHyperlinkedRelatedField(ManyRelatedMixin, HyperlinkedRelatedField):
|
|||
"""
|
||||
Represents a to-many relationship, using hyperlinking.
|
||||
"""
|
||||
pass
|
||||
form_field_class = forms.MultipleChoiceField
|
||||
|
||||
|
||||
class HyperlinkedIdentityField(Field):
|
||||
|
@ -682,6 +690,7 @@ class HyperlinkedIdentityField(Field):
|
|||
|
||||
class BooleanField(WritableField):
|
||||
type_name = 'BooleanField'
|
||||
form_field_class = forms.BooleanField
|
||||
widget = widgets.CheckboxInput
|
||||
default_error_messages = {
|
||||
'invalid': _("'%s' value must be either True or False."),
|
||||
|
@ -703,6 +712,7 @@ class BooleanField(WritableField):
|
|||
|
||||
class CharField(WritableField):
|
||||
type_name = 'CharField'
|
||||
form_field_class = forms.CharField
|
||||
|
||||
def __init__(self, max_length=None, min_length=None, *args, **kwargs):
|
||||
self.max_length, self.min_length = max_length, min_length
|
||||
|
@ -747,6 +757,7 @@ class SlugField(CharField):
|
|||
|
||||
class ChoiceField(WritableField):
|
||||
type_name = 'ChoiceField'
|
||||
form_field_class = forms.ChoiceField
|
||||
widget = widgets.Select
|
||||
default_error_messages = {
|
||||
'invalid_choice': _('Select a valid choice. %(value)s is not one of the available choices.'),
|
||||
|
@ -793,6 +804,7 @@ class ChoiceField(WritableField):
|
|||
|
||||
class EmailField(CharField):
|
||||
type_name = 'EmailField'
|
||||
form_field_class = forms.EmailField
|
||||
|
||||
default_error_messages = {
|
||||
'invalid': _('Enter a valid e-mail address.'),
|
||||
|
@ -843,6 +855,8 @@ class RegexField(CharField):
|
|||
|
||||
class DateField(WritableField):
|
||||
type_name = 'DateField'
|
||||
widget = widgets.DateInput
|
||||
form_field_class = forms.DateField
|
||||
|
||||
default_error_messages = {
|
||||
'invalid': _("'%s' value has an invalid date format. It must be "
|
||||
|
@ -880,6 +894,8 @@ class DateField(WritableField):
|
|||
|
||||
class DateTimeField(WritableField):
|
||||
type_name = 'DateTimeField'
|
||||
widget = widgets.DateTimeInput
|
||||
form_field_class = forms.DateTimeField
|
||||
|
||||
default_error_messages = {
|
||||
'invalid': _("'%s' value has an invalid format. It must be in "
|
||||
|
@ -934,6 +950,7 @@ class DateTimeField(WritableField):
|
|||
|
||||
class IntegerField(WritableField):
|
||||
type_name = 'IntegerField'
|
||||
form_field_class = forms.IntegerField
|
||||
|
||||
default_error_messages = {
|
||||
'invalid': _('Enter a whole number.'),
|
||||
|
@ -963,6 +980,7 @@ class IntegerField(WritableField):
|
|||
|
||||
class FloatField(WritableField):
|
||||
type_name = 'FloatField'
|
||||
form_field_class = forms.FloatField
|
||||
|
||||
default_error_messages = {
|
||||
'invalid': _("'%s' value must be a float."),
|
||||
|
@ -982,6 +1000,7 @@ class FloatField(WritableField):
|
|||
class FileField(WritableField):
|
||||
_use_files = True
|
||||
type_name = 'FileField'
|
||||
form_field_class = forms.FileField
|
||||
widget = widgets.FileInput
|
||||
|
||||
default_error_messages = {
|
||||
|
@ -1024,6 +1043,7 @@ class FileField(WritableField):
|
|||
|
||||
class ImageField(FileField):
|
||||
_use_files = True
|
||||
form_field_class = forms.ImageField
|
||||
|
||||
default_error_messages = {
|
||||
'invalid_image': _("Upload a valid image. The file you uploaded was either not an image or a corrupted image."),
|
||||
|
|
|
@ -2,6 +2,7 @@ from django.http import Http404
|
|||
from rest_framework import exceptions
|
||||
from rest_framework.settings import api_settings
|
||||
from rest_framework.utils.mediatypes import order_by_precedence, media_type_matches
|
||||
from rest_framework.utils.mediatypes import _MediaType
|
||||
|
||||
|
||||
class BaseContentNegotiation(object):
|
||||
|
@ -48,7 +49,8 @@ class DefaultContentNegotiation(BaseContentNegotiation):
|
|||
for media_type in media_type_set:
|
||||
if media_type_matches(renderer.media_type, media_type):
|
||||
# Return the most specific media type as accepted.
|
||||
if len(renderer.media_type) > len(media_type):
|
||||
if (_MediaType(renderer.media_type).precedence >
|
||||
_MediaType(media_type).precedence):
|
||||
# Eg client requests '*/*'
|
||||
# Accepted media type is 'application/json'
|
||||
return renderer, renderer.media_type
|
||||
|
|
|
@ -308,26 +308,6 @@ class BrowsableAPIRenderer(BaseRenderer):
|
|||
return True
|
||||
|
||||
def serializer_to_form_fields(self, serializer):
|
||||
field_mapping = {
|
||||
serializers.FloatField: forms.FloatField,
|
||||
serializers.IntegerField: forms.IntegerField,
|
||||
serializers.DateTimeField: forms.DateTimeField,
|
||||
serializers.DateField: forms.DateField,
|
||||
serializers.EmailField: forms.EmailField,
|
||||
serializers.RegexField: forms.RegexField,
|
||||
serializers.CharField: forms.CharField,
|
||||
serializers.ChoiceField: forms.ChoiceField,
|
||||
serializers.BooleanField: forms.BooleanField,
|
||||
serializers.PrimaryKeyRelatedField: forms.ChoiceField,
|
||||
serializers.ManyPrimaryKeyRelatedField: forms.MultipleChoiceField,
|
||||
serializers.SlugRelatedField: forms.ChoiceField,
|
||||
serializers.ManySlugRelatedField: forms.MultipleChoiceField,
|
||||
serializers.HyperlinkedRelatedField: forms.ChoiceField,
|
||||
serializers.ManyHyperlinkedRelatedField: forms.MultipleChoiceField,
|
||||
serializers.FileField: forms.FileField,
|
||||
serializers.ImageField: forms.ImageField,
|
||||
}
|
||||
|
||||
fields = {}
|
||||
for k, v in serializer.get_fields().items():
|
||||
if getattr(v, 'read_only', True):
|
||||
|
@ -351,13 +331,7 @@ class BrowsableAPIRenderer(BaseRenderer):
|
|||
|
||||
kwargs['label'] = k
|
||||
|
||||
try:
|
||||
fields[k] = field_mapping[v.__class__](**kwargs)
|
||||
except KeyError:
|
||||
if getattr(v, 'choices', None) is not None:
|
||||
fields[k] = forms.ChoiceField(**kwargs)
|
||||
else:
|
||||
fields[k] = forms.CharField(**kwargs)
|
||||
fields[k] = v.form_field_class(**kwargs)
|
||||
return fields
|
||||
|
||||
def get_form(self, view, method, request):
|
||||
|
|
|
@ -62,7 +62,7 @@ def _get_declared_fields(bases, attrs):
|
|||
|
||||
# If this class is subclassing another Serializer, add that Serializer's
|
||||
# fields. Note that we loop over the bases in *reverse*. This is necessary
|
||||
# in order to the correct order of fields.
|
||||
# in order to maintain the correct order of fields.
|
||||
for base in bases[::-1]:
|
||||
if hasattr(base, 'base_fields'):
|
||||
fields = list(base.base_fields.items()) + fields
|
||||
|
@ -93,19 +93,19 @@ class BaseSerializer(Field):
|
|||
_options_class = SerializerOptions
|
||||
_dict_class = SortedDictWithMetadata # Set to unsorted dict for backwards compatibility with unsorted implementations.
|
||||
|
||||
def __init__(self, instance=None, data=None, files=None, context=None, **kwargs):
|
||||
def __init__(self, instance=None, data=None, files=None, context=None, partial=False, **kwargs):
|
||||
super(BaseSerializer, self).__init__(**kwargs)
|
||||
self.opts = self._options_class(self.Meta)
|
||||
self.fields = copy.deepcopy(self.base_fields)
|
||||
self.parent = None
|
||||
self.root = None
|
||||
self.partial = partial
|
||||
|
||||
self.context = context or {}
|
||||
|
||||
self.init_data = data
|
||||
self.init_files = files
|
||||
self.object = instance
|
||||
self.default_fields = self.get_default_fields()
|
||||
self.fields = self.get_fields()
|
||||
|
||||
self._data = None
|
||||
self._files = None
|
||||
|
@ -130,13 +130,15 @@ class BaseSerializer(Field):
|
|||
ret = SortedDict()
|
||||
|
||||
# Get the explicitly declared fields
|
||||
for key, field in self.fields.items():
|
||||
base_fields = copy.deepcopy(self.base_fields)
|
||||
for key, field in base_fields.items():
|
||||
ret[key] = field
|
||||
# Set up the field
|
||||
field.initialize(parent=self, field_name=key)
|
||||
|
||||
# Add in the default fields
|
||||
for key, val in self.default_fields.items():
|
||||
default_fields = self.get_default_fields()
|
||||
for key, val in default_fields.items():
|
||||
if key not in ret:
|
||||
ret[key] = val
|
||||
|
||||
|
@ -183,8 +185,7 @@ class BaseSerializer(Field):
|
|||
ret = self._dict_class()
|
||||
ret.fields = {}
|
||||
|
||||
fields = self.get_fields()
|
||||
for field_name, field in fields.items():
|
||||
for field_name, field in self.fields.items():
|
||||
key = self.get_field_key(field_name)
|
||||
value = field.field_to_native(obj, field_name)
|
||||
ret[key] = value
|
||||
|
@ -196,9 +197,8 @@ class BaseSerializer(Field):
|
|||
Core of deserialization, together with `restore_object`.
|
||||
Converts a dictionary of data into a dictionary of deserialized fields.
|
||||
"""
|
||||
fields = self.get_fields()
|
||||
reverted_data = {}
|
||||
for field_name, field in fields.items():
|
||||
for field_name, field in self.fields.items():
|
||||
try:
|
||||
field.field_from_native(data, files, field_name, reverted_data)
|
||||
except ValidationError as err:
|
||||
|
@ -210,10 +210,7 @@ class BaseSerializer(Field):
|
|||
"""
|
||||
Run `validate_<fieldname>()` and `validate()` methods on the serializer
|
||||
"""
|
||||
# TODO: refactor this so we're not determining the fields again
|
||||
fields = self.get_fields()
|
||||
|
||||
for field_name, field in fields.items():
|
||||
for field_name, field in self.fields.items():
|
||||
try:
|
||||
validate_method = getattr(self, 'validate_%s' % field_name, None)
|
||||
if validate_method:
|
||||
|
|
|
@ -167,14 +167,14 @@ class TokenAuthTests(TestCase):
|
|||
client = Client(enforce_csrf_checks=True)
|
||||
response = client.post('/auth-token/login/',
|
||||
json.dumps({'username': self.username, 'password': "badpass"}), 'application/json')
|
||||
self.assertEqual(response.status_code, 400)
|
||||
self.assertEqual(response.status_code, 401)
|
||||
|
||||
def test_token_login_json_missing_fields(self):
|
||||
"""Ensure token login view using JSON POST fails if missing fields."""
|
||||
client = Client(enforce_csrf_checks=True)
|
||||
response = client.post('/auth-token/login/',
|
||||
json.dumps({'username': self.username}), 'application/json')
|
||||
self.assertEqual(response.status_code, 400)
|
||||
self.assertEqual(response.status_code, 401)
|
||||
|
||||
def test_token_login_form(self):
|
||||
"""Ensure token login view using form POST works."""
|
||||
|
|
|
@ -117,6 +117,18 @@ class BasicTests(TestCase):
|
|||
self.assertTrue(serializer.object is expected)
|
||||
self.assertEquals(serializer.data['sub_comment'], 'And Merry Christmas!')
|
||||
|
||||
def test_partial_update(self):
|
||||
msg = 'Merry New Year!'
|
||||
partial_data = {'content': msg}
|
||||
serializer = CommentSerializer(self.comment, data=partial_data)
|
||||
self.assertEquals(serializer.is_valid(), False)
|
||||
serializer = CommentSerializer(self.comment, data=partial_data, partial=True)
|
||||
expected = self.comment
|
||||
self.assertEqual(serializer.is_valid(), True)
|
||||
self.assertEquals(serializer.object, expected)
|
||||
self.assertTrue(serializer.object is expected)
|
||||
self.assertEquals(serializer.data['content'], msg)
|
||||
|
||||
def test_model_fields_as_expected(self):
|
||||
"""
|
||||
Make sure that the fields returned are the same as defined
|
||||
|
|
Loading…
Reference in New Issue
Block a user