diff --git a/.travis.yml b/.travis.yml index 5a6900a5e..ececf3e9d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,6 +19,7 @@ install: - pip install Pillow==2.3.0 - pip install django-guardian==1.2.3 - pip install pytest-django==2.6.1 + - pip install flake8==2.2.2 - "if [[ ${TRAVIS_PYTHON_VERSION::1} != '3' ]]; then pip install oauth2==1.5.211; fi" - "if [[ ${TRAVIS_PYTHON_VERSION::1} != '3' ]]; then pip install django-oauth-plus==2.2.4; fi" - "if [[ ${TRAVIS_PYTHON_VERSION::1} != '3' ]]; then pip install django-oauth2-provider==0.2.4; fi" @@ -28,7 +29,7 @@ install: - export PYTHONPATH=. script: - - py.test + - ./runtests.py matrix: exclude: diff --git a/pytest.ini b/pytest.ini deleted file mode 100644 index bbd083ac1..000000000 --- a/pytest.ini +++ /dev/null @@ -1,2 +0,0 @@ -[pytest] -addopts = --tb=short diff --git a/optionals.txt b/requirements-test.txt similarity index 59% rename from optionals.txt rename to requirements-test.txt index 262e76443..411daeba2 100644 --- a/optionals.txt +++ b/requirements-test.txt @@ -1,3 +1,10 @@ +# Test requirements +pytest-django==2.6 +pytest==2.5.2 +pytest-cov==1.6 +flake8==2.2.2 + +# Optional packages markdown>=2.1.0 PyYAML>=3.10 defusedxml>=0.3 diff --git a/requirements.txt b/requirements.txt index 360acb14d..730c1d07a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1 @@ --e . Django>=1.3 -pytest-django==2.6 diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py index 01036cefa..f30012b9b 100644 --- a/rest_framework/__init__.py +++ b/rest_framework/__init__.py @@ -1,9 +1,9 @@ """ -______ _____ _____ _____ __ _ -| ___ \ ___/ ___|_ _| / _| | | -| |_/ / |__ \ `--. | | | |_ _ __ __ _ _ __ ___ _____ _____ _ __| | __ +______ _____ _____ _____ __ +| ___ \ ___/ ___|_ _| / _| | | +| |_/ / |__ \ `--. | | | |_ _ __ __ _ _ __ ___ _____ _____ _ __| |__ | /| __| `--. \ | | | _| '__/ _` | '_ ` _ \ / _ \ \ /\ / / _ \| '__| |/ / -| |\ \| |___/\__/ / | | | | | | | (_| | | | | | | __/\ V V / (_) | | | < +| |\ \| |___/\__/ / | | | | | | | (_| | | | | | | __/\ V V / (_) | | | < \_| \_\____/\____/ \_/ |_| |_| \__,_|_| |_| |_|\___| \_/\_/ \___/|_| |_|\_| """ diff --git a/rest_framework/authentication.py b/rest_framework/authentication.py index 82cea70fc..5721a869e 100644 --- a/rest_framework/authentication.py +++ b/rest_framework/authentication.py @@ -21,7 +21,7 @@ def get_authorization_header(request): Hide some test client ickyness where the header can be unicode. """ auth = request.META.get('HTTP_AUTHORIZATION', b'') - if type(auth) == type(''): + if isinstance(auth, type('')): # Work around django test client oddness auth = auth.encode(HTTP_HEADER_ENCODING) return auth diff --git a/rest_framework/authtoken/models.py b/rest_framework/authtoken/models.py index 167fa5314..db21d44c3 100644 --- a/rest_framework/authtoken/models.py +++ b/rest_framework/authtoken/models.py @@ -1,6 +1,5 @@ import binascii import os -from hashlib import sha1 from django.conf import settings from django.db import models diff --git a/rest_framework/authtoken/south_migrations/0001_initial.py b/rest_framework/authtoken/south_migrations/0001_initial.py index d5965e404..926de02b1 100644 --- a/rest_framework/authtoken/south_migrations/0001_initial.py +++ b/rest_framework/authtoken/south_migrations/0001_initial.py @@ -1,15 +1,10 @@ # -*- coding: utf-8 -*- -import datetime from south.db import db from south.v2 import SchemaMigration -from django.db import models - -from rest_framework.settings import api_settings - try: from django.contrib.auth import get_user_model -except ImportError: # django < 1.5 +except ImportError: # django < 1.5 from django.contrib.auth.models import User else: User = get_user_model() @@ -26,12 +21,10 @@ class Migration(SchemaMigration): )) 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'}, diff --git a/rest_framework/decorators.py b/rest_framework/decorators.py index 18e41a18d..e06d6ff56 100644 --- a/rest_framework/decorators.py +++ b/rest_framework/decorators.py @@ -131,6 +131,7 @@ def list_route(methods=['get'], **kwargs): return func return decorator + # These are now pending deprecation, in favor of `detail_route` and `list_route`. def link(**kwargs): @@ -139,11 +140,13 @@ def link(**kwargs): """ msg = 'link is pending deprecation. Use detail_route instead.' warnings.warn(msg, PendingDeprecationWarning, stacklevel=2) + def decorator(func): func.bind_to_methods = ['get'] func.detail = True func.kwargs = kwargs return func + return decorator @@ -153,9 +156,11 @@ def action(methods=['post'], **kwargs): """ msg = 'action is pending deprecation. Use detail_route instead.' warnings.warn(msg, PendingDeprecationWarning, stacklevel=2) + def decorator(func): func.bind_to_methods = methods func.detail = True func.kwargs = kwargs return func - return decorator \ No newline at end of file + + return decorator diff --git a/rest_framework/exceptions.py b/rest_framework/exceptions.py index 5f774a9f3..97dab77ea 100644 --- a/rest_framework/exceptions.py +++ b/rest_framework/exceptions.py @@ -23,6 +23,7 @@ class APIException(Exception): def __str__(self): return self.detail + class ParseError(APIException): status_code = status.HTTP_400_BAD_REQUEST default_detail = 'Malformed request.' diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 43a74ae6e..85fcbd965 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -63,8 +63,10 @@ def get_component(obj, attr_name): def readable_datetime_formats(formats): - format = ', '.join(formats).replace(ISO_8601, - 'YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HH:MM|-HH:MM|Z]') + format = ', '.join(formats).replace( + ISO_8601, + 'YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HH:MM|-HH:MM|Z]' + ) return humanize_strptime(format) @@ -425,7 +427,7 @@ class ModelField(WritableField): } -##### Typed Fields ##### +# Typed Fields class BooleanField(WritableField): type_name = 'BooleanField' @@ -484,7 +486,7 @@ class URLField(CharField): type_label = 'url' def __init__(self, **kwargs): - if not 'validators' in kwargs: + if 'validators' not in kwargs: kwargs['validators'] = [validators.URLValidator()] super(URLField, self).__init__(**kwargs) diff --git a/rest_framework/filters.py b/rest_framework/filters.py index c3b846aed..538386cee 100644 --- a/rest_framework/filters.py +++ b/rest_framework/filters.py @@ -116,7 +116,7 @@ class OrderingFilter(BaseFilterBackend): def get_ordering(self, request): """ Ordering is set by a comma delimited ?ordering=... query parameter. - + The `ordering` query parameter can be overridden by setting the `ordering_param` value on the OrderingFilter or by specifying an `ORDERING_PARAM` value in the API settings. diff --git a/rest_framework/generics.py b/rest_framework/generics.py index cecb548fb..77deb8e4f 100644 --- a/rest_framework/generics.py +++ b/rest_framework/generics.py @@ -25,6 +25,7 @@ def strict_positive_int(integer_string, cutoff=None): ret = min(ret, cutoff) return ret + def get_object_or_404(queryset, *filter_args, **filter_kwargs): """ Same as Django's standard shortcut, but make sure to raise 404 @@ -162,10 +163,11 @@ class GenericAPIView(views.APIView): raise Http404(_("Page is not 'last', nor can it be converted to an int.")) try: page = paginator.page(page_number) - except InvalidPage as e: - raise Http404(_('Invalid page (%(page_number)s): %(message)s') % { - 'page_number': page_number, - 'message': str(e) + except InvalidPage as exc: + error_format = _('Invalid page (%(page_number)s): %(message)s') + raise Http404(error_format % { + 'page_number': page_number, + 'message': str(exc) }) if deprecated_style: @@ -208,10 +210,8 @@ class GenericAPIView(views.APIView): return filter_backends - - ######################## - ### The following methods provide default implementations - ### that you may want to override for more complex cases. + # The following methods provide default implementations + # that you may want to override for more complex cases. def get_paginate_by(self, queryset=None): """ @@ -284,8 +284,8 @@ class GenericAPIView(views.APIView): if self.model is not None: return self.model._default_manager.all() - raise ImproperlyConfigured("'%s' must define 'queryset' or 'model'" - % self.__class__.__name__) + error_format = "'%s' must define 'queryset' or 'model'" + raise ImproperlyConfigured(error_format % self.__class__.__name__) def get_object(self, queryset=None): """ @@ -339,12 +339,11 @@ class GenericAPIView(views.APIView): return obj - ######################## - ### The following are placeholder methods, - ### and are intended to be overridden. - ### - ### The are not called by GenericAPIView directly, - ### but are used by the mixin methods. + # The following are placeholder methods, + # and are intended to be overridden. + # + # The are not called by GenericAPIView directly, + # but are used by the mixin methods. def pre_save(self, obj): """ @@ -416,10 +415,8 @@ class GenericAPIView(views.APIView): return ret -########################################################## -### Concrete view classes that provide method handlers ### -### by composing the mixin classes with the base view. ### -########################################################## +# Concrete view classes that provide method handlers +# by composing the mixin classes with the base view. class CreateAPIView(mixins.CreateModelMixin, GenericAPIView): @@ -534,9 +531,7 @@ class RetrieveUpdateDestroyAPIView(mixins.RetrieveModelMixin, return self.destroy(request, *args, **kwargs) -########################## -### Deprecated classes ### -########################## +# Deprecated classes class MultipleObjectAPIView(GenericAPIView): def __init__(self, *args, **kwargs): diff --git a/rest_framework/negotiation.py b/rest_framework/negotiation.py index 4d205c0e8..ca7b53978 100644 --- a/rest_framework/negotiation.py +++ b/rest_framework/negotiation.py @@ -54,8 +54,10 @@ 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 (_MediaType(renderer.media_type).precedence > - _MediaType(media_type).precedence): + if ( + _MediaType(renderer.media_type).precedence > + _MediaType(media_type).precedence + ): # Eg client requests '*/*' # Accepted media type is 'application/json' return renderer, renderer.media_type diff --git a/rest_framework/permissions.py b/rest_framework/permissions.py index c95171389..6a1a00770 100644 --- a/rest_framework/permissions.py +++ b/rest_framework/permissions.py @@ -62,9 +62,11 @@ class IsAuthenticatedOrReadOnly(BasePermission): """ def has_permission(self, request, view): - return (request.method in SAFE_METHODS or - request.user and - request.user.is_authenticated()) + return ( + request.method in SAFE_METHODS or + request.user and + request.user.is_authenticated() + ) class DjangoModelPermissions(BasePermission): @@ -122,9 +124,11 @@ class DjangoModelPermissions(BasePermission): perms = self.get_required_permissions(request.method, model_cls) - return (request.user and + return ( + request.user and (request.user.is_authenticated() or not self.authenticated_users_only) and - request.user.has_perms(perms)) + request.user.has_perms(perms) + ) class DjangoModelPermissionsOrAnonReadOnly(DjangoModelPermissions): @@ -212,6 +216,8 @@ class TokenHasReadWriteScope(BasePermission): required = oauth2_constants.READ if read_only else oauth2_constants.WRITE return oauth2_provider_scope.check(required, request.auth.scope) - assert False, ('TokenHasReadWriteScope requires either the' - '`OAuthAuthentication` or `OAuth2Authentication` authentication ' - 'class to be used.') + assert False, ( + 'TokenHasReadWriteScope requires either the' + '`OAuthAuthentication` or `OAuth2Authentication` authentication ' + 'class to be used.' + ) diff --git a/rest_framework/relations.py b/rest_framework/relations.py index 3b234dd58..1acbdce26 100644 --- a/rest_framework/relations.py +++ b/rest_framework/relations.py @@ -19,8 +19,7 @@ from rest_framework.compat import smart_text import warnings -##### Relational fields ##### - +# Relational fields # Not actually Writable, but subclasses may need to be. class RelatedField(WritableField): @@ -66,7 +65,7 @@ class RelatedField(WritableField): else: # Reverse self.queryset = manager.field.rel.to._default_manager.all() - ### We need this stuff to make form choices work... + # We need this stuff to make form choices work... def prepare_value(self, obj): return self.to_native(obj) @@ -113,7 +112,7 @@ class RelatedField(WritableField): choices = property(_get_choices, _set_choices) - ### Default value handling + # Default value handling def get_default_value(self): default = super(RelatedField, self).get_default_value() @@ -121,7 +120,7 @@ class RelatedField(WritableField): return [] return default - ### Regular serializer stuff... + # Regular serializer stuff... def field_to_native(self, obj, field_name): try: @@ -181,7 +180,7 @@ class RelatedField(WritableField): into[(self.source or field_name)] = self.from_native(value) -### PrimaryKey relationships +# PrimaryKey relationships class PrimaryKeyRelatedField(RelatedField): """ @@ -269,8 +268,7 @@ class PrimaryKeyRelatedField(RelatedField): return self.to_native(pk) -### Slug relationships - +# Slug relationships class SlugRelatedField(RelatedField): """ @@ -305,7 +303,7 @@ class SlugRelatedField(RelatedField): raise ValidationError(msg) -### Hyperlinked relationships +# Hyperlinked relationships class HyperlinkedRelatedField(RelatedField): """ diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index 7048d87de..ac7175a7b 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -8,7 +8,6 @@ REST framework also provides an HTML renderer the renders the browsable API. """ from __future__ import unicode_literals -import copy import json import django from django import forms @@ -75,7 +74,6 @@ class JSONRenderer(BaseRenderer): # E.g. If we're being called by the BrowsableAPIRenderer. return renderer_context.get('indent', None) - def render(self, data, accepted_media_type=None, renderer_context=None): """ Render `data` into JSON, returning a bytestring. @@ -86,8 +84,10 @@ class JSONRenderer(BaseRenderer): renderer_context = renderer_context or {} indent = self.get_indent(accepted_media_type, renderer_context) - ret = json.dumps(data, cls=self.encoder_class, - indent=indent, ensure_ascii=self.ensure_ascii) + ret = json.dumps( + data, cls=self.encoder_class, + indent=indent, ensure_ascii=self.ensure_ascii + ) # On python 2.x json.dumps() returns bytestrings if ensure_ascii=True, # but if ensure_ascii=False, the return type is underspecified, @@ -414,7 +414,7 @@ class BrowsableAPIRenderer(BaseRenderer): """ Returns True if a form should be shown for this method. """ - if not method in view.allowed_methods: + if method not in view.allowed_methods: return # Not a valid method if not api_settings.FORM_METHOD_OVERRIDE: @@ -454,8 +454,10 @@ class BrowsableAPIRenderer(BaseRenderer): if method in ('DELETE', 'OPTIONS'): return True # Don't actually need to return a form - if (not getattr(view, 'get_serializer', None) - or not any(is_form_media_type(parser.media_type) for parser in view.parser_classes)): + if ( + not getattr(view, 'get_serializer', None) + or not any(is_form_media_type(parser.media_type) for parser in view.parser_classes) + ): return serializer = view.get_serializer(instance=obj, data=data, files=files) @@ -576,7 +578,7 @@ class BrowsableAPIRenderer(BaseRenderer): 'version': VERSION, 'breadcrumblist': self.get_breadcrumbs(request), 'allowed_methods': view.allowed_methods, - 'available_formats': [renderer.format for renderer in view.renderer_classes], + '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), @@ -625,4 +627,3 @@ class MultiPartRenderer(BaseRenderer): def render(self, data, accepted_media_type=None, renderer_context=None): return encode_multipart(self.BOUNDARY, data) - diff --git a/rest_framework/request.py b/rest_framework/request.py index d508f9b43..275326614 100644 --- a/rest_framework/request.py +++ b/rest_framework/request.py @@ -295,8 +295,11 @@ class Request(object): Return the content body of the request, as a stream. """ try: - content_length = int(self.META.get('CONTENT_LENGTH', - self.META.get('HTTP_CONTENT_LENGTH'))) + content_length = int( + self.META.get( + 'CONTENT_LENGTH', self.META.get('HTTP_CONTENT_LENGTH') + ) + ) except (ValueError, TypeError): content_length = 0 @@ -320,9 +323,11 @@ class Request(object): ) # We only need to use form overloading on form POST requests. - if (not USE_FORM_OVERLOADING + if ( + not USE_FORM_OVERLOADING or self._request.method != 'POST' - or not is_form_media_type(self._content_type)): + or not is_form_media_type(self._content_type) + ): return # At this point we're committed to parsing the request as form data. @@ -330,15 +335,19 @@ class Request(object): self._files = self._request.FILES # Method overloading - change the method and remove the param from the content. - if (self._METHOD_PARAM and - self._METHOD_PARAM in self._data): + if ( + self._METHOD_PARAM and + self._METHOD_PARAM in self._data + ): self._method = self._data[self._METHOD_PARAM].upper() # Content overloading - modify the content type, and force re-parse. - if (self._CONTENT_PARAM and + if ( + self._CONTENT_PARAM and self._CONTENTTYPE_PARAM and self._CONTENT_PARAM in self._data and - self._CONTENTTYPE_PARAM in self._data): + self._CONTENTTYPE_PARAM in self._data + ): self._content_type = self._data[self._CONTENTTYPE_PARAM] self._stream = BytesIO(self._data[self._CONTENT_PARAM].encode(self.parser_context['encoding'])) self._data, self._files = (Empty, Empty) @@ -394,7 +403,7 @@ class Request(object): self._not_authenticated() raise - if not user_auth_tuple is None: + if user_auth_tuple is not None: self._authenticator = authenticator self._user, self._auth = user_auth_tuple return diff --git a/rest_framework/response.py b/rest_framework/response.py index 25b785245..80225cac3 100644 --- a/rest_framework/response.py +++ b/rest_framework/response.py @@ -62,8 +62,10 @@ class Response(SimpleTemplateResponse): ret = renderer.render(self.data, media_type, context) if isinstance(ret, six.text_type): - assert charset, 'renderer returned unicode, and did not specify ' \ - 'a charset value.' + assert charset, ( + 'renderer returned unicode, and did not specify ' + 'a charset value.' + ) return bytes(ret.encode(charset)) if not ret: diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 2fdc9b9da..95288671c 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -449,9 +449,11 @@ class BaseSerializer(WritableField): # If we have a model manager or similar object then we need # to iterate through each instance. - if (self.many and + if ( + self.many and not hasattr(obj, '__iter__') and - is_simple_callable(getattr(obj, 'all', None))): + is_simple_callable(getattr(obj, 'all', None)) + ): obj = obj.all() kwargs = { @@ -601,8 +603,10 @@ class BaseSerializer(WritableField): API schemas for auto-documentation. """ return SortedDict( - [(field_name, field.metadata()) - for field_name, field in six.iteritems(self.fields)] + [ + (field_name, field.metadata()) + for field_name, field in six.iteritems(self.fields) + ] ) @@ -656,8 +660,10 @@ class ModelSerializer(Serializer): """ cls = self.opts.model - assert cls is not None, \ - "Serializer class '%s' is missing 'model' Meta option" % self.__class__.__name__ + assert cls is not None, ( + "Serializer class '%s' is missing 'model' Meta option" % + self.__class__.__name__ + ) opts = cls._meta.concrete_model._meta ret = SortedDict() nested = bool(self.opts.depth) @@ -668,9 +674,9 @@ class ModelSerializer(Serializer): # If model is a child via multitable inheritance, use parent's pk pk_field = pk_field.rel.to._meta.pk - field = self.get_pk_field(pk_field) - if field: - ret[pk_field.name] = field + serializer_pk_field = self.get_pk_field(pk_field) + if serializer_pk_field: + ret[pk_field.name] = serializer_pk_field # Deal with forward relationships forward_rels = [field for field in opts.fields if field.serialize] @@ -739,9 +745,11 @@ class ModelSerializer(Serializer): is_m2m = isinstance(relation.field, models.fields.related.ManyToManyField) - if (is_m2m and + if ( + is_m2m and hasattr(relation.field.rel, 'through') and - not relation.field.rel.through._meta.auto_created): + not relation.field.rel.through._meta.auto_created + ): has_through_model = True if nested: @@ -911,10 +919,12 @@ class ModelSerializer(Serializer): for field_name, field in self.fields.items(): field_name = field.source or field_name - if field_name in exclusions \ - and not field.read_only \ - and (field.required or hasattr(instance, field_name)) \ - and not isinstance(field, Serializer): + if ( + field_name in exclusions + and not field.read_only + and (field.required or hasattr(instance, field_name)) + and not isinstance(field, Serializer) + ): exclusions.remove(field_name) return exclusions diff --git a/rest_framework/settings.py b/rest_framework/settings.py index 2727f5960..6806a4689 100644 --- a/rest_framework/settings.py +++ b/rest_framework/settings.py @@ -46,16 +46,12 @@ DEFAULTS = { 'DEFAULT_PERMISSION_CLASSES': ( 'rest_framework.permissions.AllowAny', ), - 'DEFAULT_THROTTLE_CLASSES': ( - ), - 'DEFAULT_CONTENT_NEGOTIATION_CLASS': - 'rest_framework.negotiation.DefaultContentNegotiation', + 'DEFAULT_THROTTLE_CLASSES': (), + 'DEFAULT_CONTENT_NEGOTIATION_CLASS': 'rest_framework.negotiation.DefaultContentNegotiation', # Genric view behavior - 'DEFAULT_MODEL_SERIALIZER_CLASS': - 'rest_framework.serializers.ModelSerializer', - 'DEFAULT_PAGINATION_SERIALIZER_CLASS': - 'rest_framework.pagination.PaginationSerializer', + 'DEFAULT_MODEL_SERIALIZER_CLASS': 'rest_framework.serializers.ModelSerializer', + 'DEFAULT_PAGINATION_SERIALIZER_CLASS': 'rest_framework.pagination.PaginationSerializer', 'DEFAULT_FILTER_BACKENDS': (), # Throttling diff --git a/rest_framework/status.py b/rest_framework/status.py index 764353711..90a755089 100644 --- a/rest_framework/status.py +++ b/rest_framework/status.py @@ -10,15 +10,19 @@ from __future__ import unicode_literals def is_informational(code): return code >= 100 and code <= 199 + def is_success(code): return code >= 200 and code <= 299 + def is_redirect(code): return code >= 300 and code <= 399 + def is_client_error(code): return code >= 400 and code <= 499 + def is_server_error(code): return code >= 500 and code <= 599 diff --git a/rest_framework/templatetags/rest_framework.py b/rest_framework/templatetags/rest_framework.py index 911b1b622..9110aedb3 100644 --- a/rest_framework/templatetags/rest_framework.py +++ b/rest_framework/templatetags/rest_framework.py @@ -152,8 +152,10 @@ def urlize_quoted_links(text, trim_url_limit=None, nofollow=True, autoescape=Tru middle = middle[len(opening):] lead = lead + opening # Keep parentheses at the end only if they're balanced. - if (middle.endswith(closing) - and middle.count(closing) == middle.count(opening) + 1): + if ( + middle.endswith(closing) + and middle.count(closing) == middle.count(opening) + 1 + ): middle = middle[:-len(closing)] trail = closing + trail @@ -164,7 +166,7 @@ def urlize_quoted_links(text, trim_url_limit=None, nofollow=True, autoescape=Tru url = smart_urlquote_wrapper(middle) elif simple_url_2_re.match(middle): url = smart_urlquote_wrapper('http://%s' % middle) - elif not ':' in middle and simple_email_re.match(middle): + elif ':' not in middle and simple_email_re.match(middle): local, domain = middle.rsplit('@', 1) try: domain = domain.encode('idna').decode('ascii') diff --git a/rest_framework/test.py b/rest_framework/test.py index d4ec50a06..9242cf7c6 100644 --- a/rest_framework/test.py +++ b/rest_framework/test.py @@ -49,9 +49,10 @@ class APIRequestFactory(DjangoRequestFactory): else: format = format or self.default_format - assert format in self.renderer_classes, ("Invalid format '{0}'. " - "Available formats are {1}. Set TEST_REQUEST_RENDERER_CLASSES " - "to enable extra request formats.".format( + assert format in self.renderer_classes, ( + "Invalid format '{0}'. Available formats are {1}. " + "Set TEST_REQUEST_RENDERER_CLASSES to enable " + "extra request formats.".format( format, ', '.join(["'" + fmt + "'" for fmt in self.renderer_classes.keys()]) ) diff --git a/rest_framework/urls.py b/rest_framework/urls.py index eed4bd140..8fa3073e8 100644 --- a/rest_framework/urls.py +++ b/rest_framework/urls.py @@ -8,17 +8,19 @@ your API requires authentication: ... url(r'^auth', include('rest_framework.urls', namespace='rest_framework')) ) - + The urls must be namespaced as 'rest_framework', and you should make sure your authentication settings include `SessionAuthentication`. """ from __future__ import unicode_literals from django.conf.urls import patterns, url +from django.contrib.auth import views template_name = {'template_name': 'rest_framework/login.html'} -urlpatterns = patterns('django.contrib.auth.views', - url(r'^login/$', 'login', template_name, name='login'), - url(r'^logout/$', 'logout', template_name, name='logout'), +urlpatterns = patterns( + '', + url(r'^login/$', views.login, template_name, name='login'), + url(r'^logout/$', views.logout, template_name, name='logout') ) diff --git a/rest_framework/utils/encoders.py b/rest_framework/utils/encoders.py index c125ac8a8..00ffdfbae 100644 --- a/rest_framework/utils/encoders.py +++ b/rest_framework/utils/encoders.py @@ -98,14 +98,23 @@ else: node.flow_style = best_style return node - SafeDumper.add_representer(decimal.Decimal, - SafeDumper.represent_decimal) - - SafeDumper.add_representer(SortedDict, - yaml.representer.SafeRepresenter.represent_dict) - SafeDumper.add_representer(DictWithMetadata, - yaml.representer.SafeRepresenter.represent_dict) - SafeDumper.add_representer(SortedDictWithMetadata, - yaml.representer.SafeRepresenter.represent_dict) - SafeDumper.add_representer(types.GeneratorType, - yaml.representer.SafeRepresenter.represent_list) + SafeDumper.add_representer( + decimal.Decimal, + SafeDumper.represent_decimal + ) + SafeDumper.add_representer( + SortedDict, + yaml.representer.SafeRepresenter.represent_dict + ) + SafeDumper.add_representer( + DictWithMetadata, + yaml.representer.SafeRepresenter.represent_dict + ) + SafeDumper.add_representer( + SortedDictWithMetadata, + yaml.representer.SafeRepresenter.represent_dict + ) + SafeDumper.add_representer( + types.GeneratorType, + yaml.representer.SafeRepresenter.represent_list + ) diff --git a/rest_framework/utils/formatting.py b/rest_framework/utils/formatting.py index 4b59ba840..6d53aed11 100644 --- a/rest_framework/utils/formatting.py +++ b/rest_framework/utils/formatting.py @@ -6,8 +6,6 @@ from __future__ import unicode_literals from django.utils.html import escape from django.utils.safestring import mark_safe from rest_framework.compat import apply_markdown -from rest_framework.settings import api_settings -from textwrap import dedent import re @@ -40,6 +38,7 @@ def dedent(content): return content.strip() + def camelcase_to_spaces(content): """ Translate 'CamelCaseNames' to 'Camel Case Names'. @@ -49,6 +48,7 @@ def camelcase_to_spaces(content): content = re.sub(camelcase_boundry, ' \\1', content).strip() return ' '.join(content.split('_')).title() + def markup_description(description): """ Apply HTML markup to the given description. diff --git a/rest_framework/utils/mediatypes.py b/rest_framework/utils/mediatypes.py index 92f99efd2..87b3cc6a3 100644 --- a/rest_framework/utils/mediatypes.py +++ b/rest_framework/utils/mediatypes.py @@ -57,7 +57,7 @@ class _MediaType(object): if key != 'q' and other.params.get(key, None) != self.params.get(key, None): return False - if self.sub_type != '*' and other.sub_type != '*' and other.sub_type != self.sub_type: + if self.sub_type != '*' and other.sub_type != '*' and other.sub_type != self.sub_type: return False if self.main_type != '*' and other.main_type != '*' and other.main_type != self.main_type: @@ -79,7 +79,7 @@ class _MediaType(object): return 3 def __str__(self): - return unicode(self).encode('utf-8') + return self.__unicode__().encode('utf-8') def __unicode__(self): ret = "%s/%s" % (self.main_type, self.sub_type) diff --git a/rest_framework/views.py b/rest_framework/views.py index a2668f2c0..bca0aaef1 100644 --- a/rest_framework/views.py +++ b/rest_framework/views.py @@ -31,6 +31,7 @@ def get_view_name(view_cls, suffix=None): return name + def get_view_description(view_cls, html=False): """ Given a view class, return a textual description to represent the view. @@ -119,7 +120,6 @@ class APIView(View): headers['Vary'] = 'Accept' return headers - def http_method_not_allowed(self, request, *args, **kwargs): """ If `request.method` does not correspond to a handler method, diff --git a/rest_framework/viewsets.py b/rest_framework/viewsets.py index 7eb29f99b..bb5b304ee 100644 --- a/rest_framework/viewsets.py +++ b/rest_framework/viewsets.py @@ -127,11 +127,11 @@ class ReadOnlyModelViewSet(mixins.RetrieveModelMixin, class ModelViewSet(mixins.CreateModelMixin, - mixins.RetrieveModelMixin, - mixins.UpdateModelMixin, - mixins.DestroyModelMixin, - mixins.ListModelMixin, - GenericViewSet): + mixins.RetrieveModelMixin, + mixins.UpdateModelMixin, + mixins.DestroyModelMixin, + mixins.ListModelMixin, + GenericViewSet): """ A viewset that provides default `create()`, `retrieve()`, `update()`, `partial_update()`, `destroy()` and `list()` actions. diff --git a/runtests.py b/runtests.py new file mode 100755 index 000000000..4da05ac37 --- /dev/null +++ b/runtests.py @@ -0,0 +1,86 @@ +#! /usr/bin/env python +from __future__ import print_function + +import pytest +import sys +import os +import subprocess + + +PYTEST_ARGS = { + 'default': ['tests'], + 'fast': ['tests', '-q'], +} + +FLAKE8_ARGS = ['rest_framework', 'tests', '--ignore=E501'] + + +sys.path.append(os.path.dirname(__file__)) + +def exit_on_failure(ret, message=None): + if ret: + sys.exit(ret) + +def flake8_main(args): + print('Running flake8 code linting') + ret = subprocess.call(['flake8'] + args) + print('flake8 failed' if ret else 'flake8 passed') + return ret + +def split_class_and_function(string): + class_string, function_string = string.split('.', 1) + return "%s and %s" % (class_string, function_string) + +def is_function(string): + # `True` if it looks like a test function is included in the string. + return string.startswith('test_') or '.test_' in string + +def is_class(string): + # `True` if first character is uppercase - assume it's a class name. + return string[0] == string[0].upper() + + +if __name__ == "__main__": + try: + sys.argv.remove('--nolint') + except ValueError: + run_flake8 = True + else: + run_flake8 = False + + try: + sys.argv.remove('--lintonly') + except ValueError: + run_tests = True + else: + run_tests = False + + try: + sys.argv.remove('--fast') + except ValueError: + style = 'default' + else: + style = 'fast' + run_flake8 = False + + if len(sys.argv) > 1: + pytest_args = sys.argv[1:] + first_arg = pytest_args[0] + if first_arg.startswith('-'): + # `runtests.py [flags]` + pytest_args = ['tests'] + pytest_args + elif is_class(first_arg) and is_function(first_arg): + # `runtests.py TestCase.test_function [flags]` + expression = split_class_and_function(first_arg) + pytest_args = ['tests', '-k', expression] + pytest_args[1:] + elif is_class(first_arg) or is_function(first_arg): + # `runtests.py TestCase [flags]` + # `runtests.py test_function [flags]` + pytest_args = ['tests', '-k', pytest_args[0]] + pytest_args[1:] + else: + pytest_args = PYTEST_ARGS[style] + + if run_tests: + exit_on_failure(pytest.main(pytest_args)) + if run_flake8: + exit_on_failure(flake8_main(FLAKE8_ARGS)) diff --git a/conftest.py b/tests/conftest.py similarity index 92% rename from conftest.py rename to tests/conftest.py index fa5184dd8..f3723aeae 100644 --- a/conftest.py +++ b/tests/conftest.py @@ -47,8 +47,8 @@ def pytest_configure(): ) try: - import oauth_provider - import oauth2 + import oauth_provider # NOQA + import oauth2 # NOQA except ImportError: pass else: @@ -57,7 +57,7 @@ def pytest_configure(): ) try: - import provider + import provider # NOQA except ImportError: pass else: @@ -68,13 +68,13 @@ def pytest_configure(): # guardian is optional try: - import guardian + import guardian # NOQA except ImportError: pass else: settings.ANONYMOUS_USER_ID = -1 settings.AUTHENTICATION_BACKENDS = ( - 'django.contrib.auth.backends.ModelBackend', # default + 'django.contrib.auth.backends.ModelBackend', 'guardian.backends.ObjectPermissionBackend', ) settings.INSTALLED_APPS += ( diff --git a/tests/serializers.py b/tests/serializers.py index f2f85b6ea..be7b37722 100644 --- a/tests/serializers.py +++ b/tests/serializers.py @@ -1,5 +1,4 @@ from rest_framework import serializers - from tests.models import NullableForeignKeySource diff --git a/tests/settings.py b/tests/settings.py index de41dc66a..91c9ed09e 100644 --- a/tests/settings.py +++ b/tests/settings.py @@ -68,7 +68,6 @@ SECRET_KEY = 'u@x-aj9(hoh#rb-^ymf#g2jx_hp0vj7u5#b@ag1n^seu9e!%cy' TEMPLATE_LOADERS = ( 'django.template.loaders.filesystem.Loader', 'django.template.loaders.app_directories.Loader', -# 'django.template.loaders.eggs.Loader', ) MIDDLEWARE_CLASSES = ( @@ -104,8 +103,8 @@ INSTALLED_APPS = ( # OAuth is optional and won't work if there is no oauth_provider & oauth2 try: - import oauth_provider - import oauth2 + import oauth_provider # NOQA + import oauth2 # NOQA except ImportError: pass else: @@ -114,7 +113,7 @@ else: ) try: - import provider + import provider # NOQA except ImportError: pass else: @@ -125,13 +124,13 @@ else: # guardian is optional try: - import guardian + import guardian # NOQA except ImportError: pass else: ANONYMOUS_USER_ID = -1 AUTHENTICATION_BACKENDS = ( - 'django.contrib.auth.backends.ModelBackend', # default + 'django.contrib.auth.backends.ModelBackend', # default 'guardian.backends.ObjectPermissionBackend', ) INSTALLED_APPS += ( diff --git a/tests/test_authentication.py b/tests/test_authentication.py index f5bfc5e61..9db4f62df 100644 --- a/tests/test_authentication.py +++ b/tests/test_authentication.py @@ -45,26 +45,39 @@ class MockView(APIView): return HttpResponse({'a': 1, 'b': 2, 'c': 3}) -urlpatterns = patterns('', +urlpatterns = patterns( + '', (r'^session/$', MockView.as_view(authentication_classes=[SessionAuthentication])), (r'^basic/$', MockView.as_view(authentication_classes=[BasicAuthentication])), (r'^token/$', MockView.as_view(authentication_classes=[TokenAuthentication])), (r'^auth-token/$', 'rest_framework.authtoken.views.obtain_auth_token'), (r'^oauth/$', MockView.as_view(authentication_classes=[OAuthAuthentication])), - (r'^oauth-with-scope/$', MockView.as_view(authentication_classes=[OAuthAuthentication], - permission_classes=[permissions.TokenHasReadWriteScope])) + ( + r'^oauth-with-scope/$', + MockView.as_view( + authentication_classes=[OAuthAuthentication], + permission_classes=[permissions.TokenHasReadWriteScope] + ) + ) ) + class OAuth2AuthenticationDebug(OAuth2Authentication): allow_query_params_token = True if oauth2_provider is not None: - urlpatterns += patterns('', + urlpatterns += patterns( + '', url(r'^oauth2/', include('provider.oauth2.urls', namespace='oauth2')), url(r'^oauth2-test/$', MockView.as_view(authentication_classes=[OAuth2Authentication])), url(r'^oauth2-test-debug/$', MockView.as_view(authentication_classes=[OAuth2AuthenticationDebug])), - url(r'^oauth2-with-scope-test/$', MockView.as_view(authentication_classes=[OAuth2Authentication], - permission_classes=[permissions.TokenHasReadWriteScope])), + url( + r'^oauth2-with-scope-test/$', + MockView.as_view( + authentication_classes=[OAuth2Authentication], + permission_classes=[permissions.TokenHasReadWriteScope] + ) + ) ) @@ -278,12 +291,16 @@ class OAuthTests(TestCase): self.TOKEN_KEY = "token_key" self.TOKEN_SECRET = "token_secret" - self.consumer = Consumer.objects.create(key=self.CONSUMER_KEY, secret=self.CONSUMER_SECRET, - name='example', user=self.user, status=self.consts.ACCEPTED) + self.consumer = Consumer.objects.create( + key=self.CONSUMER_KEY, secret=self.CONSUMER_SECRET, + name='example', user=self.user, status=self.consts.ACCEPTED + ) self.scope = Scope.objects.create(name="resource name", url="api/") - self.token = OAuthToken.objects.create(user=self.user, consumer=self.consumer, scope=self.scope, - token_type=OAuthToken.ACCESS, key=self.TOKEN_KEY, secret=self.TOKEN_SECRET, is_approved=True + self.token = OAuthToken.objects.create( + user=self.user, consumer=self.consumer, scope=self.scope, + token_type=OAuthToken.ACCESS, key=self.TOKEN_KEY, secret=self.TOKEN_SECRET, + is_approved=True ) def _create_authorization_header(self): @@ -501,24 +518,24 @@ class OAuth2Tests(TestCase): self.REFRESH_TOKEN = "refresh_token" self.oauth2_client = oauth2_provider.oauth2.models.Client.objects.create( - client_id=self.CLIENT_ID, - client_secret=self.CLIENT_SECRET, - redirect_uri='', - client_type=0, - name='example', - user=None, - ) + client_id=self.CLIENT_ID, + client_secret=self.CLIENT_SECRET, + redirect_uri='', + client_type=0, + name='example', + user=None, + ) self.access_token = oauth2_provider.oauth2.models.AccessToken.objects.create( - token=self.ACCESS_TOKEN, - client=self.oauth2_client, - user=self.user, - ) + token=self.ACCESS_TOKEN, + client=self.oauth2_client, + user=self.user, + ) self.refresh_token = oauth2_provider.oauth2.models.RefreshToken.objects.create( - user=self.user, - access_token=self.access_token, - client=self.oauth2_client - ) + user=self.user, + access_token=self.access_token, + client=self.oauth2_client + ) def _create_authorization_header(self, token=None): return "Bearer {0}".format(token or self.access_token.token) @@ -569,8 +586,10 @@ class OAuth2Tests(TestCase): @unittest.skipUnless(oauth2_provider, 'django-oauth2-provider not installed') def test_post_form_passing_auth_url_transport(self): """Ensure GETing form over OAuth with correct client credentials in form data succeed""" - response = self.csrf_client.post('/oauth2-test/', - data={'access_token': self.access_token.token}) + response = self.csrf_client.post( + '/oauth2-test/', + data={'access_token': self.access_token.token} + ) self.assertEqual(response.status_code, 200) @unittest.skipUnless(oauth2_provider, 'django-oauth2-provider not installed') diff --git a/tests/test_breadcrumbs.py b/tests/test_breadcrumbs.py index f26c3eafe..780fd5c4d 100644 --- a/tests/test_breadcrumbs.py +++ b/tests/test_breadcrumbs.py @@ -24,7 +24,8 @@ class NestedResourceRoot(APIView): class NestedResourceInstance(APIView): pass -urlpatterns = patterns('', +urlpatterns = patterns( + '', url(r'^$', Root.as_view()), url(r'^resource/$', ResourceRoot.as_view()), url(r'^resource/(?P[0-9]+)$', ResourceInstance.as_view()), @@ -40,34 +41,60 @@ class BreadcrumbTests(TestCase): def test_root_breadcrumbs(self): url = '/' - self.assertEqual(get_breadcrumbs(url), [('Root', '/')]) + self.assertEqual( + get_breadcrumbs(url), + [('Root', '/')] + ) def test_resource_root_breadcrumbs(self): url = '/resource/' - self.assertEqual(get_breadcrumbs(url), [('Root', '/'), - ('Resource Root', '/resource/')]) + self.assertEqual( + get_breadcrumbs(url), + [ + ('Root', '/'), + ('Resource Root', '/resource/') + ] + ) def test_resource_instance_breadcrumbs(self): url = '/resource/123' - self.assertEqual(get_breadcrumbs(url), [('Root', '/'), - ('Resource Root', '/resource/'), - ('Resource Instance', '/resource/123')]) + self.assertEqual( + get_breadcrumbs(url), + [ + ('Root', '/'), + ('Resource Root', '/resource/'), + ('Resource Instance', '/resource/123') + ] + ) def test_nested_resource_breadcrumbs(self): url = '/resource/123/' - self.assertEqual(get_breadcrumbs(url), [('Root', '/'), - ('Resource Root', '/resource/'), - ('Resource Instance', '/resource/123'), - ('Nested Resource Root', '/resource/123/')]) + self.assertEqual( + get_breadcrumbs(url), + [ + ('Root', '/'), + ('Resource Root', '/resource/'), + ('Resource Instance', '/resource/123'), + ('Nested Resource Root', '/resource/123/') + ] + ) def test_nested_resource_instance_breadcrumbs(self): url = '/resource/123/abc' - self.assertEqual(get_breadcrumbs(url), [('Root', '/'), - ('Resource Root', '/resource/'), - ('Resource Instance', '/resource/123'), - ('Nested Resource Root', '/resource/123/'), - ('Nested Resource Instance', '/resource/123/abc')]) + self.assertEqual( + get_breadcrumbs(url), + [ + ('Root', '/'), + ('Resource Root', '/resource/'), + ('Resource Instance', '/resource/123'), + ('Nested Resource Root', '/resource/123/'), + ('Nested Resource Instance', '/resource/123/abc') + ] + ) def test_broken_url_breadcrumbs_handled_gracefully(self): url = '/foobar' - self.assertEqual(get_breadcrumbs(url), [('Root', '/')]) + self.assertEqual( + get_breadcrumbs(url), + [('Root', '/')] + ) diff --git a/tests/test_fields.py b/tests/test_fields.py index 97ef016fa..094ac1eb0 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -648,7 +648,7 @@ class DecimalFieldTest(TestCase): s = DecimalSerializer(data={'decimal_field': '123'}) self.assertFalse(s.is_valid()) - self.assertEqual(s.errors, {'decimal_field': ['Ensure this value is less than or equal to 100.']}) + self.assertEqual(s.errors, {'decimal_field': ['Ensure this value is less than or equal to 100.']}) def test_raise_min_value(self): """ @@ -660,7 +660,7 @@ class DecimalFieldTest(TestCase): s = DecimalSerializer(data={'decimal_field': '99'}) self.assertFalse(s.is_valid()) - self.assertEqual(s.errors, {'decimal_field': ['Ensure this value is greater than or equal to 100.']}) + self.assertEqual(s.errors, {'decimal_field': ['Ensure this value is greater than or equal to 100.']}) def test_raise_max_digits(self): """ @@ -672,7 +672,7 @@ class DecimalFieldTest(TestCase): s = DecimalSerializer(data={'decimal_field': '123.456'}) self.assertFalse(s.is_valid()) - self.assertEqual(s.errors, {'decimal_field': ['Ensure that there are no more than 5 digits in total.']}) + self.assertEqual(s.errors, {'decimal_field': ['Ensure that there are no more than 5 digits in total.']}) def test_raise_max_decimal_places(self): """ @@ -684,7 +684,7 @@ class DecimalFieldTest(TestCase): s = DecimalSerializer(data={'decimal_field': '123.4567'}) self.assertFalse(s.is_valid()) - self.assertEqual(s.errors, {'decimal_field': ['Ensure that there are no more than 3 decimal places.']}) + self.assertEqual(s.errors, {'decimal_field': ['Ensure that there are no more than 3 decimal places.']}) def test_raise_max_whole_digits(self): """ @@ -696,7 +696,7 @@ class DecimalFieldTest(TestCase): s = DecimalSerializer(data={'decimal_field': '12345.6'}) self.assertFalse(s.is_valid()) - self.assertEqual(s.errors, {'decimal_field': ['Ensure that there are no more than 4 digits in total.']}) + self.assertEqual(s.errors, {'decimal_field': ['Ensure that there are no more than 4 digits in total.']}) class ChoiceFieldTests(TestCase): @@ -729,7 +729,7 @@ class ChoiceFieldTests(TestCase): def test_invalid_choice_model(self): s = ChoiceFieldModelSerializer(data={'choice': 'wrong_value'}) self.assertFalse(s.is_valid()) - self.assertEqual(s.errors, {'choice': ['Select a valid choice. wrong_value is not one of the available choices.']}) + self.assertEqual(s.errors, {'choice': ['Select a valid choice. wrong_value is not one of the available choices.']}) self.assertEqual(s.data['choice'], '') def test_empty_choice_model(self): @@ -875,7 +875,7 @@ class SlugFieldTests(TestCase): s = SlugFieldSerializer(data={'slug_field': 'a b'}) self.assertEqual(s.is_valid(), False) - self.assertEqual(s.errors, {'slug_field': ["Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."]}) + self.assertEqual(s.errors, {'slug_field': ["Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."]}) class URLFieldTests(TestCase): diff --git a/tests/test_files.py b/tests/test_files.py index 78f4cf425..af110df91 100644 --- a/tests/test_files.py +++ b/tests/test_files.py @@ -85,11 +85,8 @@ class FileSerializerTests(TestCase): """ Validation should still function when no data dictionary is provided. """ - now = datetime.datetime.now() - file = BytesIO(six.b('stuff')) - file.name = 'stuff.txt' - file.size = len(file.getvalue()) - uploaded_file = UploadedFile(file=file, created=now) - - serializer = UploadedFileSerializer(files={'file': file}) + uploaded_file = BytesIO(six.b('stuff')) + uploaded_file.name = 'stuff.txt' + uploaded_file.size = len(uploaded_file.getvalue()) + serializer = UploadedFileSerializer(files={'file': uploaded_file}) self.assertFalse(serializer.is_valid()) diff --git a/tests/test_filters.py b/tests/test_filters.py index 85840e018..b29760fa4 100644 --- a/tests/test_filters.py +++ b/tests/test_filters.py @@ -74,7 +74,8 @@ if django_filters: def get_queryset(self): return FilterableItem.objects.all() - urlpatterns = patterns('', + urlpatterns = patterns( + '', url(r'^(?P\d+)/$', FilterClassDetailView.as_view(), name='detail-view'), url(r'^$', FilterClassRootView.as_view(), name='root-view'), url(r'^get-queryset/$', GetQuerysetView.as_view(), @@ -653,8 +654,8 @@ class SensitiveOrderingFilterTests(TestCase): self.assertEqual( response.data, [ - {'id': 1, username_field: 'userA'}, # PassB - {'id': 2, username_field: 'userB'}, # PassC - {'id': 3, username_field: 'userC'}, # PassA + {'id': 1, username_field: 'userA'}, # PassB + {'id': 2, username_field: 'userB'}, # PassC + {'id': 3, username_field: 'userC'}, # PassA ] ) diff --git a/tests/test_genericrelations.py b/tests/test_genericrelations.py index 3a8f3c7f1..95295eaa8 100644 --- a/tests/test_genericrelations.py +++ b/tests/test_genericrelations.py @@ -117,18 +117,18 @@ class TestGenericRelations(TestCase): serializer = TagSerializer(Tag.objects.all(), many=True) expected = [ - { - 'tag': 'django', - 'tagged_item': 'Bookmark: https://www.djangoproject.com/' - }, - { - 'tag': 'python', - 'tagged_item': 'Bookmark: https://www.djangoproject.com/' - }, - { - 'tag': 'reminder', - 'tagged_item': 'Note: Remember the milk' - } + { + 'tag': 'django', + 'tagged_item': 'Bookmark: https://www.djangoproject.com/' + }, + { + 'tag': 'python', + 'tagged_item': 'Bookmark: https://www.djangoproject.com/' + }, + { + 'tag': 'reminder', + 'tagged_item': 'Note: Remember the milk' + } ] self.assertEqual(serializer.data, expected) diff --git a/tests/test_htmlrenderer.py b/tests/test_htmlrenderer.py index 88d11c46b..5a680f99b 100644 --- a/tests/test_htmlrenderer.py +++ b/tests/test_htmlrenderer.py @@ -34,7 +34,8 @@ def not_found(request): raise Http404() -urlpatterns = patterns('', +urlpatterns = patterns( + '', url(r'^$', example), url(r'^permission_denied$', permission_denied), url(r'^not_found$', not_found), diff --git a/tests/test_hyperlinkedserializers.py b/tests/test_hyperlinkedserializers.py index d478ea730..d45485391 100644 --- a/tests/test_hyperlinkedserializers.py +++ b/tests/test_hyperlinkedserializers.py @@ -94,7 +94,8 @@ class OptionalRelationDetail(generics.RetrieveUpdateDestroyAPIView): model_serializer_class = serializers.HyperlinkedModelSerializer -urlpatterns = patterns('', +urlpatterns = patterns( + '', url(r'^basic/$', BasicList.as_view(), name='basicmodel-list'), url(r'^basic/(?P\d+)/$', BasicDetail.as_view(), name='basicmodel-detail'), url(r'^anchor/(?P\d+)/$', AnchorDetail.as_view(), name='anchor-detail'), diff --git a/tests/test_pagination.py b/tests/test_pagination.py index 293146c08..80c33e2eb 100644 --- a/tests/test_pagination.py +++ b/tests/test_pagination.py @@ -1,7 +1,6 @@ from __future__ import unicode_literals import datetime from decimal import Decimal -from django.db import models from django.core.paginator import Paginator from django.test import TestCase from django.utils import unittest @@ -12,6 +11,7 @@ from .models import BasicModel, FilterableItem factory = APIRequestFactory() + # Helper function to split arguments out of an url def split_arguments_from_url(url): if '?' not in url: @@ -274,8 +274,8 @@ class TestUnpaginated(TestCase): BasicModel(text=i).save() self.objects = BasicModel.objects self.data = [ - {'id': obj.id, 'text': obj.text} - for obj in self.objects.all() + {'id': obj.id, 'text': obj.text} + for obj in self.objects.all() ] self.view = DefaultPageSizeKwargView.as_view() @@ -302,8 +302,8 @@ class TestCustomPaginateByParam(TestCase): BasicModel(text=i).save() self.objects = BasicModel.objects self.data = [ - {'id': obj.id, 'text': obj.text} - for obj in self.objects.all() + {'id': obj.id, 'text': obj.text} + for obj in self.objects.all() ] self.view = PaginateByParamView.as_view() @@ -363,11 +363,11 @@ class TestMaxPaginateByParam(TestCase): self.assertEqual(response.data['results'], self.data[:3]) -### Tests for context in pagination serializers +# Tests for context in pagination serializers class CustomField(serializers.Field): def to_native(self, value): - if not 'view' in self.context: + if 'view' not in self.context: raise RuntimeError("context isn't getting passed into custom field") return "value" @@ -377,7 +377,7 @@ class BasicModelSerializer(serializers.Serializer): def __init__(self, *args, **kwargs): super(BasicModelSerializer, self).__init__(*args, **kwargs) - if not 'view' in self.context: + if 'view' not in self.context: raise RuntimeError("context isn't getting passed into serializer init") @@ -398,7 +398,7 @@ class TestContextPassedToCustomField(TestCase): self.assertEqual(response.status_code, status.HTTP_200_OK) -### Tests for custom pagination serializers +# Tests for custom pagination serializers class LinksSerializer(serializers.Serializer): next = pagination.NextPageField(source='*') @@ -483,8 +483,6 @@ class NonIntegerPaginator(object): class TestNonIntegerPagination(TestCase): - - def test_custom_pagination_serializer(self): objects = ['john', 'paul', 'george', 'ringo'] paginator = NonIntegerPaginator(objects, 2) diff --git a/tests/test_permissions.py b/tests/test_permissions.py index a2cb0c362..93f8020f3 100644 --- a/tests/test_permissions.py +++ b/tests/test_permissions.py @@ -12,6 +12,7 @@ import base64 factory = APIRequestFactory() + class RootView(generics.ListCreateAPIView): model = BasicModel authentication_classes = [authentication.BasicAuthentication] @@ -101,42 +102,54 @@ class ModelPermissionsIntegrationTests(TestCase): self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) def test_options_permitted(self): - request = factory.options('/', - HTTP_AUTHORIZATION=self.permitted_credentials) + 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) + 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) + 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) + 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) + 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) + 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) @@ -153,6 +166,7 @@ class BasicPermModel(models.Model): # add, change, delete built in to django ) + # Custom object-level permission, that includes 'view' permissions class ViewObjectPermissions(permissions.DjangoObjectPermissions): perms_map = { @@ -205,7 +219,7 @@ class ObjectPermissionsIntegrationTests(TestCase): app_label = BasicPermModel._meta.app_label f = '{0}_{1}'.format perms = { - 'view': f('view', model_name), + 'view': f('view', model_name), 'change': f('change', model_name), 'delete': f('delete', model_name) } @@ -246,21 +260,27 @@ class ObjectPermissionsIntegrationTests(TestCase): # Update def test_can_update_permissions(self): - request = factory.patch('/1', {'text': 'foobar'}, format='json', - HTTP_AUTHORIZATION=self.credentials['writeonly']) + request = factory.patch( + '/1', {'text': 'foobar'}, format='json', + HTTP_AUTHORIZATION=self.credentials['writeonly'] + ) response = object_permissions_view(request, pk='1') self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data.get('text'), 'foobar') def test_cannot_update_permissions(self): - request = factory.patch('/1', {'text': 'foobar'}, format='json', - HTTP_AUTHORIZATION=self.credentials['deleteonly']) + request = factory.patch( + '/1', {'text': 'foobar'}, format='json', + HTTP_AUTHORIZATION=self.credentials['deleteonly'] + ) response = object_permissions_view(request, pk='1') self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) def test_cannot_update_permissions_non_existing(self): - request = factory.patch('/999', {'text': 'foobar'}, format='json', - HTTP_AUTHORIZATION=self.credentials['deleteonly']) + request = factory.patch( + '/999', {'text': 'foobar'}, format='json', + HTTP_AUTHORIZATION=self.credentials['deleteonly'] + ) response = object_permissions_view(request, pk='999') self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) diff --git a/tests/test_relations.py b/tests/test_relations.py index cd276d30c..bc1db69fc 100644 --- a/tests/test_relations.py +++ b/tests/test_relations.py @@ -108,19 +108,25 @@ class RelatedFieldSourceTests(TestCase): doesn't exist. """ from tests.models import ManyToManySource + class Meta: model = ManyToManySource + attrs = { 'name': serializers.SlugRelatedField( slug_field='name', source='banzai'), 'Meta': Meta, } - TestSerializer = type(str('TestSerializer'), - (serializers.ModelSerializer,), attrs) + TestSerializer = type( + str('TestSerializer'), + (serializers.ModelSerializer,), + attrs + ) with self.assertRaises(AttributeError): TestSerializer(data={'name': 'foo'}) + @unittest.skipIf(get_version() < '1.6.0', 'Upstream behaviour changed in v1.6') class RelatedFieldChoicesTests(TestCase): """ @@ -141,4 +147,3 @@ class RelatedFieldChoicesTests(TestCase): widget_count = len(field.widget.choices) self.assertEqual(widget_count, choice_count + 1, 'BLANK_CHOICE_DASH option should have been added') - diff --git a/tests/test_relations_hyperlink.py b/tests/test_relations_hyperlink.py index ab1c66646..0c8eb2544 100644 --- a/tests/test_relations_hyperlink.py +++ b/tests/test_relations_hyperlink.py @@ -16,7 +16,8 @@ request = factory.get('/') # Just to ensure we have a request in the serializer def dummy_view(request, pk): pass -urlpatterns = patterns('', +urlpatterns = patterns( + '', url(r'^dummyurl/(?P[0-9]+)/$', dummy_view, name='dummy-url'), url(r'^manytomanysource/(?P[0-9]+)/$', dummy_view, name='manytomanysource-detail'), url(r'^manytomanytarget/(?P[0-9]+)/$', dummy_view, name='manytomanytarget-detail'), @@ -86,9 +87,9 @@ class HyperlinkedManyToManyTests(TestCase): queryset = ManyToManySource.objects.all() serializer = ManyToManySourceSerializer(queryset, many=True, context={'request': request}) expected = [ - {'url': 'http://testserver/manytomanysource/1/', 'name': 'source-1', 'targets': ['http://testserver/manytomanytarget/1/']}, - {'url': 'http://testserver/manytomanysource/2/', 'name': 'source-2', 'targets': ['http://testserver/manytomanytarget/1/', 'http://testserver/manytomanytarget/2/']}, - {'url': 'http://testserver/manytomanysource/3/', 'name': 'source-3', 'targets': ['http://testserver/manytomanytarget/1/', 'http://testserver/manytomanytarget/2/', 'http://testserver/manytomanytarget/3/']} + {'url': 'http://testserver/manytomanysource/1/', 'name': 'source-1', 'targets': ['http://testserver/manytomanytarget/1/']}, + {'url': 'http://testserver/manytomanysource/2/', 'name': 'source-2', 'targets': ['http://testserver/manytomanytarget/1/', 'http://testserver/manytomanytarget/2/']}, + {'url': 'http://testserver/manytomanysource/3/', 'name': 'source-3', 'targets': ['http://testserver/manytomanytarget/1/', 'http://testserver/manytomanytarget/2/', 'http://testserver/manytomanytarget/3/']} ] self.assertEqual(serializer.data, expected) @@ -114,9 +115,9 @@ class HyperlinkedManyToManyTests(TestCase): queryset = ManyToManySource.objects.all() serializer = ManyToManySourceSerializer(queryset, many=True, context={'request': request}) expected = [ - {'url': 'http://testserver/manytomanysource/1/', 'name': 'source-1', 'targets': ['http://testserver/manytomanytarget/1/', 'http://testserver/manytomanytarget/2/', 'http://testserver/manytomanytarget/3/']}, - {'url': 'http://testserver/manytomanysource/2/', 'name': 'source-2', 'targets': ['http://testserver/manytomanytarget/1/', 'http://testserver/manytomanytarget/2/']}, - {'url': 'http://testserver/manytomanysource/3/', 'name': 'source-3', 'targets': ['http://testserver/manytomanytarget/1/', 'http://testserver/manytomanytarget/2/', 'http://testserver/manytomanytarget/3/']} + {'url': 'http://testserver/manytomanysource/1/', 'name': 'source-1', 'targets': ['http://testserver/manytomanytarget/1/', 'http://testserver/manytomanytarget/2/', 'http://testserver/manytomanytarget/3/']}, + {'url': 'http://testserver/manytomanysource/2/', 'name': 'source-2', 'targets': ['http://testserver/manytomanytarget/1/', 'http://testserver/manytomanytarget/2/']}, + {'url': 'http://testserver/manytomanysource/3/', 'name': 'source-3', 'targets': ['http://testserver/manytomanytarget/1/', 'http://testserver/manytomanytarget/2/', 'http://testserver/manytomanytarget/3/']} ] self.assertEqual(serializer.data, expected) diff --git a/tests/test_relations_pk.py b/tests/test_relations_pk.py index ff59b250a..c051b0769 100644 --- a/tests/test_relations_pk.py +++ b/tests/test_relations_pk.py @@ -65,9 +65,9 @@ class PKManyToManyTests(TestCase): queryset = ManyToManySource.objects.all() serializer = ManyToManySourceSerializer(queryset, many=True) expected = [ - {'id': 1, 'name': 'source-1', 'targets': [1]}, - {'id': 2, 'name': 'source-2', 'targets': [1, 2]}, - {'id': 3, 'name': 'source-3', 'targets': [1, 2, 3]} + {'id': 1, 'name': 'source-1', 'targets': [1]}, + {'id': 2, 'name': 'source-2', 'targets': [1, 2]}, + {'id': 3, 'name': 'source-3', 'targets': [1, 2, 3]} ] self.assertEqual(serializer.data, expected) @@ -93,9 +93,9 @@ class PKManyToManyTests(TestCase): queryset = ManyToManySource.objects.all() serializer = ManyToManySourceSerializer(queryset, many=True) expected = [ - {'id': 1, 'name': 'source-1', 'targets': [1, 2, 3]}, - {'id': 2, 'name': 'source-2', 'targets': [1, 2]}, - {'id': 3, 'name': 'source-3', 'targets': [1, 2, 3]} + {'id': 1, 'name': 'source-1', 'targets': [1, 2, 3]}, + {'id': 2, 'name': 'source-2', 'targets': [1, 2]}, + {'id': 3, 'name': 'source-3', 'targets': [1, 2, 3]} ] self.assertEqual(serializer.data, expected) diff --git a/tests/test_renderers.py b/tests/test_renderers.py index 1d8adfa74..0403cde28 100644 --- a/tests/test_renderers.py +++ b/tests/test_renderers.py @@ -76,7 +76,6 @@ class MockGETView(APIView): return Response({'foo': ['bar', 'baz']}) - class MockPOSTView(APIView): def post(self, request, **kwargs): return Response({'foo': request.DATA}) @@ -102,7 +101,8 @@ class HTMLView1(APIView): def get(self, request, **kwargs): return Response('text') -urlpatterns = patterns('', +urlpatterns = patterns( + '', url(r'^.*\.(?P.+)$', MockView.as_view(renderer_classes=[RendererA, RendererB])), url(r'^$', MockView.as_view(renderer_classes=[RendererA, RendererB])), url(r'^cache$', MockGETView.as_view()), @@ -312,16 +312,22 @@ class JSONRendererTests(TestCase): class Dict(MutableMapping): def __init__(self): self._dict = dict() + def __getitem__(self, key): return self._dict.__getitem__(key) + def __setitem__(self, key, value): return self._dict.__setitem__(key, value) + def __delitem__(self, key): return self._dict.__delitem__(key) + def __iter__(self): return self._dict.__iter__() + def __len__(self): return self._dict.__len__() + def keys(self): return self._dict.keys() @@ -330,22 +336,24 @@ class JSONRendererTests(TestCase): x[2] = 3 ret = JSONRenderer().render(x) data = json.loads(ret.decode('utf-8')) - self.assertEquals(data, {'key': 'string value', '2': 3}) + self.assertEquals(data, {'key': 'string value', '2': 3}) def test_render_obj_with_getitem(self): class DictLike(object): def __init__(self): self._dict = {} + def set(self, value): self._dict = dict(value) + def __getitem__(self, key): return self._dict[key] - + x = DictLike() x.set({'a': 1, 'b': 'string'}) with self.assertRaises(TypeError): JSONRenderer().render(x) - + def test_without_content_type_args(self): """ Test basic JSON rendering. @@ -394,35 +402,47 @@ class JSONPRendererTests(TestCase): """ Test JSONP rendering with View JSON Renderer. """ - resp = self.client.get('/jsonp/jsonrenderer', - HTTP_ACCEPT='application/javascript') + resp = self.client.get( + '/jsonp/jsonrenderer', + HTTP_ACCEPT='application/javascript' + ) self.assertEqual(resp.status_code, status.HTTP_200_OK) self.assertEqual(resp['Content-Type'], 'application/javascript; charset=utf-8') - self.assertEqual(resp.content, - ('callback(%s);' % _flat_repr).encode('ascii')) + self.assertEqual( + resp.content, + ('callback(%s);' % _flat_repr).encode('ascii') + ) def test_without_callback_without_json_renderer(self): """ Test JSONP rendering without View JSON Renderer. """ - resp = self.client.get('/jsonp/nojsonrenderer', - HTTP_ACCEPT='application/javascript') + resp = self.client.get( + '/jsonp/nojsonrenderer', + HTTP_ACCEPT='application/javascript' + ) self.assertEqual(resp.status_code, status.HTTP_200_OK) self.assertEqual(resp['Content-Type'], 'application/javascript; charset=utf-8') - self.assertEqual(resp.content, - ('callback(%s);' % _flat_repr).encode('ascii')) + self.assertEqual( + resp.content, + ('callback(%s);' % _flat_repr).encode('ascii') + ) def test_with_callback(self): """ Test JSONP rendering with callback function name. """ callback_func = 'myjsonpcallback' - resp = self.client.get('/jsonp/nojsonrenderer?callback=' + callback_func, - HTTP_ACCEPT='application/javascript') + resp = self.client.get( + '/jsonp/nojsonrenderer?callback=' + callback_func, + HTTP_ACCEPT='application/javascript' + ) self.assertEqual(resp.status_code, status.HTTP_200_OK) self.assertEqual(resp['Content-Type'], 'application/javascript; charset=utf-8') - self.assertEqual(resp.content, - ('%s(%s);' % (callback_func, _flat_repr)).encode('ascii')) + self.assertEqual( + resp.content, + ('%s(%s);' % (callback_func, _flat_repr)).encode('ascii') + ) if yaml: @@ -467,7 +487,6 @@ if yaml: def assertYAMLContains(self, content, string): self.assertTrue(string in content, '%r not in %r' % (string, content)) - class UnicodeYAMLRendererTests(TestCase): """ Tests specific for the Unicode YAML Renderer @@ -592,13 +611,13 @@ class CacheRenderTest(TestCase): """ Return any errors that would be raised if `obj' is pickled Courtesy of koffie @ http://stackoverflow.com/a/7218986/109897 """ - if seen == None: + if seen is None: seen = [] try: state = obj.__getstate__() except AttributeError: return - if state == None: + if state is None: return if isinstance(state, tuple): if not isinstance(state[0], dict): diff --git a/tests/test_request.py b/tests/test_request.py index 0cde0fb40..8b048b5c2 100644 --- a/tests/test_request.py +++ b/tests/test_request.py @@ -272,7 +272,8 @@ class MockView(APIView): return Response(status=status.INTERNAL_SERVER_ERROR) -urlpatterns = patterns('', +urlpatterns = patterns( + '', (r'^$', MockView.as_view()), ) diff --git a/tests/test_response.py b/tests/test_response.py index 0551f4a80..c28f186e0 100644 --- a/tests/test_response.py +++ b/tests/test_response.py @@ -100,7 +100,8 @@ new_model_viewset_router = routers.DefaultRouter() new_model_viewset_router.register(r'', HTMLNewModelViewSet) -urlpatterns = patterns('', +urlpatterns = patterns( + '', url(r'^setbyview$', MockViewSettingContentType.as_view(renderer_classes=[RendererA, RendererB, RendererC])), url(r'^.*\.(?P.+)$', MockView.as_view(renderer_classes=[RendererA, RendererB, RendererC])), url(r'^$', MockView.as_view(renderer_classes=[RendererA, RendererB, RendererC])), diff --git a/tests/test_reverse.py b/tests/test_reverse.py index 0d3fddf07..675a9d5a0 100644 --- a/tests/test_reverse.py +++ b/tests/test_reverse.py @@ -10,7 +10,8 @@ factory = APIRequestFactory() def null_view(request): pass -urlpatterns = patterns('', +urlpatterns = patterns( + '', url(r'^view$', null_view, name='view'), ) diff --git a/tests/test_routers.py b/tests/test_routers.py index 381569bde..b076f134e 100644 --- a/tests/test_routers.py +++ b/tests/test_routers.py @@ -93,7 +93,8 @@ class TestCustomLookupFields(TestCase): from tests import test_routers urls = getattr(test_routers, 'urlpatterns') - urls += patterns('', + urls += patterns( + '', url(r'^', include(self.router.urls)), ) @@ -104,7 +105,8 @@ class TestCustomLookupFields(TestCase): def test_retrieve_lookup_field_list_view(self): response = self.client.get('/notes/') - self.assertEqual(response.data, + self.assertEqual( + response.data, [{ "url": "http://testserver/notes/123/", "uuid": "123", "text": "foo bar" @@ -113,7 +115,8 @@ class TestCustomLookupFields(TestCase): def test_retrieve_lookup_field_detail_view(self): response = self.client.get('/notes/123/') - self.assertEqual(response.data, + self.assertEqual( + response.data, { "url": "http://testserver/notes/123/", "uuid": "123", "text": "foo bar" diff --git a/tests/test_serializer.py b/tests/test_serializer.py index 7d57fcf01..90f37cf2e 100644 --- a/tests/test_serializer.py +++ b/tests/test_serializer.py @@ -7,10 +7,12 @@ from django.utils import unittest from django.utils.datastructures import MultiValueDict from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers, fields, relations -from tests.models import (HasPositiveIntegerAsChoice, Album, ActionItem, Anchor, BasicModel, - BlankFieldModel, BlogPost, BlogPostComment, Book, CallableDefaultValueModel, DefaultValueModel, - ManyToManyModel, Person, ReadOnlyManyToManyModel, Photo, RESTFrameworkModel, - ForeignKeySource, ManyToManySource) +from tests.models import ( + HasPositiveIntegerAsChoice, Album, ActionItem, Anchor, BasicModel, + BlankFieldModel, BlogPost, BlogPostComment, Book, CallableDefaultValueModel, + DefaultValueModel, ManyToManyModel, Person, ReadOnlyManyToManyModel, Photo, + RESTFrameworkModel, ForeignKeySource +) from tests.models import BasicModelSerializer import datetime import pickle @@ -99,6 +101,7 @@ class ActionItemSerializer(serializers.ModelSerializer): class Meta: model = ActionItem + class ActionItemSerializerOptionalFields(serializers.ModelSerializer): """ Intended to test that fields with `required=False` are excluded from validation. @@ -109,6 +112,7 @@ class ActionItemSerializerOptionalFields(serializers.ModelSerializer): model = ActionItem fields = ('title',) + class ActionItemSerializerCustomRestore(serializers.ModelSerializer): class Meta: @@ -295,8 +299,10 @@ class BasicTests(TestCase): in the Meta data """ serializer = PersonSerializer(self.person) - self.assertEqual(set(serializer.data.keys()), - set(['name', 'age', 'info'])) + self.assertEqual( + set(serializer.data.keys()), + set(['name', 'age', 'info']) + ) def test_field_with_dictionary(self): """ @@ -331,9 +337,9 @@ class BasicTests(TestCase): — id field is not populated if `data` is accessed prior to `save()` """ serializer = ActionItemSerializer(self.actionitem) - self.assertIsNone(serializer.data.get('id',None), 'New instance. `id` should not be set.') + self.assertIsNone(serializer.data.get('id', None), 'New instance. `id` should not be set.') serializer.save() - self.assertIsNotNone(serializer.data.get('id',None), 'Model is saved. `id` should be set.') + self.assertIsNotNone(serializer.data.get('id', None), 'Model is saved. `id` should be set.') def test_fields_marked_as_not_required_are_excluded_from_validation(self): """ @@ -409,7 +415,7 @@ class ValidationTests(TestCase): mistaken for not having a default.""" data = { 'title': 'Some action item', - #No 'done' value. + # No 'done' value. } serializer = ActionItemSerializer(self.actionitem, data=data) self.assertEqual(serializer.is_valid(), True) @@ -660,10 +666,10 @@ class ModelValidationTests(TestCase): serializer.save() second_serializer = AlbumsSerializer(data={'title': 'a'}) self.assertFalse(second_serializer.is_valid()) - self.assertEqual(second_serializer.errors, {'title': ['Album with this Title already exists.'],}) + self.assertEqual(second_serializer.errors, {'title': ['Album with this Title already exists.']}) third_serializer = AlbumsSerializer(data=[{'title': 'b', 'ref': '1'}, {'title': 'c'}], many=True) self.assertFalse(third_serializer.is_valid()) - self.assertEqual(third_serializer.errors, [{'ref': ['Album with this Ref already exists.']}, {}]) + self.assertEqual(third_serializer.errors, [{'ref': ['Album with this Ref already exists.']}, {}]) def test_foreign_key_is_null_with_partial(self): """ @@ -959,7 +965,7 @@ class WritableFieldDefaultValueTests(TestCase): self.assertEqual(got, self.expected) def test_get_default_value_with_callable(self): - field = self.create_field(default=lambda : self.expected) + field = self.create_field(default=lambda: self.expected) got = field.get_default_value() self.assertEqual(got, self.expected) @@ -974,7 +980,7 @@ class WritableFieldDefaultValueTests(TestCase): self.assertIsNone(got) def test_get_default_value_returns_non_True_values(self): - values = [None, '', False, 0, [], (), {}] # values that assumed as 'False' in the 'if' clause + values = [None, '', False, 0, [], (), {}] # values that assumed as 'False' in the 'if' clause for expected in values: field = self.create_field(default=expected) got = field.get_default_value() @@ -1276,7 +1282,7 @@ class BlankFieldTests(TestCase): self.fail('Exception raised on save() after validation passes') -#test for issue #460 +# Test for issue #460 class SerializerPickleTests(TestCase): """ Test pickleability of the output of Serializers @@ -1500,7 +1506,7 @@ class NestedSerializerContextTests(TestCase): callable = serializers.SerializerMethodField('_callable') def _callable(self, instance): - if not 'context_item' in self.context: + if 'context_item' not in self.context: raise RuntimeError("context isn't getting passed into 2nd level nested serializer") return "success" @@ -1513,7 +1519,7 @@ class NestedSerializerContextTests(TestCase): callable = serializers.SerializerMethodField("_callable") def _callable(self, instance): - if not 'context_item' in self.context: + if 'context_item' not in self.context: raise RuntimeError("context isn't getting passed into 1st level nested serializer") return "success" @@ -1816,7 +1822,7 @@ class MetadataSerializerTestCase(TestCase): self.assertEqual(expected, metadata) -### Regression test for #840 +# Regression test for #840 class SimpleModel(models.Model): text = models.CharField(max_length=100) @@ -1850,7 +1856,7 @@ class FieldValidationRemovingAttr(TestCase): self.assertEqual(serializer.object.text, 'foo') -### Regression test for #878 +# Regression test for #878 class SimpleTargetModel(models.Model): text = models.CharField(max_length=100) diff --git a/tests/test_serializer_bulk_update.py b/tests/test_serializer_bulk_update.py index 8b0ded1a8..67a8ed0dc 100644 --- a/tests/test_serializer_bulk_update.py +++ b/tests/test_serializer_bulk_update.py @@ -83,9 +83,9 @@ class BulkCreateSerializerTests(TestCase): self.assertEqual(serializer.is_valid(), False) expected_errors = [ - {'non_field_errors': ['Invalid data']}, - {'non_field_errors': ['Invalid data']}, - {'non_field_errors': ['Invalid data']} + {'non_field_errors': ['Invalid data']}, + {'non_field_errors': ['Invalid data']}, + {'non_field_errors': ['Invalid data']} ] self.assertEqual(serializer.errors, expected_errors) diff --git a/tests/test_serializer_nested.py b/tests/test_serializer_nested.py index 6d69ffbd0..c09c24db2 100644 --- a/tests/test_serializer_nested.py +++ b/tests/test_serializer_nested.py @@ -328,12 +328,14 @@ class NestedModelSerializerUpdateTests(TestCase): class BlogPostSerializer(serializers.ModelSerializer): comments = BlogPostCommentSerializer(many=True, source='blogpostcomment_set') + class Meta: model = models.BlogPost fields = ('id', 'title', 'comments') class PersonSerializer(serializers.ModelSerializer): posts = BlogPostSerializer(many=True, source='blogpost_set') + class Meta: model = models.Person fields = ('id', 'name', 'age', 'posts') diff --git a/tests/test_serializers.py b/tests/test_serializers.py index 2e276f15c..09de9f4c1 100644 --- a/tests/test_serializers.py +++ b/tests/test_serializers.py @@ -1,9 +1,7 @@ -from django.db import models from django.test import TestCase - +from rest_framework.compat import six from rest_framework.serializers import _resolve_model from tests.models import BasicModel -from rest_framework.compat import six class ResolveModelTests(TestCase): diff --git a/tests/test_status.py b/tests/test_status.py index 7b1bdae31..721a6e30b 100644 --- a/tests/test_status.py +++ b/tests/test_status.py @@ -30,4 +30,4 @@ class TestStatus(TestCase): self.assertFalse(is_server_error(499)) self.assertTrue(is_server_error(500)) self.assertTrue(is_server_error(599)) - self.assertFalse(is_server_error(600)) \ No newline at end of file + self.assertFalse(is_server_error(600)) diff --git a/tests/test_templatetags.py b/tests/test_templatetags.py index d4da0c23b..75ee0eaa8 100644 --- a/tests/test_templatetags.py +++ b/tests/test_templatetags.py @@ -11,7 +11,7 @@ class TemplateTagTests(TestCase): def test_add_query_param_with_non_latin_charactor(self): # Ensure we don't double-escape non-latin characters - # that are present in the querystring. + # that are present in the querystring. # See #1314. request = factory.get("/", {'q': '查询'}) json_url = add_query_param(request, "format", "json") @@ -48,4 +48,4 @@ class Issue1386Tests(TestCase): self.assertEqual(i, res) # example from issue #1386, this shouldn't raise an exception - _ = urlize_quoted_links("asdf:[/p]zxcv.com") + urlize_quoted_links("asdf:[/p]zxcv.com") diff --git a/tests/test_testing.py b/tests/test_testing.py index 1b126e002..9c472026a 100644 --- a/tests/test_testing.py +++ b/tests/test_testing.py @@ -28,7 +28,8 @@ def session_view(request): }) -urlpatterns = patterns('', +urlpatterns = patterns( + '', url(r'^view/$', view), url(r'^session-view/$', session_view), ) @@ -142,7 +143,8 @@ class TestAPIRequestFactory(TestCase): assertion error. """ factory = APIRequestFactory() - self.assertRaises(AssertionError, factory.post, + self.assertRaises( + AssertionError, factory.post, path='/view/', data={'example': 1}, format='xml' ) diff --git a/tests/test_throttling.py b/tests/test_throttling.py index 8c5eefe9c..b0cb2fe73 100644 --- a/tests/test_throttling.py +++ b/tests/test_throttling.py @@ -27,7 +27,7 @@ class NonTimeThrottle(BaseThrottle): if not hasattr(self.__class__, 'called'): self.__class__.called = True return True - return False + return False class MockView(APIView): @@ -125,36 +125,42 @@ class ThrottlingTests(TestCase): """ Ensure for second based throttles. """ - self.ensure_response_header_contains_proper_throttle_field(MockView, - ((0, None), - (0, None), - (0, None), - (0, '1') - )) + self.ensure_response_header_contains_proper_throttle_field( + MockView, ( + (0, None), + (0, None), + (0, None), + (0, '1') + ) + ) def test_minutes_fields(self): """ Ensure for minute based throttles. """ - self.ensure_response_header_contains_proper_throttle_field(MockView_MinuteThrottling, - ((0, None), - (0, None), - (0, None), - (0, '60') - )) + self.ensure_response_header_contains_proper_throttle_field( + MockView_MinuteThrottling, ( + (0, None), + (0, None), + (0, None), + (0, '60') + ) + ) def test_next_rate_remains_constant_if_followed(self): """ If a client follows the recommended next request rate, the throttling rate should stay constant. """ - self.ensure_response_header_contains_proper_throttle_field(MockView_MinuteThrottling, - ((0, None), - (20, None), - (40, None), - (60, None), - (80, None) - )) + self.ensure_response_header_contains_proper_throttle_field( + MockView_MinuteThrottling, ( + (0, None), + (20, None), + (40, None), + (60, None), + (80, None) + ) + ) def test_non_time_throttle(self): """ @@ -170,7 +176,7 @@ class ThrottlingTests(TestCase): self.assertTrue(MockView_NonTimeThrottling.throttle_classes[0].called) response = MockView_NonTimeThrottling.as_view()(request) - self.assertFalse('X-Throttle-Wait-Seconds' in response) + self.assertFalse('X-Throttle-Wait-Seconds' in response) class ScopedRateThrottleTests(TestCase): diff --git a/tests/test_urlizer.py b/tests/test_urlizer.py index 3dc8e8fe5..a77aa22ab 100644 --- a/tests/test_urlizer.py +++ b/tests/test_urlizer.py @@ -1,7 +1,6 @@ from __future__ import unicode_literals from django.test import TestCase from rest_framework.templatetags.rest_framework import urlize_quoted_links -import sys class URLizerTests(TestCase): diff --git a/tox.ini b/tox.ini index 484053a69..6f588de12 100644 --- a/tox.ini +++ b/tox.ini @@ -1,13 +1,20 @@ [tox] downloadcache = {toxworkdir}/cache/ envlist = + flake8, py3.4-django1.7,py3.3-django1.7,py3.2-django1.7,py2.7-django1.7, py3.4-django1.6,py3.3-django1.6,py3.2-django1.6,py2.7-django1.6,py2.6-django1.6, py3.4-django1.5,py3.3-django1.5,py3.2-django1.5,py2.7-django1.5,py2.6-django1.5, - py2.7-django1.4,py2.6-django1.4, + py2.7-django1.4,py2.6-django1.4 [testenv] -commands = py.test -q +commands = ./runtests.py --fast + +[testenv:flake8] +basepython = python2.7 +deps = pytest==2.5.2 + flake8==2.2.2 +commands = ./runtests.py --lintonly [testenv:py3.4-django1.7] basepython = python3.4