diff --git a/README.md b/README.md index 394d7fcf4..c86bb65ff 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # Django REST framework [![build-status-image]][travis] +[![pypi-version]][pypi] **Awesome web-browseable Web APIs.** @@ -181,6 +182,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. [build-status-image]: https://secure.travis-ci.org/tomchristie/django-rest-framework.png?branch=master [travis]: http://travis-ci.org/tomchristie/django-rest-framework?branch=master +[pypi-version]: https://pypip.in/version/djangorestframework/badge.svg +[pypi]: https://pypi.python.org/pypi/djangorestframework [twitter]: https://twitter.com/_tomchristie [group]: https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework [0.4]: https://github.com/tomchristie/django-rest-framework/tree/0.4.X diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md index d3da0607e..885fc183e 100644 --- a/docs/topics/3.0-announcement.md +++ b/docs/topics/3.0-announcement.md @@ -506,9 +506,9 @@ We now use the following: REST framework now has more explicit and clear control over validating empty values for fields. -Previously the meaning of the `required=False` keyword argument was underspecified. In practice its use meant that a field could either be not included in the input, or it could be included, but be `None`. +Previously the meaning of the `required=False` keyword argument was underspecified. In practice its use meant that a field could either be not included in the input, or it could be included, but be `None` or the empty string. -We now have a better separation, with separate `required` and `allow_none` arguments. +We now have a better separation, with separate `required`, `allow_none` and `allow_blank` arguments. The following set of arguments are used to control validation of empty values: @@ -519,7 +519,7 @@ The following set of arguments are used to control validation of empty values: Typically you'll want to use `required=False` if the corresponding model field has a default value, and additionally set either `allow_none=True` or `allow_blank=True` if required. -The `default` argument is there if you need it, but you'll more typically want defaults to be set on model fields, rather than serializer fields. +The `default` argument is also available and always implies that the field is not required to be in the input. It is unnecessary to use the `required` argument when a default is specified, and doing so will result in an error. #### Coercing output types. diff --git a/rest_framework/relations.py b/rest_framework/relations.py index 48ddf41e0..79c8057b6 100644 --- a/rest_framework/relations.py +++ b/rest_framework/relations.py @@ -10,9 +10,17 @@ from django.utils.translation import ugettext_lazy as _ class PKOnlyObject(object): + """ + This is a mock object, used for when we only need the pk of the object + instance, but still want to return an object with a .pk attribute, + in order to keep the same interface as a regular model instance. + """ def __init__(self, pk): self.pk = pk + +# We assume that 'validators' are intended for the child serializer, +# rather than the parent serializer. MANY_RELATION_KWARGS = ( 'read_only', 'write_only', 'required', 'default', 'initial', 'source', 'label', 'help_text', 'style', 'error_messages' @@ -34,15 +42,19 @@ class RelatedField(Field): def __new__(cls, *args, **kwargs): # We override this method in order to automagically create - # `ManyRelation` classes instead when `many=True` is set. + # `ManyRelatedField` classes instead when `many=True` is set. if kwargs.pop('many', False): - list_kwargs = {'child_relation': cls(*args, **kwargs)} - for key in kwargs.keys(): - if key in MANY_RELATION_KWARGS: - list_kwargs[key] = kwargs[key] - return ManyRelation(**list_kwargs) + return cls.many_init(*args, **kwargs) return super(RelatedField, cls).__new__(cls, *args, **kwargs) + @classmethod + def many_init(cls, *args, **kwargs): + list_kwargs = {'child_relation': cls(*args, **kwargs)} + for key in kwargs.keys(): + if key in MANY_RELATION_KWARGS: + list_kwargs[key] = kwargs[key] + return ManyRelatedField(**list_kwargs) + def run_validation(self, data=empty): # We force empty strings to None values for relational fields. if data == '': @@ -286,12 +298,12 @@ class SlugRelatedField(RelatedField): return getattr(obj, self.slug_field) -class ManyRelation(Field): +class ManyRelatedField(Field): """ Relationships with `many=True` transparently get coerced into instead being - a ManyRelation with a child relationship. + a ManyRelatedField with a child relationship. - The `ManyRelation` class is responsible for handling iterating through + The `ManyRelatedField` class is responsible for handling iterating through the values and passing each one to the child relationship. You shouldn't need to be using this class directly yourself. @@ -302,7 +314,7 @@ class ManyRelation(Field): def __init__(self, child_relation=None, *args, **kwargs): self.child_relation = child_relation assert child_relation is not None, '`child_relation` is a required argument.' - super(ManyRelation, self).__init__(*args, **kwargs) + super(ManyRelatedField, self).__init__(*args, **kwargs) self.child_relation.bind(field_name='', parent=self) def get_value(self, dictionary): diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index 9b9d2ced2..f62a34e91 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -387,7 +387,10 @@ class HTMLFormRenderer(BaseRenderer): serializers.MultipleChoiceField: { 'base_template': 'select_multiple.html', # Also valid: 'checkbox_multiple.html' }, - serializers.ManyRelation: { + serializers.RelatedField: { + 'base_template': 'select.html', # Also valid: 'radio.html' + }, + serializers.ManyRelatedField: { 'base_template': 'select_multiple.html', # Also valid: 'checkbox_multiple.html' }, serializers.Serializer: { diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index d83367f40..e7e93f380 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -46,6 +46,9 @@ import warnings from rest_framework.relations import * # NOQA from rest_framework.fields import * # NOQA + +# We assume that 'validators' are intended for the child serializer, +# rather than the parent serializer. LIST_SERIALIZER_KWARGS = ( 'read_only', 'write_only', 'required', 'default', 'initial', 'source', 'label', 'help_text', 'style', 'error_messages', @@ -73,13 +76,25 @@ class BaseSerializer(Field): # We override this method in order to automagically create # `ListSerializer` classes instead when `many=True` is set. if kwargs.pop('many', False): - list_kwargs = {'child': cls(*args, **kwargs)} - for key in kwargs.keys(): - if key in LIST_SERIALIZER_KWARGS: - list_kwargs[key] = kwargs[key] - return ListSerializer(*args, **list_kwargs) + return cls.many_init(*args, **kwargs) return super(BaseSerializer, cls).__new__(cls, *args, **kwargs) + @classmethod + def many_init(cls, *args, **kwargs): + """ + This method implements the creation of a `ListSerializer` parent + class when `many=True` is used. You can customize it if you need to + control which keyword arguments are passed to the parent, and + which are passed to the child. + """ + child_serializer = cls(*args, **kwargs) + list_kwargs = {'child': child_serializer} + list_kwargs.update(dict([ + (key, value) for key, value in kwargs.items() + if key in LIST_SERIALIZER_KWARGS + ])) + return ListSerializer(*args, **list_kwargs) + def to_internal_value(self, data): raise NotImplementedError('`to_internal_value()` must be implemented.') @@ -230,18 +245,18 @@ class Serializer(BaseSerializer): def get_initial(self): if self._initial_data is not None: - return ReturnDict([ + return OrderedDict([ (field_name, field.get_value(self._initial_data)) for field_name, field in self.fields.items() if field.get_value(self._initial_data) is not empty and not field.read_only - ], serializer=self) + ]) - return ReturnDict([ + return OrderedDict([ (field.field_name, field.get_initial()) for field in self.fields.values() if not field.read_only - ], serializer=self) + ]) def get_value(self, dictionary): # We override the default field access in order to support @@ -304,8 +319,8 @@ class Serializer(BaseSerializer): """ Dict of native values <- Dict of primitive datatypes. """ - ret = {} - errors = ReturnDict(serializer=self) + ret = OrderedDict() + errors = OrderedDict() fields = [ field for field in self.fields.values() if (not field.read_only) or (field.default is not empty) @@ -334,7 +349,7 @@ class Serializer(BaseSerializer): """ Object instance -> Dict of primitive datatypes. """ - ret = ReturnDict(serializer=self) + ret = OrderedDict() fields = [field for field in self.fields.values() if not field.write_only] for field in fields: @@ -373,6 +388,19 @@ class Serializer(BaseSerializer): return NestedBoundField(field, value, error) return BoundField(field, value, error) + # Include a backlink to the serializer class on return objects. + # Allows renderers such as HTMLFormRenderer to get the full field info. + + @property + def data(self): + ret = super(Serializer, self).data + return ReturnDict(ret, serializer=self) + + @property + def errors(self): + ret = super(Serializer, self).errors + return ReturnDict(ret, serializer=self) + # There's some replication of `ListField` here, # but that's probably better than obfuscating the call hierarchy. @@ -395,7 +423,7 @@ class ListSerializer(BaseSerializer): def get_initial(self): if self._initial_data is not None: return self.to_representation(self._initial_data) - return ReturnList(serializer=self) + return [] def get_value(self, dictionary): """ @@ -423,7 +451,7 @@ class ListSerializer(BaseSerializer): }) ret = [] - errors = ReturnList(serializer=self) + errors = [] for item in data: try: @@ -444,37 +472,64 @@ class ListSerializer(BaseSerializer): List of object instances -> List of dicts of primitive datatypes. """ iterable = data.all() if (hasattr(data, 'all')) else data - return ReturnList( - [self.child.to_representation(item) for item in iterable], - serializer=self + return [ + self.child.to_representation(item) for item in iterable + ] + + def update(self, instance, validated_data): + raise NotImplementedError( + "Serializers with many=True do not support multiple update by " + "default, only multiple create. For updates it is unclear how to " + "deal with insertions and deletions. If you need to support " + "multiple update, use a `ListSerializer` class and override " + "`.update()` so you can specify the behavior exactly." ) + def create(self, validated_data): + return [ + self.child.create(attrs) for attrs in validated_data + ] + def save(self, **kwargs): """ Save and return a list of object instances. """ - assert self.instance is None, ( - "Serializers do not support multiple update by default, only " - "multiple create. For updates it is unclear how to deal with " - "insertions and deletions. If you need to support multiple update, " - "use a `ListSerializer` class and override `.save()` so you can " - "specify the behavior exactly." - ) - validated_data = [ dict(list(attrs.items()) + list(kwargs.items())) for attrs in self.validated_data ] - self.instance = [ - self.child.create(attrs) for attrs in validated_data - ] + if self.instance is not None: + self.instance = self.update(self.instance, validated_data) + assert self.instance is not None, ( + '`update()` did not return an object instance.' + ) + else: + self.instance = self.create(validated_data) + assert self.instance is not None, ( + '`create()` did not return an object instance.' + ) return self.instance def __repr__(self): return representation.list_repr(self, indent=1) + # Include a backlink to the serializer class on return objects. + # Allows renderers such as HTMLFormRenderer to get the full field info. + + @property + def data(self): + ret = super(ListSerializer, self).data + return ReturnList(ret, serializer=self) + + @property + def errors(self): + ret = super(ListSerializer, self).errors + if isinstance(ret, dict): + return ReturnDict(ret, serializer=self) + return ReturnList(ret, serializer=self) + # ModelSerializer & HyperlinkedModelSerializer # -------------------------------------------- diff --git a/rest_framework/utils/field_mapping.py b/rest_framework/utils/field_mapping.py index 246390853..9c187176d 100644 --- a/rest_framework/utils/field_mapping.py +++ b/rest_framework/utils/field_mapping.py @@ -88,7 +88,7 @@ def get_field_kwargs(field_name, model_field): kwargs['read_only'] = True return kwargs - if model_field.has_default(): + if model_field.has_default() or model_field.blank or model_field.null: kwargs['required'] = False if model_field.flatchoices: @@ -215,7 +215,7 @@ def get_relation_kwargs(field_name, relation_info): # If this field is read-only, then return early. # No further keyword arguments are valid. return kwargs - if model_field.has_default(): + if model_field.has_default() or model_field.null: kwargs['required'] = False if model_field.null: kwargs['allow_null'] = True diff --git a/tests/accounts/__init__.py b/tests/accounts/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/accounts/models.py b/tests/accounts/models.py deleted file mode 100644 index 3bf4a0c3c..000000000 --- a/tests/accounts/models.py +++ /dev/null @@ -1,8 +0,0 @@ -from django.db import models - -from tests.users.models import User - - -class Account(models.Model): - owner = models.ForeignKey(User, related_name='accounts_owned') - admins = models.ManyToManyField(User, blank=True, null=True, related_name='accounts_administered') diff --git a/tests/accounts/serializers.py b/tests/accounts/serializers.py deleted file mode 100644 index 57a91b92c..000000000 --- a/tests/accounts/serializers.py +++ /dev/null @@ -1,11 +0,0 @@ -from rest_framework import serializers - -from tests.accounts.models import Account -from tests.users.serializers import UserSerializer - - -class AccountSerializer(serializers.ModelSerializer): - admins = UserSerializer(many=True) - - class Meta: - model = Account diff --git a/tests/conftest.py b/tests/conftest.py index 4b33e19c1..31142eaf9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -33,9 +33,6 @@ def pytest_configure(): 'rest_framework', 'rest_framework.authtoken', 'tests', - 'tests.accounts', - 'tests.records', - 'tests.users', ), PASSWORD_HASHERS=( 'django.contrib.auth.hashers.SHA1PasswordHasher', diff --git a/tests/put_as_create_workspace.txt b/tests/put_as_create_workspace.txt deleted file mode 100644 index 6bc5218eb..000000000 --- a/tests/put_as_create_workspace.txt +++ /dev/null @@ -1,33 +0,0 @@ -# From test_validation... - -class TestPreSaveValidationExclusions(TestCase): - def test_pre_save_validation_exclusions(self): - """ - Somewhat weird test case to ensure that we don't perform model - validation on read only fields. - """ - obj = ValidationModel.objects.create(blank_validated_field='') - request = factory.put('/', {}, format='json') - view = UpdateValidationModel().as_view() - response = view(request, pk=obj.pk).render() - self.assertEqual(response.status_code, status.HTTP_200_OK) - - -# From test_permissions... - -class ModelPermissionsIntegrationTests(TestCase): - def setUp(...): - ... - - def test_has_put_as_create_permissions(self): - # User only has update permissions - should be able to update an entity. - request = factory.put('/1', {'text': 'foobar'}, format='json', - HTTP_AUTHORIZATION=self.updateonly_credentials) - response = instance_view(request, pk='1') - self.assertEqual(response.status_code, status.HTTP_200_OK) - - # But if PUTing to a new entity, permission should be denied. - request = factory.put('/2', {'text': 'foobar'}, format='json', - HTTP_AUTHORIZATION=self.updateonly_credentials) - response = instance_view(request, pk='2') - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) diff --git a/tests/records/__init__.py b/tests/records/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/records/models.py b/tests/records/models.py deleted file mode 100644 index 769548074..000000000 --- a/tests/records/models.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.db import models - - -class Record(models.Model): - account = models.ForeignKey('accounts.Account', blank=True, null=True) - owner = models.ForeignKey('users.User', blank=True, null=True) diff --git a/tests/settings.py b/tests/settings.py deleted file mode 100644 index 91c9ed09e..000000000 --- a/tests/settings.py +++ /dev/null @@ -1,165 +0,0 @@ -# Django settings for testproject project. - -DEBUG = True -TEMPLATE_DEBUG = DEBUG -DEBUG_PROPAGATE_EXCEPTIONS = True - -ALLOWED_HOSTS = ['*'] - -ADMINS = ( - # ('Your Name', 'your_email@domain.com'), -) - -MANAGERS = ADMINS - -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. - 'NAME': 'sqlite.db', # Or path to database file if using sqlite3. - 'USER': '', # Not used with sqlite3. - 'PASSWORD': '', # Not used with sqlite3. - 'HOST': '', # Set to empty string for localhost. Not used with sqlite3. - 'PORT': '', # Set to empty string for default. Not used with sqlite3. - } -} - -CACHES = { - 'default': { - 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', - } -} - -# Local time zone for this installation. Choices can be found here: -# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name -# although not all choices may be available on all operating systems. -# On Unix systems, a value of None will cause Django to use the same -# timezone as the operating system. -# If running in a Windows environment this must be set to the same as your -# system time zone. -TIME_ZONE = 'Europe/London' - -# Language code for this installation. All choices can be found here: -# http://www.i18nguy.com/unicode/language-identifiers.html -LANGUAGE_CODE = 'en-uk' - -SITE_ID = 1 - -# If you set this to False, Django will make some optimizations so as not -# to load the internationalization machinery. -USE_I18N = True - -# If you set this to False, Django will not format dates, numbers and -# calendars according to the current locale -USE_L10N = True - -# Absolute filesystem path to the directory that will hold user-uploaded files. -# Example: "/home/media/media.lawrence.com/" -MEDIA_ROOT = '' - -# URL that handles the media served from MEDIA_ROOT. Make sure to use a -# trailing slash if there is a path component (optional in other cases). -# Examples: "http://media.lawrence.com", "http://example.com/media/" -MEDIA_URL = '' - -# Make this unique, and don't share it with anybody. -SECRET_KEY = 'u@x-aj9(hoh#rb-^ymf#g2jx_hp0vj7u5#b@ag1n^seu9e!%cy' - -# List of callables that know how to import templates from various sources. -TEMPLATE_LOADERS = ( - 'django.template.loaders.filesystem.Loader', - 'django.template.loaders.app_directories.Loader', -) - -MIDDLEWARE_CLASSES = ( - 'django.middleware.common.CommonMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', -) - -ROOT_URLCONF = 'tests.urls' - -TEMPLATE_DIRS = ( - # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". - # Always use forward slashes, even on Windows. - # Don't forget to use absolute paths, not relative paths. -) - -INSTALLED_APPS = ( - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.sites', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'rest_framework', - 'rest_framework.authtoken', - 'tests', - 'tests.accounts', - 'tests.records', - 'tests.users', -) - -# OAuth is optional and won't work if there is no oauth_provider & oauth2 -try: - import oauth_provider # NOQA - import oauth2 # NOQA -except ImportError: - pass -else: - INSTALLED_APPS += ( - 'oauth_provider', - ) - -try: - import provider # NOQA -except ImportError: - pass -else: - INSTALLED_APPS += ( - 'provider', - 'provider.oauth2', - ) - -# guardian is optional -try: - import guardian # NOQA -except ImportError: - pass -else: - ANONYMOUS_USER_ID = -1 - AUTHENTICATION_BACKENDS = ( - 'django.contrib.auth.backends.ModelBackend', # default - 'guardian.backends.ObjectPermissionBackend', - ) - INSTALLED_APPS += ( - 'guardian', - ) - -STATIC_URL = '/static/' - -PASSWORD_HASHERS = ( - 'django.contrib.auth.hashers.SHA1PasswordHasher', - 'django.contrib.auth.hashers.PBKDF2PasswordHasher', - 'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher', - 'django.contrib.auth.hashers.BCryptPasswordHasher', - 'django.contrib.auth.hashers.MD5PasswordHasher', - 'django.contrib.auth.hashers.CryptPasswordHasher', -) - -AUTH_USER_MODEL = 'auth.User' - -import django - -if django.VERSION < (1, 3): - INSTALLED_APPS += ('staticfiles',) - - -# If we're running on the Jenkins server we want to archive the coverage reports as XML. -import os -if os.environ.get('HUDSON_URL', None): - TEST_RUNNER = 'xmlrunner.extra.djangotestrunner.XMLTestRunner' - TEST_OUTPUT_VERBOSE = True - TEST_OUTPUT_DESCRIPTIONS = True - TEST_OUTPUT_DIR = 'xmlrunner' diff --git a/tests/test_model_serializer.py b/tests/test_model_serializer.py index b8b621be8..3aec0da0c 100644 --- a/tests/test_model_serializer.py +++ b/tests/test_model_serializer.py @@ -90,7 +90,7 @@ class TestRegularFieldMappings(TestCase): email_field = EmailField(max_length=100) float_field = FloatField() integer_field = IntegerField() - null_boolean_field = NullBooleanField() + null_boolean_field = NullBooleanField(required=False) positive_integer_field = IntegerField() positive_small_integer_field = IntegerField() slug_field = SlugField(max_length=100) @@ -112,8 +112,8 @@ class TestRegularFieldMappings(TestCase): id = IntegerField(label='ID', read_only=True) value_limit_field = IntegerField(max_value=10, min_value=1) length_limit_field = CharField(max_length=12, min_length=3) - blank_field = CharField(allow_blank=True, max_length=10) - null_field = IntegerField(allow_null=True) + blank_field = CharField(allow_blank=True, max_length=10, required=False) + null_field = IntegerField(allow_null=True, required=False) default_field = IntegerField(required=False) descriptive_field = IntegerField(help_text='Some help text', label='A label') choices_field = ChoiceField(choices=[('red', 'Red'), ('blue', 'Blue'), ('green', 'Green')]) diff --git a/tests/test_permissions.py b/tests/test_permissions.py index ac398f80d..97bac33db 100644 --- a/tests/test_permissions.py +++ b/tests/test_permissions.py @@ -95,59 +95,59 @@ class ModelPermissionsIntegrationTests(TestCase): response = instance_view(request, pk=1) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - # def test_options_permitted(self): - # request = factory.options( - # '/', - # HTTP_AUTHORIZATION=self.permitted_credentials - # ) - # response = root_view(request, pk='1') - # self.assertEqual(response.status_code, status.HTTP_200_OK) - # self.assertIn('actions', response.data) - # self.assertEqual(list(response.data['actions'].keys()), ['POST']) + def test_options_permitted(self): + request = factory.options( + '/', + HTTP_AUTHORIZATION=self.permitted_credentials + ) + response = root_view(request, pk='1') + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertIn('actions', response.data) + self.assertEqual(list(response.data['actions'].keys()), ['POST']) - # request = factory.options( - # '/1', - # HTTP_AUTHORIZATION=self.permitted_credentials - # ) - # response = instance_view(request, pk='1') - # self.assertEqual(response.status_code, status.HTTP_200_OK) - # self.assertIn('actions', response.data) - # self.assertEqual(list(response.data['actions'].keys()), ['PUT']) + request = factory.options( + '/1', + HTTP_AUTHORIZATION=self.permitted_credentials + ) + response = instance_view(request, pk='1') + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertIn('actions', response.data) + self.assertEqual(list(response.data['actions'].keys()), ['PUT']) - # def test_options_disallowed(self): - # request = factory.options( - # '/', - # HTTP_AUTHORIZATION=self.disallowed_credentials - # ) - # response = root_view(request, pk='1') - # self.assertEqual(response.status_code, status.HTTP_200_OK) - # self.assertNotIn('actions', response.data) + def test_options_disallowed(self): + request = factory.options( + '/', + HTTP_AUTHORIZATION=self.disallowed_credentials + ) + response = root_view(request, pk='1') + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertNotIn('actions', response.data) - # request = factory.options( - # '/1', - # HTTP_AUTHORIZATION=self.disallowed_credentials - # ) - # response = instance_view(request, pk='1') - # self.assertEqual(response.status_code, status.HTTP_200_OK) - # self.assertNotIn('actions', response.data) + request = factory.options( + '/1', + HTTP_AUTHORIZATION=self.disallowed_credentials + ) + response = instance_view(request, pk='1') + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertNotIn('actions', response.data) - # def test_options_updateonly(self): - # request = factory.options( - # '/', - # HTTP_AUTHORIZATION=self.updateonly_credentials - # ) - # response = root_view(request, pk='1') - # self.assertEqual(response.status_code, status.HTTP_200_OK) - # self.assertNotIn('actions', response.data) + def test_options_updateonly(self): + request = factory.options( + '/', + HTTP_AUTHORIZATION=self.updateonly_credentials + ) + response = root_view(request, pk='1') + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertNotIn('actions', response.data) - # request = factory.options( - # '/1', - # HTTP_AUTHORIZATION=self.updateonly_credentials - # ) - # response = instance_view(request, pk='1') - # self.assertEqual(response.status_code, status.HTTP_200_OK) - # self.assertIn('actions', response.data) - # self.assertEqual(list(response.data['actions'].keys()), ['PUT']) + request = factory.options( + '/1', + HTTP_AUTHORIZATION=self.updateonly_credentials + ) + response = instance_view(request, pk='1') + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertIn('actions', response.data) + self.assertEqual(list(response.data['actions'].keys()), ['PUT']) class BasicPermModel(models.Model): diff --git a/tests/test_serializer_import.py b/tests/test_serializer_import.py deleted file mode 100644 index d029c3c55..000000000 --- a/tests/test_serializer_import.py +++ /dev/null @@ -1,19 +0,0 @@ -# from django.test import TestCase - -# from rest_framework import serializers -# from tests.accounts.serializers import AccountSerializer - - -# class ImportingModelSerializerTests(TestCase): -# """ -# In some situations like, GH #1225, it is possible, especially in -# testing, to import a serializer who's related models have not yet -# been resolved by Django. `AccountSerializer` is an example of such -# a serializer (imported at the top of this file). -# """ -# def test_import_model_serializer(self): -# """ -# The serializer at the top of this file should have been -# imported successfully, and we should be able to instantiate it. -# """ -# self.assertIsInstance(AccountSerializer(), serializers.ModelSerializer) diff --git a/tests/users/__init__.py b/tests/users/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/users/models.py b/tests/users/models.py deleted file mode 100644 index 128bac90b..000000000 --- a/tests/users/models.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.db import models - - -class User(models.Model): - account = models.ForeignKey('accounts.Account', blank=True, null=True, related_name='users') - active_record = models.ForeignKey('records.Record', blank=True, null=True) diff --git a/tests/users/serializers.py b/tests/users/serializers.py deleted file mode 100644 index 4893ddb34..000000000 --- a/tests/users/serializers.py +++ /dev/null @@ -1,8 +0,0 @@ -from rest_framework import serializers - -from tests.users.models import User - - -class UserSerializer(serializers.ModelSerializer): - class Meta: - model = User