diff --git a/.travis.yml b/.travis.yml
index e8561a7ac..67404d750 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -31,4 +31,4 @@ script:
after_success:
- pip install codecov
- - codecov
+ - codecov -e TOX_ENV
diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md
index f8be0c1b9..377fe983f 100644
--- a/docs/api-guide/fields.md
+++ b/docs/api-guide/fields.md
@@ -57,7 +57,7 @@ Note that setting a `default` value implies that the field is not required. Incl
### `source`
-The name of the attribute that will be used to populate the field. May be a method that only takes a `self` argument, such as `URLField('get_absolute_url')`, or may use dotted notation to traverse attributes, such as `EmailField(source='user.email')`.
+The name of the attribute that will be used to populate the field. May be a method that only takes a `self` argument, such as `URLField(source='get_absolute_url')`, or may use dotted notation to traverse attributes, such as `EmailField(source='user.email')`.
The value `source='*'` has a special meaning, and is used to indicate that the entire object should be passed through to the field. This can be useful for creating nested representations, or for fields which require access to the complete object in order to determine the output representation.
diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md
index 63189b966..95703033a 100644
--- a/docs/api-guide/serializers.md
+++ b/docs/api-guide/serializers.md
@@ -287,7 +287,7 @@ Similarly if a nested representation should be a list of items, you should pass
## Writable nested representations
-When dealing with nested representations that support deserializing the data, an errors with nested objects will be nested under the field name of the nested object.
+When dealing with nested representations that support deserializing the data, any errors with nested objects will be nested under the field name of the nested object.
serializer = CommentSerializer(data={'user': {'email': 'foobar', 'username': 'doe'}, 'content': 'baz'})
serializer.is_valid()
@@ -356,7 +356,7 @@ It is possible that a third party package, providing automatic support some kind
#### Handling saving related instances in model manager classes
-An alternative to saving multiple related instances in the serializer is to write custom model manager classes handle creating the correct instances.
+An alternative to saving multiple related instances in the serializer is to write custom model manager classes that handle creating the correct instances.
For example, suppose we wanted to ensure that `User` instances and `Profile` instances are always created together as a pair. We might write a custom manager class that looks something like this:
@@ -478,7 +478,7 @@ For example:
model = Account
fields = '__all__'
-You can set the `exclude` attribute of the to a list of fields to be excluded from the serializer.
+You can set the `exclude` attribute to a list of fields to be excluded from the serializer.
For example:
@@ -551,7 +551,7 @@ Please review the [Validators Documentation](/api-guide/validators/) for details
## Additional keyword arguments
-There is also a shortcut allowing you to specify arbitrary additional keyword arguments on fields, using the `extra_kwargs` option. Similarly to `read_only_fields` this means you do not need to explicitly declare the field on the serializer.
+There is also a shortcut allowing you to specify arbitrary additional keyword arguments on fields, using the `extra_kwargs` option. As in the case of `read_only_fields`, this means you do not need to explicitly declare the field on the serializer.
This option is a dictionary, mapping field names to a dictionary of keyword arguments. For example:
@@ -832,7 +832,7 @@ This class implements the same basic API as the `Serializer` class:
* `.data` - Returns the outgoing primitive representation.
* `.is_valid()` - Deserializes and validates incoming data.
* `.validated_data` - Returns the validated incoming data.
-* `.errors` - Returns an errors during validation.
+* `.errors` - Returns any errors during validation.
* `.save()` - Persists the validated data into an object instance.
There are four methods that can be overridden, depending on what functionality you want the serializer class to support:
diff --git a/docs/api-guide/versioning.md b/docs/api-guide/versioning.md
index 482943b86..06b0056a4 100644
--- a/docs/api-guide/versioning.md
+++ b/docs/api-guide/versioning.md
@@ -72,7 +72,7 @@ The following settings keys are also used to control versioning:
* `DEFAULT_VERSION`. The value that should be used for `request.version` when no versioning information is present. Defaults to `None`.
* `ALLOWED_VERSIONS`. If set, this value will restrict the set of versions that may be returned by the versioning scheme, and will raise an error if the provided version if not in this set. Note that the value used for the `DEFAULT_VERSION` setting is always considered to be part of the `ALLOWED_VERSIONS` set. Defaults to `None`.
-* `VERSION_PARAMETER`. The string that should used for any versioning parameters, such as in the media type or URL query parameters. Defaults to `'version'`.
+* `VERSION_PARAM`. The string that should used for any versioning parameters, such as in the media type or URL query parameters. Defaults to `'version'`.
You can also set your versioning class plus those three values on a per-view or a per-viewset basis by defining your own versioning scheme and using the `default_version`, `allowed_versions` and `version_param` class variables. For example, if you want to use `URLPathVersioning`:
diff --git a/docs/topics/third-party-resources.md b/docs/topics/third-party-resources.md
index bef3effc7..a4f1eefcb 100644
--- a/docs/topics/third-party-resources.md
+++ b/docs/topics/third-party-resources.md
@@ -237,6 +237,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
### Misc
+* [cookiecutter-django-rest][cookiecutter-django-rest] - A cookiecutter template that takes care of the setup and configuration so you can focus on making your REST apis awesome.
* [djangorestrelationalhyperlink][djangorestrelationalhyperlink] - A hyperlinked serialiser that can can be used to alter relationships via hyperlinks, but otherwise like a hyperlink model serializer.
* [django-rest-swagger][django-rest-swagger] - An API documentation generator for Swagger UI.
* [django-rest-framework-proxy][django-rest-framework-proxy] - Proxy to redirect incoming request to another API server.
@@ -346,4 +347,5 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
[django-rest-framework-braces]: https://github.com/dealertrack/django-rest-framework-braces
[dry-rest-permissions]: https://github.com/Helioscene/dry-rest-permissions
[django-url-filter]: https://github.com/miki725/django-url-filter
+[cookiecutter-django-rest]: https://github.com/agconti/cookiecutter-django-rest
[drf-haystack]: http://drf-haystack.readthedocs.org/en/latest/
diff --git a/rest_framework/authtoken/south_migrations/0001_initial.py b/rest_framework/authtoken/south_migrations/0001_initial.py
deleted file mode 100644
index 5b927f3e5..000000000
--- a/rest_framework/authtoken/south_migrations/0001_initial.py
+++ /dev/null
@@ -1,60 +0,0 @@
-# -*- coding: utf-8 -*-
-from south.db import db
-from south.v2 import SchemaMigration
-
-try:
- from django.contrib.auth import get_user_model
-except ImportError: # django < 1.5
- from django.contrib.auth.models import User
-else:
- User = get_user_model()
-
-
-class Migration(SchemaMigration):
-
- def forwards(self, orm):
- # Adding model 'Token'
- db.create_table('authtoken_token', (
- ('key', self.gf('django.db.models.fields.CharField')(max_length=40, primary_key=True)),
- ('user', self.gf('django.db.models.fields.related.OneToOneField')(related_name='auth_token', unique=True, to=orm['%s.%s' % (User._meta.app_label, User._meta.object_name)])),
- ('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
- ))
- db.send_create_signal('authtoken', ['Token'])
-
- def backwards(self, orm):
- # Deleting model 'Token'
- db.delete_table('authtoken_token')
-
- models = {
- 'auth.group': {
- 'Meta': {'object_name': 'Group'},
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
- 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
- },
- 'auth.permission': {
- 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
- 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
- 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
- },
- "%s.%s" % (User._meta.app_label, User._meta.module_name): {
- 'Meta': {'object_name': User._meta.module_name, 'db_table': repr(User._meta.db_table)},
- },
- 'authtoken.token': {
- 'Meta': {'object_name': 'Token'},
- 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
- 'key': ('django.db.models.fields.CharField', [], {'max_length': '40', 'primary_key': 'True'}),
- 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'auth_token'", 'unique': 'True', 'to': "orm['%s.%s']" % (User._meta.app_label, User._meta.object_name)})
- },
- 'contenttypes.contenttype': {
- 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
- 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
- }
- }
-
- complete_apps = ['authtoken']
diff --git a/rest_framework/authtoken/south_migrations/__init__.py b/rest_framework/authtoken/south_migrations/__init__.py
deleted file mode 100644
index e69de29bb..000000000
diff --git a/rest_framework/compat.py b/rest_framework/compat.py
index 6de823b8a..40da9d9c1 100644
--- a/rest_framework/compat.py
+++ b/rest_framework/compat.py
@@ -111,8 +111,9 @@ except ImportError:
# Fixes (#1712). We keep the try/except for the test suite.
guardian = None
try:
- import guardian
- import guardian.shortcuts # Fixes #1624
+ if 'guardian' in settings.INSTALLED_APPS:
+ import guardian
+ import guardian.shortcuts # Fixes #1624
except ImportError:
pass
diff --git a/rest_framework/exceptions.py b/rest_framework/exceptions.py
index 236087bde..8447a9ded 100644
--- a/rest_framework/exceptions.py
+++ b/rest_framework/exceptions.py
@@ -30,10 +30,10 @@ def _force_text_recursive(data):
return ReturnList(ret, serializer=data.serializer)
return data
elif isinstance(data, dict):
- ret = dict([
- (key, _force_text_recursive(value))
+ ret = {
+ key: _force_text_recursive(value)
for key, value in data.items()
- ])
+ }
if isinstance(data, ReturnDict):
return ReturnDict(ret, serializer=data.serializer)
return data
diff --git a/rest_framework/fields.py b/rest_framework/fields.py
index 0d4a51152..0b214b872 100644
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -604,8 +604,8 @@ class BooleanField(Field):
}
default_empty_html = False
initial = False
- TRUE_VALUES = set(('t', 'T', 'true', 'True', 'TRUE', '1', 1, True))
- FALSE_VALUES = set(('f', 'F', 'false', 'False', 'FALSE', '0', 0, 0.0, False))
+ TRUE_VALUES = {'t', 'T', 'true', 'True', 'TRUE', '1', 1, True}
+ FALSE_VALUES = {'f', 'F', 'false', 'False', 'FALSE', '0', 0, 0.0, False}
def __init__(self, **kwargs):
assert 'allow_null' not in kwargs, '`allow_null` is not a valid option. Use `NullBooleanField` instead.'
@@ -634,9 +634,9 @@ class NullBooleanField(Field):
'invalid': _('"{input}" is not a valid boolean.')
}
initial = None
- TRUE_VALUES = set(('t', 'T', 'true', 'True', 'TRUE', '1', 1, True))
- FALSE_VALUES = set(('f', 'F', 'false', 'False', 'FALSE', '0', 0, 0.0, False))
- NULL_VALUES = set(('n', 'N', 'null', 'Null', 'NULL', '', None))
+ TRUE_VALUES = {'t', 'T', 'true', 'True', 'TRUE', '1', 1, True}
+ FALSE_VALUES = {'f', 'F', 'false', 'False', 'FALSE', '0', 0, 0.0, False}
+ NULL_VALUES = {'n', 'N', 'null', 'Null', 'NULL', '', None}
def __init__(self, **kwargs):
assert 'allow_null' not in kwargs, '`allow_null` is not a valid option.'
@@ -1241,9 +1241,9 @@ class ChoiceField(Field):
# Map the string representation of choices to the underlying value.
# Allows us to deal with eg. integer choices while supporting either
# integer or string input, but still get the correct datatype out.
- self.choice_strings_to_values = dict([
- (six.text_type(key), key) for key in self.choices.keys()
- ])
+ self.choice_strings_to_values = {
+ six.text_type(key): key for key in self.choices.keys()
+ }
self.allow_blank = kwargs.pop('allow_blank', False)
@@ -1302,15 +1302,15 @@ class MultipleChoiceField(ChoiceField):
if not self.allow_empty and len(data) == 0:
self.fail('empty')
- return set([
+ return {
super(MultipleChoiceField, self).to_internal_value(item)
for item in data
- ])
+ }
def to_representation(self, value):
- return set([
+ return {
self.choice_strings_to_values.get(six.text_type(item), item) for item in value
- ])
+ }
class FilePathField(ChoiceField):
@@ -1508,19 +1508,19 @@ class DictField(Field):
data = html.parse_html_dict(data)
if not isinstance(data, dict):
self.fail('not_a_dict', input_type=type(data).__name__)
- return dict([
- (six.text_type(key), self.child.run_validation(value))
+ return {
+ six.text_type(key): self.child.run_validation(value)
for key, value in data.items()
- ])
+ }
def to_representation(self, value):
"""
List of object instances -> List of dicts of primitive datatypes.
"""
- return dict([
- (six.text_type(key), self.child.to_representation(val))
+ return {
+ six.text_type(key): self.child.to_representation(val)
for key, val in value.items()
- ])
+ }
class JSONField(Field):
diff --git a/rest_framework/metadata.py b/rest_framework/metadata.py
index aba1a2013..6c4f17692 100644
--- a/rest_framework/metadata.py
+++ b/rest_framework/metadata.py
@@ -77,7 +77,7 @@ class SimpleMetadata(BaseMetadata):
the fields that are accepted for 'PUT' and 'POST' methods.
"""
actions = {}
- for method in set(['PUT', 'POST']) & set(view.allowed_methods):
+ for method in {'PUT', 'POST'} & set(view.allowed_methods):
view.request = clone_request(request, method)
try:
# Test global permissions
diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py
index 3945e330c..cc91c676f 100644
--- a/rest_framework/pagination.py
+++ b/rest_framework/pagination.py
@@ -79,11 +79,7 @@ def _get_displayed_page_numbers(current, final):
# We always include the first two pages, last two pages, and
# two pages either side of the current page.
- included = set((
- 1,
- current - 1, current, current + 1,
- final
- ))
+ included = {1, current - 1, current, current + 1, final}
# If the break would only exclude a single page number then we
# may as well include the page number instead of the break.
diff --git a/rest_framework/routers.py b/rest_framework/routers.py
index d4e9d95ed..39605923d 100644
--- a/rest_framework/routers.py
+++ b/rest_framework/routers.py
@@ -174,7 +174,7 @@ class SimpleRouter(BaseRouter):
url_path = initkwargs.pop("url_path", None) or methodname
ret.append(Route(
url=replace_methodname(route.url, url_path),
- mapping=dict((httpmethod, methodname) for httpmethod in httpmethods),
+ mapping={httpmethod: methodname for httpmethod in httpmethods},
name=replace_methodname(route.name, url_path),
initkwargs=initkwargs,
))
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index 5aef1df69..d505d8d3a 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -125,10 +125,10 @@ class BaseSerializer(Field):
}
if allow_empty is not None:
list_kwargs['allow_empty'] = allow_empty
- list_kwargs.update(dict([
- (key, value) for key, value in kwargs.items()
+ list_kwargs.update({
+ key: value for key, value in kwargs.items()
if key in LIST_SERIALIZER_KWARGS
- ]))
+ })
meta = getattr(cls, 'Meta', None)
list_serializer_class = getattr(meta, 'list_serializer_class', ListSerializer)
return list_serializer_class(*args, **list_kwargs)
@@ -305,10 +305,10 @@ def get_validation_error_detail(exc):
elif isinstance(exc.detail, dict):
# If errors may be a dict we use the standard {key: list of values}.
# Here we ensure that all the values are *lists* of errors.
- return dict([
- (key, value if isinstance(value, list) else [value])
+ return {
+ key: value if isinstance(value, list) else [value]
for key, value in exc.detail.items()
- ])
+ }
elif isinstance(exc.detail, list):
# Errors raised as a list are non-field errors.
return {
@@ -1237,13 +1237,10 @@ class ModelSerializer(Serializer):
for model_field in model_fields.values():
# Include each of the `unique_for_*` field names.
- unique_constraint_names |= set([
- model_field.unique_for_date,
- model_field.unique_for_month,
- model_field.unique_for_year
- ])
+ unique_constraint_names |= {model_field.unique_for_date, model_field.unique_for_month,
+ model_field.unique_for_year}
- unique_constraint_names -= set([None])
+ unique_constraint_names -= {None}
# Include each of the `unique_together` field names,
# so long as all the field names are included on the serializer.
@@ -1357,10 +1354,10 @@ class ModelSerializer(Serializer):
# which may map onto a model field. Any dotted field name lookups
# cannot map to a field, and must be a traversal, so we're not
# including those.
- field_names = set([
+ field_names = {
field.source for field in self.fields.values()
if (field.source != '*') and ('.' not in field.source)
- ])
+ }
# Note that we make sure to check `unique_together` both on the
# base model class, but also on any parent classes.
diff --git a/rest_framework/templates/rest_framework/admin.html b/rest_framework/templates/rest_framework/admin.html
index e1751b21c..318a1e706 100644
--- a/rest_framework/templates/rest_framework/admin.html
+++ b/rest_framework/templates/rest_framework/admin.html
@@ -219,7 +219,7 @@
{% endif %}
{% block script %}
-
+
diff --git a/rest_framework/utils/field_mapping.py b/rest_framework/utils/field_mapping.py
index 8206578ee..f109ad80d 100644
--- a/rest_framework/utils/field_mapping.py
+++ b/rest_framework/utils/field_mapping.py
@@ -123,7 +123,8 @@ def get_field_kwargs(field_name, model_field):
# Ensure that max_length is passed explicitly as a keyword arg,
# rather than as a validator.
max_length = getattr(model_field, 'max_length', None)
- if max_length is not None and isinstance(model_field, models.CharField):
+ if max_length is not None and (isinstance(model_field, models.CharField) or
+ isinstance(model_field, models.TextField)):
kwargs['max_length'] = max_length
validator_kwarg = [
validator for validator in validator_kwarg
diff --git a/rest_framework/validators.py b/rest_framework/validators.py
index a1771a92e..a21f67e60 100644
--- a/rest_framework/validators.py
+++ b/rest_framework/validators.py
@@ -100,11 +100,11 @@ class UniqueTogetherValidator(object):
if self.instance is not None:
return
- missing = dict([
- (field_name, self.missing_message)
+ missing = {
+ field_name: self.missing_message
for field_name in self.fields
if field_name not in attrs
- ])
+ }
if missing:
raise ValidationError(missing)
@@ -120,10 +120,10 @@ class UniqueTogetherValidator(object):
attrs[field_name] = getattr(self.instance, field_name)
# Determine the filter keyword arguments and filter the queryset.
- filter_kwargs = dict([
- (field_name, attrs[field_name])
+ filter_kwargs = {
+ field_name: attrs[field_name]
for field_name in self.fields
- ])
+ }
return queryset.filter(**filter_kwargs)
def exclude_current_instance(self, attrs, queryset):
@@ -184,11 +184,11 @@ class BaseUniqueForValidator(object):
The `UniqueForValidator` classes always force an implied
'required' state on the fields they are applied to.
"""
- missing = dict([
- (field_name, self.missing_message)
+ missing = {
+ field_name: self.missing_message
for field_name in [self.field, self.date_field]
if field_name not in attrs
- ])
+ }
if missing:
raise ValidationError(missing)
diff --git a/runtests.py b/runtests.py
index 36a2183bd..504cd1d37 100755
--- a/runtests.py
+++ b/runtests.py
@@ -93,7 +93,11 @@ if __name__ == "__main__":
except ValueError:
pass
else:
- pytest_args = ['--cov', 'rest_framework'] + pytest_args
+ pytest_args = [
+ '--cov-report',
+ 'xml',
+ '--cov',
+ 'rest_framework'] + pytest_args
if first_arg.startswith('-'):
# `runtests.py [flags]`
diff --git a/tests/test_model_serializer.py b/tests/test_model_serializer.py
index aa62ec4ae..c9b2d313e 100644
--- a/tests/test_model_serializer.py
+++ b/tests/test_model_serializer.py
@@ -63,7 +63,7 @@ class RegularFieldsModel(models.Model):
positive_small_integer_field = models.PositiveSmallIntegerField()
slug_field = models.SlugField(max_length=100)
small_integer_field = models.SmallIntegerField()
- text_field = models.TextField()
+ text_field = models.TextField(max_length=100)
time_field = models.TimeField()
url_field = models.URLField(max_length=100)
custom_field = CustomField()
@@ -161,11 +161,12 @@ class TestRegularFieldMappings(TestCase):
positive_small_integer_field = IntegerField()
slug_field = SlugField(max_length=100)
small_integer_field = IntegerField()
- text_field = CharField(style={'base_template': 'textarea.html'})
+ text_field = CharField(max_length=100, style={'base_template': 'textarea.html'})
time_field = TimeField()
url_field = URLField(max_length=100)
custom_field = ModelField(model_field=)
""")
+
self.assertEqual(unicode_repr(TestSerializer()), expected)
def test_field_options(self):
diff --git a/tox.ini b/tox.ini
index 0ef64cc49..4648f592c 100644
--- a/tox.ini
+++ b/tox.ini
@@ -19,9 +19,9 @@ commands = ./runtests.py --fast {posargs} --coverage
setenv =
PYTHONDONTWRITEBYTECODE=1
deps =
- django17: Django==1.7.10 # Should track maximum supported
- django18: Django==1.8.4 # Should track maximum supported
- django19: https://www.djangoproject.com/download/1.9a1/tarball/
+ django17: Django==1.7.10
+ django18: Django==1.8.4
+ django19: https://www.djangoproject.com/download/1.9b1/tarball/
-rrequirements/requirements-testing.txt
-rrequirements/requirements-optionals.txt
@@ -40,21 +40,21 @@ deps =
# Specify explicitly to exclude Django Guardian against Django 1.9
[testenv:py27-django19]
deps =
- https://www.djangoproject.com/download/1.9a1/tarball/
+ https://www.djangoproject.com/download/1.9b1/tarball/
-rrequirements/requirements-testing.txt
markdown==2.5.2
django-filter==0.10.0
[testenv:py34-django19]
deps =
- https://www.djangoproject.com/download/1.9a1/tarball/
+ https://www.djangoproject.com/download/1.9b1/tarball/
-rrequirements/requirements-testing.txt
markdown==2.5.2
django-filter==0.10.0
[testenv:py35-django19]
deps =
- https://www.djangoproject.com/download/1.9a1/tarball/
+ https://www.djangoproject.com/download/1.9b1/tarball/
-rrequirements/requirements-testing.txt
markdown==2.5.2
django-filter==0.10.0