From 8ff95dc1f2afa5b84ba498660440ce402448a6b7 Mon Sep 17 00:00:00 2001 From: Oliver Sauder Date: Thu, 11 May 2017 14:52:47 +0200 Subject: [PATCH 1/2] Enforcing of flake8 linter * Allow F405 (star imports) as this is part of drf json api concept * Remove E501 so defined max-line-length won't be ignored --- .travis.yml | 3 +- example/api/resources/identity.py | 1 - example/api/serializers/identity.py | 3 +- example/api/serializers/post.py | 1 - example/factories/__init__.py | 2 + example/models.py | 1 - example/serializers.py | 14 +-- example/settings/__init__.py | 2 +- example/settings/test.py | 2 +- example/tests/__init__.py | 4 +- example/tests/conftest.py | 5 +- example/tests/integration/test_includes.py | 32 ++++-- .../integration/test_model_resource_name.py | 37 +++++-- .../test_non_paginated_responses.py | 1 - example/tests/integration/test_pagination.py | 11 +- .../integration/test_sparse_fieldsets.py | 3 +- example/tests/test_format_keys.py | 1 - example/tests/test_generic_validation.py | 5 +- example/tests/test_generic_viewset.py | 3 - example/tests/test_model_viewsets.py | 18 ++- example/tests/test_multiple_id_mixin.py | 6 +- example/tests/test_parsers.py | 3 +- example/tests/test_relations.py | 8 +- example/tests/test_serializers.py | 6 +- example/tests/test_sideload_resources.py | 1 - example/tests/test_views.py | 75 ++++++++++--- .../tests/unit/test_renderer_class_methods.py | 26 +++-- example/tests/unit/test_utils.py | 10 +- example/urls_test.py | 7 +- example/views.py | 1 - rest_framework_json_api/metadata.py | 19 ++-- rest_framework_json_api/mixins.py | 3 +- rest_framework_json_api/pagination.py | 1 - rest_framework_json_api/parsers.py | 18 ++- rest_framework_json_api/relations.py | 38 +++++-- rest_framework_json_api/renderers.py | 104 +++++++++++++----- rest_framework_json_api/serializers.py | 31 ++++-- rest_framework_json_api/utils.py | 21 +++- rest_framework_json_api/views.py | 66 ++++++----- setup.cfg | 3 +- 40 files changed, 401 insertions(+), 195 deletions(-) diff --git a/.travis.yml b/.travis.yml index fa4f244e..711914ee 100644 --- a/.travis.yml +++ b/.travis.yml @@ -40,11 +40,12 @@ before_install: # Force an upgrade of py & pytest to avoid VersionConflict - pip install --upgrade py - pip install "pytest>=2.8,<3" - - pip install codecov + - pip install codecov flake8 install: - pip install Django${DJANGO} djangorestframework${DRF} - python setup.py install script: + - flake8 - coverage run setup.py -v test after_success: - codecov diff --git a/example/api/resources/identity.py b/example/api/resources/identity.py index 9b8b8127..12862671 100644 --- a/example/api/resources/identity.py +++ b/example/api/resources/identity.py @@ -59,7 +59,6 @@ class GenericIdentity(generics.GenericAPIView): renderer_classes = (renderers.JSONRenderer, ) parser_classes = (parsers.JSONParser, ) - def get_queryset(self): return auth_models.User.objects.all() diff --git a/example/api/serializers/identity.py b/example/api/serializers/identity.py index ab838ef6..2538c6df 100644 --- a/example/api/serializers/identity.py +++ b/example/api/serializers/identity.py @@ -6,10 +6,11 @@ class IdentitySerializer(serializers.ModelSerializer): """ Identity Serializer """ + def validate_first_name(self, data): if len(data) > 10: raise serializers.ValidationError( - 'There\'s a problem with first name') + 'There\'s a problem with first name') return data def validate_last_name(self, data): diff --git a/example/api/serializers/post.py b/example/api/serializers/post.py index 3fd5b5d0..bf0cf463 100644 --- a/example/api/serializers/post.py +++ b/example/api/serializers/post.py @@ -6,4 +6,3 @@ class PostSerializer(serializers.Serializer): Blog post serializer """ title = serializers.CharField(max_length=50) - diff --git a/example/factories/__init__.py b/example/factories/__init__.py index bd689cf7..de9a02fa 100644 --- a/example/factories/__init__.py +++ b/example/factories/__init__.py @@ -7,6 +7,7 @@ faker = FakerFactory.create() faker.seed(983843) + class BlogFactory(factory.django.DjangoModelFactory): class Meta: model = Blog @@ -23,6 +24,7 @@ class Meta: bio = factory.RelatedFactory('example.factories.AuthorBioFactory', 'author') + class AuthorBioFactory(factory.django.DjangoModelFactory): class Meta: model = AuthorBio diff --git a/example/models.py b/example/models.py index d0dcf3e4..6442b0e4 100644 --- a/example/models.py +++ b/example/models.py @@ -86,4 +86,3 @@ class Comment(BaseModel): def __str__(self): return self.body - diff --git a/example/serializers.py b/example/serializers.py index 743d10e6..e8ee53ca 100644 --- a/example/serializers.py +++ b/example/serializers.py @@ -24,7 +24,7 @@ def get_copyright(self, resource): def get_root_meta(self, resource, many): return { - 'api_docs': '/docs/api/blogs' + 'api_docs': '/docs/api/blogs' } class Meta: @@ -55,17 +55,17 @@ def __init__(self, *args, **kwargs): body_format = serializers.SerializerMethodField() # many related from model comments = relations.ResourceRelatedField( - many=True, read_only=True) + many=True, read_only=True) # many related from serializer suggested = relations.SerializerMethodResourceRelatedField( - source='get_suggested', model=Entry, many=True, read_only=True, - related_link_view_name='entry-suggested', - related_link_url_kwarg='entry_pk', - self_link_view_name='entry-relationships', + source='get_suggested', model=Entry, many=True, read_only=True, + related_link_view_name='entry-suggested', + related_link_url_kwarg='entry_pk', + self_link_view_name='entry-relationships', ) # single related from serializer featured = relations.SerializerMethodResourceRelatedField( - source='get_featured', model=Entry, read_only=True) + source='get_featured', model=Entry, read_only=True) tags = TaggedItemSerializer(many=True, read_only=True) def get_suggested(self, obj): diff --git a/example/settings/__init__.py b/example/settings/__init__.py index c7873286..70fd9acf 100644 --- a/example/settings/__init__.py +++ b/example/settings/__init__.py @@ -1 +1 @@ -from .dev import * +from .dev import * # noqa diff --git a/example/settings/test.py b/example/settings/test.py index 5bb3f45d..1f0e959d 100644 --- a/example/settings/test.py +++ b/example/settings/test.py @@ -1,4 +1,4 @@ -from .dev import * +from .dev import * # noqa DATABASES = { 'default': { diff --git a/example/tests/__init__.py b/example/tests/__init__.py index 16d1bdc8..30a04e22 100644 --- a/example/tests/__init__.py +++ b/example/tests/__init__.py @@ -7,6 +7,7 @@ class TestBase(APITestCase): """ Test base class to setup a couple users. """ + def setUp(self): """ Create those users @@ -15,7 +16,7 @@ def setUp(self): self.create_users() def create_user(self, username, email, password="pw", - first_name='', last_name=''): + first_name='', last_name=''): """ Helper method to create a user """ @@ -39,4 +40,3 @@ def create_users(self): self.miles = self.create_user( 'miles', 'miles@example.com', first_name="Miles", last_name="Davis") - diff --git a/example/tests/conftest.py b/example/tests/conftest.py index 8e3c8f40..acdc8543 100644 --- a/example/tests/conftest.py +++ b/example/tests/conftest.py @@ -1,8 +1,10 @@ import pytest from pytest_factoryboy import register -from example.factories import BlogFactory, AuthorFactory, AuthorBioFactory, EntryFactory, CommentFactory, \ +from example.factories import ( + BlogFactory, AuthorFactory, AuthorBioFactory, EntryFactory, CommentFactory, TaggedItemFactory +) register(BlogFactory) register(AuthorFactory) @@ -31,4 +33,3 @@ def multiple_entries(blog_factory, author_factory, entry_factory, comment_factor comment_factory(entry=entries[0]) comment_factory(entry=entries[1]) return entries - diff --git a/example/tests/integration/test_includes.py b/example/tests/integration/test_includes.py index 622c8c13..55f86986 100644 --- a/example/tests/integration/test_includes.py +++ b/example/tests/integration/test_includes.py @@ -7,15 +7,21 @@ def test_default_included_data_on_list(multiple_entries, client): - return test_included_data_on_list(multiple_entries=multiple_entries, client=client, query='?page_size=5') + return test_included_data_on_list( + multiple_entries=multiple_entries, client=client, query='?page_size=5' + ) def test_included_data_on_list(multiple_entries, client, query='?include=comments&page_size=5'): response = client.get(reverse("entry-list") + query) included = load_json(response.content).get('included') - assert len(load_json(response.content)['data']) == len(multiple_entries), 'Incorrect entry count' - assert [x.get('type') for x in included] == ['comments', 'comments'], 'List included types are incorrect' + assert len(load_json(response.content)['data']) == len(multiple_entries), ( + 'Incorrect entry count' + ) + assert [x.get('type') for x in included] == ['comments', 'comments'], ( + 'List included types are incorrect' + ) comment_count = len([resource for resource in included if resource["type"] == "comments"]) expected_comment_count = sum([entry.comments.count() for entry in multiple_entries]) @@ -39,7 +45,9 @@ def test_included_data_on_detail(single_entry, client, query='?include=comments' def test_dynamic_related_data_is_included(single_entry, entry_factory, client): entry_factory() - response = client.get(reverse("entry-detail", kwargs={'pk': single_entry.pk}) + '?include=featured') + response = client.get( + reverse("entry-detail", kwargs={'pk': single_entry.pk}) + '?include=featured' + ) included = load_json(response.content).get('included') assert [x.get('type') for x in included] == ['entries'], 'Dynamic included types are incorrect' @@ -48,7 +56,9 @@ def test_dynamic_related_data_is_included(single_entry, entry_factory, client): def test_dynamic_many_related_data_is_included(single_entry, entry_factory, client): entry_factory() - response = client.get(reverse("entry-detail", kwargs={'pk': single_entry.pk}) + '?include=suggested') + response = client.get( + reverse("entry-detail", kwargs={'pk': single_entry.pk}) + '?include=suggested' + ) included = load_json(response.content).get('included') assert included @@ -58,12 +68,12 @@ def test_dynamic_many_related_data_is_included(single_entry, entry_factory, clie def test_missing_field_not_included(author_bio_factory, author_factory, client): # First author does not have a bio author = author_factory(bio=None) - response = client.get(reverse('author-detail', args=[author.pk])+'?include=bio') + response = client.get(reverse('author-detail', args=[author.pk]) + '?include=bio') data = load_json(response.content) assert 'included' not in data # Second author does author = author_factory() - response = client.get(reverse('author-detail', args=[author.pk])+'?include=bio') + response = client.get(reverse('author-detail', args=[author.pk]) + '?include=bio') data = load_json(response.content) assert 'included' in data assert len(data['included']) == 1 @@ -75,7 +85,9 @@ def test_deep_included_data_on_list(multiple_entries, client): 'comments.author.bio&page_size=5') included = load_json(response.content).get('included') - assert len(load_json(response.content)['data']) == len(multiple_entries), 'Incorrect entry count' + assert len(load_json(response.content)['data']) == len(multiple_entries), ( + 'Incorrect entry count' + ) assert [x.get('type') for x in included] == [ 'authorBios', 'authorBios', 'authors', 'authors', 'comments', 'comments' ], 'List included types are incorrect' @@ -99,7 +111,9 @@ def test_deep_included_data_on_list(multiple_entries, client): 'comments.author.bio&page_size=5') included = load_json(response.content).get('included') - assert len(load_json(response.content)['data']) == len(multiple_entries), 'Incorrect entry count' + assert len(load_json(response.content)['data']) == len(multiple_entries), ( + 'Incorrect entry count' + ) assert [x.get('type') for x in included] == [ 'authorBios', 'authorBios', 'authors', 'authors', 'authors', 'authors', 'comments', 'comments'], 'List included types are incorrect' diff --git a/example/tests/integration/test_model_resource_name.py b/example/tests/integration/test_model_resource_name.py index c304a670..8f8aa536 100644 --- a/example/tests/integration/test_model_resource_name.py +++ b/example/tests/integration/test_model_resource_name.py @@ -22,7 +22,9 @@ def _check_resource_and_relationship_comment_type_match(django_client): comment_relationship_type = load_json(entry_response.content).get( 'data')[0].get('relationships').get('comments').get('data')[0].get('type') - assert comment_resource_type == comment_relationship_type, "The resource type seen in the relationships and head resource do not match" + assert comment_resource_type == comment_relationship_type, ( + "The resource type seen in the relationships and head resource do not match" + ) def _check_relationship_and_included_comment_type_are_the_same(django_client, url): @@ -33,7 +35,9 @@ def _check_relationship_and_included_comment_type_are_the_same(django_client, ur comment_relationship_type = data.get('relationships').get('comments').get('data')[0].get('type') comment_included_type = comment.get('type') - assert comment_relationship_type == comment_included_type, "The resource type seen in the relationships and included do not match" + assert comment_relationship_type == comment_included_type, ( + "The resource type seen in the relationships and included do not match" + ) @pytest.mark.usefixtures("single_entry") @@ -81,7 +85,12 @@ def test_resource_name_precendence(self, client, monkeypatch): 'resource_name from model incorrect on list') # serializer > model - monkeypatch.setattr(serializers.CommentSerializer.Meta, 'resource_name', 'resource_name_from_serializer', False) + monkeypatch.setattr( + serializers.CommentSerializer.Meta, + 'resource_name', + 'resource_name_from_serializer', + False + ) response = client.get(reverse("comment-list")) data = load_json(response.content)['data'][0] assert (data.get('type') == 'resource_name_from_serializer'), ( @@ -104,8 +113,18 @@ def test_model_resource_name_create(self, client): assert response.status_code == status.HTTP_201_CREATED def test_serializer_resource_name_create(self, client, monkeypatch): - monkeypatch.setattr(serializers.CommentSerializer.Meta, 'resource_name', 'renamed_comments', False) - monkeypatch.setattr(serializers.EntrySerializer.Meta, 'resource_name', 'renamed_entries', False) + monkeypatch.setattr( + serializers.CommentSerializer.Meta, + 'resource_name', + 'renamed_comments', + False + ) + monkeypatch.setattr( + serializers.EntrySerializer.Meta, + 'resource_name', + 'renamed_entries', + False + ) create_data = deepcopy(self.create_data) create_data['data']['type'] = 'renamed_comments' create_data['data']['relationships']['entry']['data']['type'] = 'renamed_entries' @@ -143,7 +162,9 @@ def test_type_match_on_included_and_inline_without_serializer_resource_name(self _check_relationship_and_included_comment_type_are_the_same(client, reverse("entry-list")) - def test_type_match_on_included_and_inline_with_serializer_resource_name_and_JSONAPIMeta(self, client): + def test_type_match_on_included_and_inline_with_serializer_resource_name_and_JSONAPIMeta( + self, client + ): models.Comment.__bases__ += (_PatchedModel,) serializers.CommentSerializer.Meta.resource_name = "resource_name_from_serializer" @@ -163,7 +184,9 @@ def test_resource_and_relationship_type_match_with_JSONAPIMeta(self, client): _check_resource_and_relationship_comment_type_match(client) - def test_resource_and_relationship_type_match_with_serializer_resource_name_and_JSONAPIMeta(self, client): + def test_resource_and_relationship_type_match_with_serializer_resource_name_and_JSONAPIMeta( + self, client + ): models.Comment.__bases__ += (_PatchedModel,) serializers.CommentSerializer.Meta.resource_name = "resource_name_from_serializer" diff --git a/example/tests/integration/test_non_paginated_responses.py b/example/tests/integration/test_non_paginated_responses.py index 8473a077..3c9ee633 100644 --- a/example/tests/integration/test_non_paginated_responses.py +++ b/example/tests/integration/test_non_paginated_responses.py @@ -1,5 +1,4 @@ from django.core.urlresolvers import reverse -from django.conf import settings try: from unittest import mock diff --git a/example/tests/integration/test_pagination.py b/example/tests/integration/test_pagination.py index 656ea709..cd6c8ff4 100644 --- a/example/tests/integration/test_pagination.py +++ b/example/tests/integration/test_pagination.py @@ -10,6 +10,7 @@ pytestmark = pytest.mark.django_db + @mock.patch( 'rest_framework_json_api.utils' '.get_default_included_resources_from_serializer', @@ -62,11 +63,11 @@ def test_pagination_with_single_entry(single_entry, client): } }], "links": { - "first": "http://testserver/entries?page=1", - "last": "http://testserver/entries?page=1", - "next": None, - "prev": None, - }, + "first": "http://testserver/entries?page=1", + "last": "http://testserver/entries?page=1", + "next": None, + "prev": None, + }, "meta": { "pagination": diff --git a/example/tests/integration/test_sparse_fieldsets.py b/example/tests/integration/test_sparse_fieldsets.py index d0d17128..28a47ceb 100644 --- a/example/tests/integration/test_sparse_fieldsets.py +++ b/example/tests/integration/test_sparse_fieldsets.py @@ -9,5 +9,6 @@ def test_sparse_fieldset_ordered_dict_error(multiple_entries, client): base_url = reverse('entry-list') querystring = '?fields[entries]=blog,headline' - response = client.get(base_url + querystring) # RuntimeError: OrderedDict mutated during iteration + # RuntimeError: OrderedDict mutated during iteration + response = client.get(base_url + querystring) assert response.status_code == 200 # succeed if we didn't fail due to the above RuntimeError diff --git a/example/tests/test_format_keys.py b/example/tests/test_format_keys.py index aa491a21..17993336 100644 --- a/example/tests/test_format_keys.py +++ b/example/tests/test_format_keys.py @@ -16,7 +16,6 @@ def setUp(self): super(FormatKeysSetTests, self).setUp() self.detail_url = reverse('user-detail', kwargs={'pk': self.miles.pk}) - def test_camelization(self): """ Test that camelization works. diff --git a/example/tests/test_generic_validation.py b/example/tests/test_generic_validation.py index 54b8e347..bd93d165 100644 --- a/example/tests/test_generic_validation.py +++ b/example/tests/test_generic_validation.py @@ -1,9 +1,5 @@ -import json - from django.core.urlresolvers import reverse -from django.conf import settings -from rest_framework.serializers import ValidationError from example.tests import TestBase from example.tests.utils import load_json @@ -13,6 +9,7 @@ class GenericValidationTest(TestBase): """ Test that a non serializer specific validation can be thrown and formatted """ + def setUp(self): super(GenericValidationTest, self).setUp() self.url = reverse('user-validation', kwargs={'pk': self.miles.pk}) diff --git a/example/tests/test_generic_viewset.py b/example/tests/test_generic_viewset.py index b42df22e..047031c2 100644 --- a/example/tests/test_generic_viewset.py +++ b/example/tests/test_generic_viewset.py @@ -1,5 +1,3 @@ -import json - from django.core.urlresolvers import reverse from django.conf import settings @@ -42,7 +40,6 @@ def test_default_rest_framework_behavior(self): assert expected == parsed_content - def test_ember_expected_renderer(self): """ The :class:`UserEmber` ViewSet has the ``resource_name`` of 'data' diff --git a/example/tests/test_model_viewsets.py b/example/tests/test_model_viewsets.py index fa03bfbf..8aa6e133 100644 --- a/example/tests/test_model_viewsets.py +++ b/example/tests/test_model_viewsets.py @@ -1,5 +1,3 @@ -import json - from django.contrib.auth import get_user_model from django.utils import encoding from django.core.urlresolvers import reverse @@ -79,14 +77,14 @@ def test_page_two_in_list_result(self): expected = { 'data': [ { - 'type': 'users', - 'id': encoding.force_text(user.pk), - 'attributes': { - 'first-name': user.first_name, - 'last-name': user.last_name, - 'email': user.email - }, - } + 'type': 'users', + 'id': encoding.force_text(user.pk), + 'attributes': { + 'first-name': user.first_name, + 'last-name': user.last_name, + 'email': user.email + }, + } ], 'links': { 'first': 'http://testserver/identities?page=1', diff --git a/example/tests/test_multiple_id_mixin.py b/example/tests/test_multiple_id_mixin.py index 8975515b..edabfd7d 100644 --- a/example/tests/test_multiple_id_mixin.py +++ b/example/tests/test_multiple_id_mixin.py @@ -1,9 +1,7 @@ import json from example.tests import TestBase from django.utils import encoding -from django.contrib.auth import get_user_model from django.core.urlresolvers import reverse -from django.conf import settings class MultipleIDMixin(TestBase): @@ -73,10 +71,10 @@ def test_multiple_ids_in_query_params(self): self.assertEquals(meta.get('count', 0), 2) self.assertEqual( sorted( - 'http://testserver/identities?ids%5B%5D=2&ids%5B%5D=1&page=2'\ + 'http://testserver/identities?ids%5B%5D=2&ids%5B%5D=1&page=2' .split('?')[1].split('&') ), sorted( links.get("next").split('?')[1].split('&')) - ) + ) self.assertEqual(meta.get("page"), 1) diff --git a/example/tests/test_parsers.py b/example/tests/test_parsers.py index 00d2ca69..3c7a102c 100644 --- a/example/tests/test_parsers.py +++ b/example/tests/test_parsers.py @@ -39,8 +39,7 @@ def test_parse_include_metadata(self): self.assertEqual(data['_meta'], {'random_key': 'random_value'}) - - def test_parse_include_metadata(self): + def test_parse_invalid_data(self): parser = JSONParser() string = json.dumps([]) diff --git a/example/tests/test_relations.py b/example/tests/test_relations.py index dc252e7e..767428b3 100644 --- a/example/tests/test_relations.py +++ b/example/tests/test_relations.py @@ -26,7 +26,7 @@ def setUp(self): n_pingbacks=0, rating=3 ) - for i in range(1,6): + for i in range(1, 6): name = 'some_author{}'.format(i) self.entry.authors.add( Author.objects.create(name=name, email='{}@example.org'.format(name)) @@ -82,7 +82,7 @@ def test_validation_fails_for_wrong_type(self): 'id': str(self.blog.id) } } - ) + ) serializer.is_valid() the_exception = cm.exception self.assertEqual(the_exception.status_code, 409) @@ -112,7 +112,9 @@ def test_deserialize_many_to_many_relation(self): self.assertIsInstance(author, Author) def test_read_only(self): - serializer = EntryModelSerializer(data={'authors': [], 'comments': [{'type': 'Comments', 'id': 2}]}) + serializer = EntryModelSerializer( + data={'authors': [], 'comments': [{'type': 'Comments', 'id': 2}]} + ) serializer.is_valid(raise_exception=True) self.assertNotIn('comments', serializer.validated_data) diff --git a/example/tests/test_serializers.py b/example/tests/test_serializers.py index 072bdc51..74b7d860 100644 --- a/example/tests/test_serializers.py +++ b/example/tests/test_serializers.py @@ -26,7 +26,7 @@ def setUp(self): n_pingbacks=0, rating=3 ) - for i in range(1,6): + for i in range(1, 6): name = 'some_author{}'.format(i) self.entry.authors.add( Author.objects.create(name=name, email='{}@example.org'.format(name)) @@ -71,7 +71,9 @@ def test_deserialize_many(self): author_pks = Author.objects.values_list('pk', flat=True) initial_data = [{'type': type_string, 'id': str(pk)} for pk in author_pks] - serializer = ResourceIdentifierObjectSerializer(data=initial_data, model_class=Author, many=True) + serializer = ResourceIdentifierObjectSerializer( + data=initial_data, model_class=Author, many=True + ) self.assertTrue(serializer.is_valid(), msg=serializer.errors) diff --git a/example/tests/test_sideload_resources.py b/example/tests/test_sideload_resources.py index aa0e8cf2..b06570c6 100644 --- a/example/tests/test_sideload_resources.py +++ b/example/tests/test_sideload_resources.py @@ -5,7 +5,6 @@ from django.core.urlresolvers import reverse from django.utils import encoding -from django.conf import settings from example.tests import TestBase diff --git a/example/tests/test_views.py b/example/tests/test_views.py index 9fd6c711..a0718739 100644 --- a/example/tests/test_views.py +++ b/example/tests/test_views.py @@ -39,7 +39,9 @@ def setUp(self): n_pingbacks=0, rating=1 ) - self.first_comment = Comment.objects.create(entry=self.first_entry, body="This entry is cool", author=None) + self.first_comment = Comment.objects.create( + entry=self.first_entry, body="This entry is cool", author=None + ) self.second_comment = Comment.objects.create( entry=self.second_entry, body="This entry is not cool", @@ -47,14 +49,18 @@ def setUp(self): ) def test_get_entry_relationship_blog(self): - url = reverse('entry-relationships', kwargs={'pk': self.first_entry.id, 'related_field': 'blog'}) + url = reverse( + 'entry-relationships', kwargs={'pk': self.first_entry.id, 'related_field': 'blog'} + ) response = self.client.get(url) expected_data = {'type': format_resource_type('Blog'), 'id': str(self.first_entry.blog.id)} assert response.data == expected_data def test_get_entry_relationship_invalid_field(self): - response = self.client.get('/entries/{}/relationships/invalid_field'.format(self.first_entry.id)) + response = self.client.get( + '/entries/{}/relationships/invalid_field'.format(self.first_entry.id) + ) assert response.status_code == 404 @@ -111,7 +117,9 @@ def test_patch_to_one_relationship(self): request_data = { 'data': {'type': format_resource_type('Blog'), 'id': str(self.other_blog.id)} } - response = self.client.patch(url, data=json.dumps(request_data), content_type='application/vnd.api+json') + response = self.client.patch( + url, data=json.dumps(request_data), content_type='application/vnd.api+json' + ) assert response.status_code == 200, response.content.decode() assert response.data == request_data['data'] @@ -123,7 +131,9 @@ def test_patch_one_to_many_relationship(self): request_data = { 'data': [{'type': format_resource_type('Entry'), 'id': str(self.first_entry.id)}, ] } - response = self.client.patch(url, data=json.dumps(request_data), content_type='application/vnd.api+json') + response = self.client.patch( + url, data=json.dumps(request_data), content_type='application/vnd.api+json' + ) assert response.status_code == 200, response.content.decode() assert response.data == request_data['data'] @@ -154,7 +164,9 @@ def test_post_to_one_relationship_should_fail(self): request_data = { 'data': {'type': format_resource_type('Blog'), 'id': str(self.other_blog.id)} } - response = self.client.post(url, data=json.dumps(request_data), content_type='application/vnd.api+json') + response = self.client.post( + url, data=json.dumps(request_data), content_type='application/vnd.api+json' + ) assert response.status_code == 405, response.content.decode() def test_post_to_many_relationship_with_no_change(self): @@ -162,7 +174,9 @@ def test_post_to_many_relationship_with_no_change(self): request_data = { 'data': [{'type': format_resource_type('Comment'), 'id': str(self.first_comment.id)}, ] } - response = self.client.post(url, data=json.dumps(request_data), content_type='application/vnd.api+json') + response = self.client.post( + url, data=json.dumps(request_data), content_type='application/vnd.api+json' + ) assert response.status_code == 204, response.content.decode() assert len(response.rendered_content) == 0, response.rendered_content.decode() @@ -171,7 +185,9 @@ def test_post_to_many_relationship_with_change(self): request_data = { 'data': [{'type': format_resource_type('Comment'), 'id': str(self.second_comment.id)}, ] } - response = self.client.post(url, data=json.dumps(request_data), content_type='application/vnd.api+json') + response = self.client.post( + url, data=json.dumps(request_data), content_type='application/vnd.api+json' + ) assert response.status_code == 200, response.content.decode() assert request_data['data'][0] in response.data @@ -181,7 +197,9 @@ def test_delete_to_one_relationship_should_fail(self): request_data = { 'data': {'type': format_resource_type('Blog'), 'id': str(self.other_blog.id)} } - response = self.client.delete(url, data=json.dumps(request_data), content_type='application/vnd.api+json') + response = self.client.delete( + url, data=json.dumps(request_data), content_type='application/vnd.api+json' + ) assert response.status_code == 405, response.content.decode() def test_delete_relationship_overriding_with_none(self): @@ -197,16 +215,20 @@ def test_delete_relationship_overriding_with_none(self): } } } - response = self.client.patch(url, data=json.dumps(request_data), content_type='application/vnd.api+json') + response = self.client.patch( + url, data=json.dumps(request_data), content_type='application/vnd.api+json' + ) assert response.status_code == 200, response.content.decode() - assert response.data['author'] == None + assert response.data['author'] is None def test_delete_to_many_relationship_with_no_change(self): url = '/entries/{}/relationships/comments'.format(self.first_entry.id) request_data = { 'data': [{'type': format_resource_type('Comment'), 'id': str(self.second_comment.id)}, ] } - response = self.client.delete(url, data=json.dumps(request_data), content_type='application/vnd.api+json') + response = self.client.delete( + url, data=json.dumps(request_data), content_type='application/vnd.api+json' + ) assert response.status_code == 204, response.content.decode() assert len(response.rendered_content) == 0, response.rendered_content.decode() @@ -215,7 +237,9 @@ def test_delete_one_to_many_relationship_with_not_null_constraint(self): request_data = { 'data': [{'type': format_resource_type('Comment'), 'id': str(self.first_comment.id)}, ] } - response = self.client.delete(url, data=json.dumps(request_data), content_type='application/vnd.api+json') + response = self.client.delete( + url, data=json.dumps(request_data), content_type='application/vnd.api+json' + ) assert response.status_code == 409, response.content.decode() def test_delete_to_many_relationship_with_change(self): @@ -223,7 +247,9 @@ def test_delete_to_many_relationship_with_change(self): request_data = { 'data': [{'type': format_resource_type('Comment'), 'id': str(self.second_comment.id)}, ] } - response = self.client.delete(url, data=json.dumps(request_data), content_type='application/vnd.api+json') + response = self.client.delete( + url, data=json.dumps(request_data), content_type='application/vnd.api+json' + ) assert response.status_code == 200, response.content.decode() @@ -232,21 +258,36 @@ def test_if_returns_error_on_empty_post(self): view = views.BlogViewSet.as_view({'post': 'create'}) response = self._get_create_response("{}", view) self.assertEqual(400, response.status_code) - expected = [{'detail': 'Received document does not contain primary data', 'status': '400', 'source': {'pointer': '/data'}}] + expected = [{ + 'detail': 'Received document does not contain primary data', + 'status': '400', + 'source': {'pointer': '/data'} + }] self.assertEqual(expected, response.data) def test_if_returns_error_on_missing_form_data_post(self): view = views.BlogViewSet.as_view({'post': 'create'}) response = self._get_create_response('{"data":{"attributes":{},"type":"blogs"}}', view) self.assertEqual(400, response.status_code) - expected = [{'status': '400', 'detail': 'This field is required.', 'source': {'pointer': '/data/attributes/name'}}] + expected = [{ + 'status': '400', + 'detail': 'This field is required.', + 'source': {'pointer': '/data/attributes/name'} + }] self.assertEqual(expected, response.data) def test_if_returns_error_on_bad_endpoint_name(self): view = views.BlogViewSet.as_view({'post': 'create'}) response = self._get_create_response('{"data":{"attributes":{},"type":"bad"}}', view) self.assertEqual(409, response.status_code) - expected = [{'detail': "The resource object's type (bad) is not the type that constitute the collection represented by the endpoint (blogs).", 'source': {'pointer': '/data'}, 'status': '409'}] + expected = [{ + 'detail': ( + "The resource object's type (bad) is not the type that constitute the collection " + "represented by the endpoint (blogs)." + ), + 'source': {'pointer': '/data'}, + 'status': '409' + }] self.assertEqual(expected, response.data) def _get_create_response(self, data, view): diff --git a/example/tests/unit/test_renderer_class_methods.py b/example/tests/unit/test_renderer_class_methods.py index fc97dee0..b6f9d69b 100644 --- a/example/tests/unit/test_renderer_class_methods.py +++ b/example/tests/unit/test_renderer_class_methods.py @@ -6,10 +6,13 @@ pytestmark = pytest.mark.django_db + class ResourceSerializer(serializers.ModelSerializer): version = serializers.SerializerMethodField() + def get_version(self, obj): return '1.0.0' + class Meta: fields = ('username',) meta_fields = ('version',) @@ -37,6 +40,7 @@ def test_build_json_resource_obj(): assert JSONRenderer.build_json_resource_obj( serializer.fields, resource, resource_instance, 'user') == output + def test_can_override_methods(): """ Make sure extract_attributes and extract_relationships can be overriden. @@ -70,13 +74,16 @@ def extract_attributes(cls, fields, resource): @classmethod def extract_relationships(cls, fields, resource, resource_instance): cls.extract_relationships_was_overriden = True - return super(CustomRenderer, cls).extract_relationships(fields, resource, resource_instance) + return super(CustomRenderer, cls).extract_relationships( + fields, resource, resource_instance + ) assert CustomRenderer.build_json_resource_obj( serializer.fields, resource, resource_instance, 'user') == output assert CustomRenderer.extract_attributes_was_overriden assert CustomRenderer.extract_relationships_was_overriden + def test_extract_attributes(): fields = { 'id': serializers.Field(), @@ -88,12 +95,15 @@ def test_extract_attributes(): 'username': 'jerel', 'deleted': None } - assert sorted(JSONRenderer.extract_attributes(fields, resource)) == sorted(expected), 'Regular fields should be extracted' + assert sorted(JSONRenderer.extract_attributes(fields, resource)) == sorted(expected), ( + 'Regular fields should be extracted' + ) assert sorted(JSONRenderer.extract_attributes(fields, {})) == sorted( {'username': ''}), 'Should not extract read_only fields on empty serializer' + def test_extract_meta(): - serializer = ResourceSerializer(data={'username': 'jerel', 'version':'1.0.0'}) + serializer = ResourceSerializer(data={'username': 'jerel', 'version': '1.0.0'}) serializer.is_valid() expected = { 'version': '1.0.0', @@ -105,11 +115,11 @@ class ExtractRootMetaResourceSerializer(ResourceSerializer): def get_root_meta(self, resource, many): if many: return { - 'foo': 'meta-many-value' + 'foo': 'meta-many-value' } else: return { - 'foo': 'meta-value' + 'foo': 'meta-value' } @@ -125,17 +135,19 @@ def test_extract_root_meta(): } assert JSONRenderer.extract_root_meta(serializer, {}) == expected + def test_extract_root_meta_many(): serializer = ExtractRootMetaResourceSerializer(many=True) expected = { - 'foo': 'meta-many-value' + 'foo': 'meta-many-value' } assert JSONRenderer.extract_root_meta(serializer, {}) == expected + def test_extract_root_meta_invalid_meta(): def get_root_meta(resource, many): return 'not a dict' serializer = InvalidExtractRootMetaResourceSerializer() - with pytest.raises(AssertionError) as e_info: + with pytest.raises(AssertionError): JSONRenderer.extract_root_meta(serializer, {}) diff --git a/example/tests/unit/test_utils.py b/example/tests/unit/test_utils.py index 92b12010..5308d40f 100644 --- a/example/tests/unit/test_utils.py +++ b/example/tests/unit/test_utils.py @@ -115,8 +115,9 @@ def test_get_included_serializers_against_class(): 'comments': CommentSerializer, 'self': klass } - assert (six.viewkeys(included_serializers) == six.viewkeys(klass.included_serializers), - 'the keys must be preserved') + assert six.viewkeys(included_serializers) == six.viewkeys(klass.included_serializers), ( + 'the keys must be preserved' + ) assert included_serializers == expected_included_serializers @@ -131,7 +132,8 @@ def test_get_included_serializers_against_instance(): 'comments': CommentSerializer, 'self': klass } - assert (six.viewkeys(included_serializers) == six.viewkeys(klass.included_serializers), - 'the keys must be preserved') + assert six.viewkeys(included_serializers) == six.viewkeys(klass.included_serializers), ( + 'the keys must be preserved' + ) assert included_serializers == expected_included_serializers diff --git a/example/urls_test.py b/example/urls_test.py index 2804863d..2d569c16 100644 --- a/example/urls_test.py +++ b/example/urls_test.py @@ -1,8 +1,10 @@ from django.conf.urls import include, url from rest_framework import routers -from example.views import BlogViewSet, EntryViewSet, AuthorViewSet, CommentViewSet, EntryRelationshipView, BlogRelationshipView, \ - CommentRelationshipView, AuthorRelationshipView +from example.views import ( + BlogViewSet, EntryViewSet, AuthorViewSet, CommentViewSet, EntryRelationshipView, + BlogRelationshipView, CommentRelationshipView, AuthorRelationshipView +) from .api.resources.identity import Identity, GenericIdentity router = routers.DefaultRouter(trailing_slash=False) @@ -40,4 +42,3 @@ AuthorRelationshipView.as_view(), name='author-relationships'), ] - diff --git a/example/views.py b/example/views.py index 54330fc5..67cb7f67 100644 --- a/example/views.py +++ b/example/views.py @@ -1,5 +1,4 @@ from rest_framework import exceptions -from rest_framework import viewsets import rest_framework.parsers import rest_framework.renderers import rest_framework_json_api.metadata diff --git a/rest_framework_json_api/metadata.py b/rest_framework_json_api/metadata.py index 19fcae7b..8306a9dd 100644 --- a/rest_framework_json_api/metadata.py +++ b/rest_framework_json_api/metadata.py @@ -82,9 +82,10 @@ def get_serializer_info(self, serializer): # Remove the URL field if present serializer.fields.pop(api_settings.URL_FIELD_NAME, None) - return OrderedDict( - [(field_name, self.get_field_info(field)) for field_name, field in serializer.fields.items()] - ) + return OrderedDict([ + (field_name, self.get_field_info(field)) + for field_name, field in serializer.fields.items() + ]) def get_field_info(self, field): """ @@ -101,7 +102,9 @@ def get_field_info(self, field): try: serializer_model = getattr(serializer.Meta, 'model') - field_info['relationship_type'] = self.relation_type_lookup[getattr(serializer_model, field.field_name)] + field_info['relationship_type'] = self.relation_type_lookup[ + getattr(serializer_model, field.field_name) + ] except KeyError: pass except AttributeError: @@ -127,9 +130,11 @@ def get_field_info(self, field): elif getattr(field, 'fields', None): field_info['children'] = self.get_serializer_info(field) - if (not field_info.get('read_only') - and not field_info.get('relationship_resource') - and hasattr(field, 'choices')): + if ( + not field_info.get('read_only') and + not field_info.get('relationship_resource') and + hasattr(field, 'choices') + ): field_info['choices'] = [ { 'value': choice_value, diff --git a/rest_framework_json_api/mixins.py b/rest_framework_json_api/mixins.py index 16af92a4..7b98d62e 100644 --- a/rest_framework_json_api/mixins.py +++ b/rest_framework_json_api/mixins.py @@ -2,10 +2,12 @@ Class Mixins. """ + class MultipleIDMixin(object): """ Override get_queryset for multiple id support """ + def get_queryset(self): """ Override :meth:``get_queryset`` @@ -17,4 +19,3 @@ def get_queryset(self): if ids: self.queryset = self.queryset.filter(id__in=ids) return self.queryset - diff --git a/rest_framework_json_api/pagination.py b/rest_framework_json_api/pagination.py index 9ec24138..d13b6ec4 100644 --- a/rest_framework_json_api/pagination.py +++ b/rest_framework_json_api/pagination.py @@ -2,7 +2,6 @@ Pagination fields """ from collections import OrderedDict -from rest_framework import serializers from rest_framework.views import Response from rest_framework.pagination import PageNumberPagination, LimitOffsetPagination from rest_framework.utils.urls import remove_query_param, replace_query_param diff --git a/rest_framework_json_api/parsers.py b/rest_framework_json_api/parsers.py index ba76df32..2f74f495 100644 --- a/rest_framework_json_api/parsers.py +++ b/rest_framework_json_api/parsers.py @@ -75,7 +75,9 @@ def parse(self, stream, media_type=None, parser_context=None): """ Parses the incoming bytestream as JSON and returns the resulting data """ - result = super(JSONParser, self).parse(stream, media_type=media_type, parser_context=parser_context) + result = super(JSONParser, self).parse( + stream, media_type=media_type, parser_context=parser_context + ) if not isinstance(result, dict) or 'data' not in result: raise ParseError('Received document does not contain primary data') @@ -84,12 +86,17 @@ def parse(self, stream, media_type=None, parser_context=None): from rest_framework_json_api.views import RelationshipView if isinstance(parser_context['view'], RelationshipView): - # We skip parsing the object as JSONAPI Resource Identifier Object and not a regular Resource Object + # We skip parsing the object as JSONAPI Resource Identifier Object and not a regular + # Resource Object if isinstance(data, list): for resource_identifier_object in data: - if not (resource_identifier_object.get('id') and resource_identifier_object.get('type')): + if not ( + resource_identifier_object.get('id') and + resource_identifier_object.get('type') + ): raise ParseError( - 'Received data contains one or more malformed JSONAPI Resource Identifier Object(s)' + 'Received data contains one or more malformed JSONAPI ' + 'Resource Identifier Object(s)' ) elif not (data.get('id') and data.get('type')): raise ParseError('Received data is not a valid JSONAPI Resource Identifier Object') @@ -103,7 +110,8 @@ def parse(self, stream, media_type=None, parser_context=None): if data.get('type') != resource_name and request.method in ('PUT', 'POST', 'PATCH'): raise exceptions.Conflict( "The resource object's type ({data_type}) is not the type " - "that constitute the collection represented by the endpoint ({resource_type}).".format( + "that constitute the collection represented by the endpoint " + "({resource_type}).".format( data_type=data.get('type'), resource_type=resource_name ) diff --git a/rest_framework_json_api/relations.py b/rest_framework_json_api/relations.py index 18b4f4b0..b488b986 100644 --- a/rest_framework_json_api/relations.py +++ b/rest_framework_json_api/relations.py @@ -2,18 +2,22 @@ import inflection import json -from rest_framework.fields import MISSING_ERROR_MESSAGE, SerializerMethodField -from rest_framework.relations import * +from rest_framework.fields import MISSING_ERROR_MESSAGE +from rest_framework.relations import * # noqa: F403 from rest_framework.serializers import Serializer from django.utils.translation import ugettext_lazy as _ -from django.db.models.query import QuerySet from rest_framework_json_api.exceptions import Conflict from rest_framework_json_api.utils import Hyperlink, \ get_resource_type_from_queryset, get_resource_type_from_instance, \ get_included_serializers, get_resource_type_from_serializer -LINKS_PARAMS = ['self_link_view_name', 'related_link_view_name', 'related_link_lookup_field', 'related_link_url_kwarg'] +LINKS_PARAMS = [ + 'self_link_view_name', + 'related_link_view_name', + 'related_link_lookup_field', + 'related_link_url_kwarg' +] class ResourceRelatedField(PrimaryKeyRelatedField): @@ -24,8 +28,12 @@ class ResourceRelatedField(PrimaryKeyRelatedField): default_error_messages = { 'required': _('This field is required.'), 'does_not_exist': _('Invalid pk "{pk_value}" - object does not exist.'), - 'incorrect_type': _('Incorrect type. Expected resource identifier object, received {data_type}.'), - 'incorrect_relation_type': _('Incorrect relation type. Expected {relation_type}, received {received_type}.'), + 'incorrect_type': _( + 'Incorrect type. Expected resource identifier object, received {data_type}.' + ), + 'incorrect_relation_type': _( + 'Incorrect relation type. Expected {relation_type}, received {received_type}.' + ), 'missing_type': _('Invalid resource identifier object: missing \'type\' attribute'), 'missing_id': _('Invalid resource identifier object: missing \'id\' attribute'), 'no_match': _('Invalid hyperlink - No URL match.'), @@ -37,8 +45,12 @@ def __init__(self, self_link_view_name=None, related_link_view_name=None, **kwar if related_link_view_name is not None: self.related_link_view_name = related_link_view_name - self.related_link_lookup_field = kwargs.pop('related_link_lookup_field', self.related_link_lookup_field) - self.related_link_url_kwarg = kwargs.pop('related_link_url_kwarg', self.related_link_lookup_field) + self.related_link_lookup_field = kwargs.pop( + 'related_link_lookup_field', self.related_link_lookup_field + ) + self.related_link_url_kwarg = kwargs.pop( + 'related_link_url_kwarg', self.related_link_lookup_field + ) # check for a model class that was passed in for the relation type model = kwargs.pop('model', None) @@ -104,7 +116,9 @@ def get_links(self, obj=None, lookup_field='pk'): kwargs = {lookup_field: getattr(obj, lookup_field) if obj else view.kwargs[lookup_field]} self_kwargs = kwargs.copy() - self_kwargs.update({'related_field': self.field_name if self.field_name else self.parent.field_name}) + self_kwargs.update({ + 'related_field': self.field_name if self.field_name else self.parent.field_name + }) self_link = self.get_url('self', self.self_link_view_name, self_kwargs, request) related_kwargs = {self.related_link_url_kwarg: kwargs[self.related_link_lookup_field]} @@ -139,7 +153,11 @@ def to_internal_value(self, data): self.fail('missing_id') if data['type'] != expected_relation_type: - self.conflict('incorrect_relation_type', relation_type=expected_relation_type, received_type=data['type']) + self.conflict( + 'incorrect_relation_type', + relation_type=expected_relation_type, + received_type=data['type'] + ) return super(ResourceRelatedField, self).to_internal_value(data['id']) diff --git a/rest_framework_json_api/renderers.py b/rest_framework_json_api/renderers.py index 3b7b6262..61a9238c 100644 --- a/rest_framework_json_api/renderers.py +++ b/rest_framework_json_api/renderers.py @@ -5,7 +5,7 @@ from collections import OrderedDict import inflection -from django.db.models import Manager, QuerySet +from django.db.models import Manager from django.utils import six, encoding from rest_framework import relations from rest_framework import renderers @@ -49,7 +49,9 @@ def extract_attributes(cls, fields, resource): if fields[field_name].write_only: continue # Skip fields with relations - if isinstance(field, (relations.RelatedField, relations.ManyRelatedField, BaseSerializer)): + if isinstance( + field, (relations.RelatedField, relations.ManyRelatedField, BaseSerializer) + ): continue # Skip read_only attribute fields when `resource` is an empty @@ -84,14 +86,18 @@ def extract_relationships(cls, fields, resource, resource_instance): continue # Skip fields without relations - if not isinstance(field, (relations.RelatedField, relations.ManyRelatedField, BaseSerializer)): + if not isinstance( + field, (relations.RelatedField, relations.ManyRelatedField, BaseSerializer) + ): continue source = field.source relation_type = utils.get_related_resource_type(field) if isinstance(field, relations.HyperlinkedIdentityField): - resolved, relation_instance = utils.get_relation_instance(resource_instance, source, field.parent) + resolved, relation_instance = utils.get_relation_instance( + resource_instance, source, field.parent + ) if not resolved: continue # special case for HyperlinkedIdentityField @@ -103,7 +109,10 @@ def extract_relationships(cls, fields, resource, resource_instance): for related_object in relation_queryset: relation_data.append( - OrderedDict([('type', relation_type), ('id', encoding.force_text(related_object.pk))]) + OrderedDict([ + ('type', relation_type), + ('id', encoding.force_text(related_object.pk)) + ]) ) data.update({field_name: { @@ -117,7 +126,9 @@ def extract_relationships(cls, fields, resource, resource_instance): continue if isinstance(field, ResourceRelatedField): - resolved, relation_instance = utils.get_relation_instance(resource_instance, source, field.parent) + resolved, relation_instance = utils.get_relation_instance( + resource_instance, source, field.parent + ) if not resolved: continue @@ -134,26 +145,41 @@ def extract_relationships(cls, fields, resource, resource_instance): data.update({field_name: relation_data}) continue - if isinstance(field, (relations.PrimaryKeyRelatedField, relations.HyperlinkedRelatedField)): - resolved, relation = utils.get_relation_instance(resource_instance, '%s_id' % source, field.parent) + if isinstance( + field, (relations.PrimaryKeyRelatedField, relations.HyperlinkedRelatedField) + ): + resolved, relation = utils.get_relation_instance( + resource_instance, '%s_id' % source, field.parent + ) if not resolved: continue relation_id = relation if resource.get(field_name) else None relation_data = { 'data': ( - OrderedDict([('type', relation_type), ('id', encoding.force_text(relation_id))]) + OrderedDict([ + ('type', relation_type), ('id', encoding.force_text(relation_id)) + ]) if relation_id is not None else None) } - relation_data.update( - {'links': {'related': resource.get(field_name)}} - if isinstance(field, relations.HyperlinkedRelatedField) and resource.get(field_name) else dict() - ) + if ( + isinstance(field, relations.HyperlinkedRelatedField) and + resource.get(field_name) + ): + relation_data.update( + { + 'links': { + 'related': resource.get(field_name) + } + } + ) data.update({field_name: relation_data}) continue if isinstance(field, relations.ManyRelatedField): - resolved, relation_instance = utils.get_relation_instance(resource_instance, source, field.parent) + resolved, relation_instance = utils.get_relation_instance( + resource_instance, source, field.parent + ) if not resolved: continue @@ -200,7 +226,9 @@ def extract_relationships(cls, fields, resource, resource_instance): continue if isinstance(field, ListSerializer): - resolved, relation_instance = utils.get_relation_instance(resource_instance, source, field.parent) + resolved, relation_instance = utils.get_relation_instance( + resource_instance, source, field.parent + ) if not resolved: continue @@ -225,7 +253,9 @@ def extract_relationships(cls, fields, resource, resource_instance): continue if isinstance(field, Serializer): - resolved, relation_instance = utils.get_relation_instance(resource_instance, source, field.parent) + resolved, relation_instance = utils.get_relation_instance( + resource_instance, source, field.parent + ) if not resolved: continue @@ -261,7 +291,9 @@ def extract_included(cls, fields, resource, resource_instance, included_resource continue # Skip fields without relations or serialized data - if not isinstance(field, (relations.RelatedField, relations.ManyRelatedField, BaseSerializer)): + if not isinstance( + field, (relations.RelatedField, relations.ManyRelatedField, BaseSerializer) + ): continue try: @@ -276,7 +308,8 @@ def extract_included(cls, fields, resource, resource_instance, included_resource relation_instance = getattr(resource_instance, field_name) except AttributeError: try: - # For ManyRelatedFields if `related_name` is not set we need to access `foo_set` from `source` + # For ManyRelatedFields if `related_name` is not set we need to access `foo_set` + # from `source` relation_instance = getattr(resource_instance, field.child_relation.source) except AttributeError: if not hasattr(current_serializer, field.source): @@ -323,12 +356,18 @@ def extract_included(cls, fields, resource, resource_instance, included_resource ) included_data.append( cls.build_json_resource_obj( - serializer_fields, serializer_resource, nested_resource_instance, resource_type + serializer_fields, + serializer_resource, + nested_resource_instance, + resource_type ) ) included_data.extend( cls.extract_included( - serializer_fields, serializer_resource, nested_resource_instance, new_included_resources + serializer_fields, + serializer_resource, + nested_resource_instance, + new_included_resources ) ) @@ -346,7 +385,10 @@ def extract_included(cls, fields, resource, resource_instance, included_resource ) included_data.extend( cls.extract_included( - serializer_fields, serializer_data, relation_instance, new_included_resources + serializer_fields, + serializer_data, + relation_instance, + new_included_resources ) ) @@ -474,24 +516,32 @@ def render(self, data, accepted_media_type=None, renderer_context=None): resource = serializer_data[position] # Get current resource resource_instance = serializer.instance[position] # Get current instance - json_resource_obj = self.build_json_resource_obj(fields, resource, resource_instance, resource_name) + json_resource_obj = self.build_json_resource_obj( + fields, resource, resource_instance, resource_name + ) meta = self.extract_meta(serializer, resource) if meta: json_resource_obj.update({'meta': utils.format_keys(meta)}) json_api_data.append(json_resource_obj) - included = self.extract_included(fields, resource, resource_instance, included_resources) + included = self.extract_included( + fields, resource, resource_instance, included_resources + ) if included: json_api_included.extend(included) else: resource_instance = serializer.instance - json_api_data = self.build_json_resource_obj(fields, serializer_data, resource_instance, resource_name) + json_api_data = self.build_json_resource_obj( + fields, serializer_data, resource_instance, resource_name + ) meta = self.extract_meta(serializer, serializer_data) if meta: json_api_data.update({'meta': utils.format_keys(meta)}) - included = self.extract_included(fields, serializer_data, resource_instance, included_resources) + included = self.extract_included( + fields, serializer_data, resource_instance, included_resources + ) if included: json_api_included.extend(included) @@ -519,7 +569,9 @@ def render(self, data, accepted_media_type=None, renderer_context=None): unique_compound_documents.append(included_dict) # Sort the items by type then by id - render_data['included'] = sorted(unique_compound_documents, key=lambda item: (item['type'], item['id'])) + render_data['included'] = sorted( + unique_compound_documents, key=lambda item: (item['type'], item['id']) + ) if json_api_meta: render_data['meta'] = utils.format_keys(json_api_meta) diff --git a/rest_framework_json_api/serializers.py b/rest_framework_json_api/serializers.py index 917ae98c..32ffb385 100644 --- a/rest_framework_json_api/serializers.py +++ b/rest_framework_json_api/serializers.py @@ -1,7 +1,7 @@ import inflection from django.utils.translation import ugettext_lazy as _ from rest_framework.exceptions import ParseError -from rest_framework.serializers import * +from rest_framework.serializers import * # noqa: F403 from rest_framework_json_api.relations import ResourceRelatedField from rest_framework_json_api.utils import ( @@ -11,7 +11,9 @@ class ResourceIdentifierObjectSerializer(BaseSerializer): default_error_messages = { - 'incorrect_model_type': _('Incorrect model type. Expected {model_type}, received {received_type}.'), + 'incorrect_model_type': _( + 'Incorrect model type. Expected {model_type}, received {received_type}.' + ), 'does_not_exist': _('Invalid pk "{pk_value}" - object does not exist.'), 'incorrect_type': _('Incorrect type. Expected pk value, received {data_type}.'), } @@ -21,7 +23,9 @@ class ResourceIdentifierObjectSerializer(BaseSerializer): def __init__(self, *args, **kwargs): self.model_class = kwargs.pop('model_class', self.model_class) if 'instance' not in kwargs and not self.model_class: - raise RuntimeError('ResourceIdentifierObjectsSerializer must be initialized with a model class.') + raise RuntimeError( + 'ResourceIdentifierObjectsSerializer must be initialized with a model class.' + ) super(ResourceIdentifierObjectSerializer, self).__init__(*args, **kwargs) def to_representation(self, instance): @@ -32,7 +36,9 @@ def to_representation(self, instance): def to_internal_value(self, data): if data['type'] != get_resource_type_from_model(self.model_class): - self.fail('incorrect_model_type', model_type=self.model_class, received_type=data['type']) + self.fail( + 'incorrect_model_type', model_type=self.model_class, received_type=data['type'] + ) pk = data['id'] try: return self.model_class.objects.get(pk=pk) @@ -48,15 +54,20 @@ def __init__(self, *args, **kwargs): request = context.get('request') if context else None if request: - sparse_fieldset_query_param = 'fields[{}]'.format(get_resource_type_from_serializer(self)) + sparse_fieldset_query_param = 'fields[{}]'.format( + get_resource_type_from_serializer(self) + ) try: - param_name = next(key for key in request.query_params if sparse_fieldset_query_param in key) + param_name = next( + key for key in request.query_params if sparse_fieldset_query_param in key + ) except StopIteration: pass else: fieldset = request.query_params.get(param_name).split(',') - # iterate over a *copy* of self.fields' underlying OrderedDict, because we may modify the - # original during the iteration. self.fields is a `rest_framework.utils.serializer_helpers.BindingDict` + # iterate over a *copy* of self.fields' underlying OrderedDict, because we may + # modify the original during the iteration. + # self.fields is a `rest_framework.utils.serializer_helpers.BindingDict` for field_name, field in self.fields.fields.copy().items(): if field_name == api_settings.URL_FIELD_NAME: # leave self link there continue @@ -100,7 +111,9 @@ def validate_path(serializer_class, field_path, path): super(IncludedResourcesValidationMixin, self).__init__(*args, **kwargs) -class HyperlinkedModelSerializer(IncludedResourcesValidationMixin, SparseFieldsetsMixin, HyperlinkedModelSerializer): +class HyperlinkedModelSerializer( + IncludedResourcesValidationMixin, SparseFieldsetsMixin, HyperlinkedModelSerializer +): """ A type of `ModelSerializer` that uses hyperlinked relationships instead of primary key relationships. Specifically: diff --git a/rest_framework_json_api/utils.py b/rest_framework_json_api/utils.py index a652dec9..f2eefa2b 100644 --- a/rest_framework_json_api/utils.py +++ b/rest_framework_json_api/utils.py @@ -28,11 +28,15 @@ HyperlinkedRouterField = object() if django.VERSION >= (1, 9): - from django.db.models.fields.related_descriptors import ManyToManyDescriptor, ReverseManyToOneDescriptor + from django.db.models.fields.related_descriptors import ( + ManyToManyDescriptor, ReverseManyToOneDescriptor + ) ReverseManyRelatedObjectsDescriptor = object() else: from django.db.models.fields.related import ManyRelatedObjectsDescriptor as ManyToManyDescriptor - from django.db.models.fields.related import ForeignRelatedObjectsDescriptor as ReverseManyToOneDescriptor + from django.db.models.fields.related import ( + ForeignRelatedObjectsDescriptor as ReverseManyToOneDescriptor + ) from django.db.models.fields.related import ReverseManyRelatedObjectsDescriptor # Generic relation descriptor from django.contrib.contenttypes. @@ -42,7 +46,9 @@ elif django.VERSION >= (1, 9): from django.contrib.contenttypes.fields import ReverseGenericManyToOneDescriptor else: - from django.contrib.contenttypes.fields import ReverseGenericRelatedObjectsDescriptor as ReverseGenericManyToOneDescriptor + from django.contrib.contenttypes.fields import ( + ReverseGenericRelatedObjectsDescriptor as ReverseGenericManyToOneDescriptor + ) def get_resource_name(context): @@ -161,7 +167,10 @@ def format_value(value, format_type=None): def format_relation_name(value, format_type=None): - warnings.warn("The 'format_relation_name' function has been renamed 'format_resource_type' and the settings are now 'JSON_API_FORMAT_TYPES' and 'JSON_API_PLURALIZE_TYPES'") + warnings.warn( + "The 'format_relation_name' function has been renamed 'format_resource_type' and the " + "settings are now 'JSON_API_FORMAT_TYPES' and 'JSON_API_PLURALIZE_TYPES'" + ) if format_type is None: format_type = getattr(settings, 'JSON_API_FORMAT_RELATION_KEYS', None) pluralize = getattr(settings, 'JSON_API_PLURALIZE_RELATION_TYPE', None) @@ -300,7 +309,9 @@ def get_included_serializers(serializer): for name, value in six.iteritems(included_serializers): if not isinstance(value, type): if value == 'self': - included_serializers[name] = serializer if isinstance(serializer, type) else serializer.__class__ + included_serializers[name] = ( + serializer if isinstance(serializer, type) else serializer.__class__ + ) else: included_serializers[name] = import_class_from_dotted_path(value) diff --git a/rest_framework_json_api/views.py b/rest_framework_json_api/views.py index 5dcb3a3f..708fb78e 100644 --- a/rest_framework_json_api/views.py +++ b/rest_framework_json_api/views.py @@ -1,12 +1,28 @@ import django from django.core.exceptions import ImproperlyConfigured +from django.db.models import Model +from django.db.models.query import QuerySet +from django.db.models.manager import Manager +from rest_framework import generics, viewsets +from rest_framework.response import Response +from rest_framework.exceptions import NotFound, MethodNotAllowed +from rest_framework.reverse import reverse +from rest_framework.serializers import Serializer + +from rest_framework_json_api.exceptions import Conflict +from rest_framework_json_api.serializers import ResourceIdentifierObjectSerializer +from rest_framework_json_api.utils import ( + get_resource_type_from_instance, + OrderedDict, + Hyperlink, + get_included_resources, +) + if django.VERSION >= (1, 10): from django.urls import NoReverseMatch else: from django.core.urlresolvers import NoReverseMatch -from django.db.models import Model -from django.db.models.query import QuerySet -from django.db.models.manager import Manager + if django.VERSION < (1, 9): from django.db.models.fields.related import ( ForeignRelatedObjectsDescriptor as ReverseManyToOneDescriptor, @@ -21,20 +37,6 @@ ReverseManyToOneDescriptor, ReverseOneToOneDescriptor, ) -from rest_framework import generics, viewsets -from rest_framework.response import Response -from rest_framework.exceptions import NotFound, MethodNotAllowed -from rest_framework.reverse import reverse -from rest_framework.serializers import Serializer - -from rest_framework_json_api.exceptions import Conflict -from rest_framework_json_api.serializers import ResourceIdentifierObjectSerializer -from rest_framework_json_api.utils import ( - get_resource_type_from_instance, - OrderedDict, - Hyperlink, - get_included_resources, -) class ModelViewSet(viewsets.ModelViewSet): @@ -53,12 +55,12 @@ def get_queryset(self, *args, **kwargs): field_class = field.__class__ is_forward_relation = ( - issubclass(field_class, ForwardManyToOneDescriptor) - or issubclass(field_class, ManyToManyDescriptor) + issubclass(field_class, ForwardManyToOneDescriptor) or + issubclass(field_class, ManyToManyDescriptor) ) is_reverse_relation = ( - issubclass(field_class, ReverseManyToOneDescriptor) - or issubclass(field_class, ReverseOneToOneDescriptor) + issubclass(field_class, ReverseManyToOneDescriptor) or + issubclass(field_class, ReverseOneToOneDescriptor) ) if not (is_forward_relation or is_reverse_relation): break @@ -133,7 +135,9 @@ def get_links(self): return_data = OrderedDict() self_link = self.get_url('self', self.self_link_view_name, self.kwargs, self.request) related_kwargs = {self.lookup_field: self.kwargs.get(self.lookup_field)} - related_link = self.get_url('related', self.related_link_view_name, related_kwargs, self.request) + related_link = self.get_url( + 'related', self.related_link_view_name, related_kwargs, self.request + ) if self_link: return_data.update({'self': self_link}) if related_link: @@ -151,7 +155,9 @@ def patch(self, request, *args, **kwargs): if isinstance(related_instance_or_manager, Manager): related_model_class = related_instance_or_manager.model - serializer = self.get_serializer(data=request.data, model_class=related_model_class, many=True) + serializer = self.get_serializer( + data=request.data, model_class=related_model_class, many=True + ) serializer.is_valid(raise_exception=True) related_instance_or_manager.all().delete() # have to set bulk to False since data isn't saved yet @@ -176,7 +182,9 @@ def post(self, request, *args, **kwargs): if isinstance(related_instance_or_manager, Manager): related_model_class = related_instance_or_manager.model - serializer = self.get_serializer(data=request.data, model_class=related_model_class, many=True) + serializer = self.get_serializer( + data=request.data, model_class=related_model_class, many=True + ) serializer.is_valid(raise_exception=True) if frozenset(serializer.validated_data) <= frozenset(related_instance_or_manager.all()): return Response(status=204) @@ -191,15 +199,19 @@ def delete(self, request, *args, **kwargs): if isinstance(related_instance_or_manager, Manager): related_model_class = related_instance_or_manager.model - serializer = self.get_serializer(data=request.data, model_class=related_model_class, many=True) + serializer = self.get_serializer( + data=request.data, model_class=related_model_class, many=True + ) serializer.is_valid(raise_exception=True) - if frozenset(serializer.validated_data).isdisjoint(frozenset(related_instance_or_manager.all())): + objects = related_instance_or_manager.all() + if frozenset(serializer.validated_data).isdisjoint(frozenset(objects)): return Response(status=204) try: related_instance_or_manager.remove(*serializer.validated_data) except AttributeError: raise Conflict( - 'This object cannot be removed from this relationship without being added to another' + 'This object cannot be removed from this relationship without being ' + 'added to another' ) else: raise MethodNotAllowed('DELETE') diff --git a/setup.cfg b/setup.cfg index 0d607daa..f7bd7b99 100644 --- a/setup.cfg +++ b/setup.cfg @@ -5,8 +5,9 @@ test = pytest universal = 1 [flake8] -ignore = E501 +ignore = F405 max-line-length = 100 +exclude = docs/conf.py,build,migrations [isort] known_django = django From c1deeeac740e9b21bc2e61d25330116efcb71304 Mon Sep 17 00:00:00 2001 From: Oliver Sauder Date: Thu, 11 May 2017 16:09:15 +0200 Subject: [PATCH 2/2] Adjusted changelog and authors --- AUTHORS | 1 + CHANGELOG.md | 1 + 2 files changed, 2 insertions(+) diff --git a/AUTHORS b/AUTHORS index 75d66ce1..78ce1bb2 100644 --- a/AUTHORS +++ b/AUTHORS @@ -2,4 +2,5 @@ Jerel Unruh Greg Aker Adam Wróbel Christian Zosel +Oliver Sauder diff --git a/CHANGELOG.md b/CHANGELOG.md index 2dd34ed2..5c157642 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ v2.3.0 and there was no way to turn it off. * Fix for apps that don't use `django.contrib.contenttypes`. * Fix `resource_name` support for POST requests and nested serializers +* Enforcing flake8 linting v2.2.0