diff --git a/core/forms.py b/core/forms.py deleted file mode 100644 index 7c080b7..0000000 --- a/core/forms.py +++ /dev/null @@ -1,18 +0,0 @@ -from django import forms - -from django_images.models import Image - - -FIELD_NAME_MAPPING = { - 'image': 'qqfile', -} - - -class ImageForm(forms.ModelForm): - def add_prefix(self, field_name): - field_name = FIELD_NAME_MAPPING.get(field_name, field_name) - return super(ImageForm, self).add_prefix(field_name) - - class Meta: - model = Image - fields = ('image',) \ No newline at end of file diff --git a/core/serializers.py b/core/serializers.py index 6d970f6..98573dd 100644 --- a/core/serializers.py +++ b/core/serializers.py @@ -107,16 +107,15 @@ class PinSerializer(serializers.HyperlinkedModelSerializer): required=False, ) - def validate(self, attrs): - if 'url' not in attrs and 'image_by_id' not in attrs: + def create(self, validated_data): + if 'url' not in validated_data and\ + 'image_by_id' not in validated_data: raise ValidationError( detail={ "url-or-image": "Either url or image_by_id is required." }, ) - return attrs - def create(self, validated_data): submitter = self.context['request'].user if 'url' in validated_data and validated_data['url']: url = validated_data['url'] @@ -133,9 +132,9 @@ class PinSerializer(serializers.HyperlinkedModelSerializer): return pin def update(self, instance, validated_data): - tags = validated_data.pop('tag_list') + tags = validated_data.pop('tag_list', None) if tags: instance.tags.set(*tags) - # change for image-id is not allowed + # change for image-id or image is not allowed validated_data.pop('image_by_id', None) return super(PinSerializer, self).update(instance, validated_data) diff --git a/core/tests/__init__.py b/core/tests/__init__.py index a2f2474..fc315e5 100644 --- a/core/tests/__init__.py +++ b/core/tests/__init__.py @@ -1,5 +1,3 @@ from .api import * -from .forms import * -from .helpers import PinFactoryTest from .views import * diff --git a/core/tests/api.py b/core/tests/api.py index da00394..2e3e588 100644 --- a/core/tests/api.py +++ b/core/tests/api.py @@ -1,16 +1,15 @@ +import json + +from django.urls import reverse import mock +from rest_framework import status +from rest_framework.test import APITestCase from django_images.models import Thumbnail from taggit.models import Tag -from tastypie.exceptions import Unauthorized -from tastypie.test import ResourceTestCase -from .helpers import ImageFactory, PinFactory, UserFactory +from .helpers import create_image, create_user, create_pin from core.models import Pin, Image -from users.models import User - - -__all__ = ['ImageResourceTest', 'PinResourceTest'] def filter_generator_for(size): @@ -19,265 +18,123 @@ def filter_generator_for(size): return wrapped_func -def mock_requests_get(url): +def mock_requests_get(url, **kwargs): response = mock.Mock(content=open('logo.png', 'rb').read()) return response -class ImageResourceTest(ResourceTestCase): +class ImageTests(APITestCase): def test_post_create_unsupported(self): - """Make sure that new images can't be created using API""" - response = self.api_client.post('/api/v1/image/', format='json', data={}) - self.assertHttpUnauthorized(response) - - def test_list_detail(self): - image = ImageFactory() - thumbnail = filter_generator_for('thumbnail')(image) - standard = filter_generator_for('standard')(image) - square = filter_generator_for('square')(image) - response = self.api_client.get('/api/v1/image/', format='json') - self.assertDictEqual(self.deserialize(response)['objects'][0], { - u'image': unicode(image.image.url), - u'height': image.height, - u'width': image.width, - u'standard': { - u'image': unicode(standard.image.url), - u'width': standard.width, - u'height': standard.height, - }, - u'thumbnail': { - u'image': unicode(thumbnail.image.url), - u'width': thumbnail.width, - u'height': thumbnail.height, - }, - u'square': { - u'image': unicode(square.image.url), - u'width': square.width, - u'height': square.height, - }, - }) + url = reverse("image-list") + data = {} + response = self.client.post( + url, + data=data, + format='json', + ) + self.assertEqual(response.status_code, 403, response.data) -class PinResourceTest(ResourceTestCase): +class PinTests(APITestCase): + _JSON_TYPE = "application/json" + def setUp(self): - super(PinResourceTest, self).setUp() - self.user = UserFactory(password='password') - self.api_client.client.login(username=self.user.username, password='password') + super(PinTests, self).setUp() + self.user = create_user("default") + self.client.login(username=self.user.username, password='password') + + def tearDown(self): + Pin.objects.all().delete() + Image.objects.all().delete() + Tag.objects.all().delete() @mock.patch('requests.get', mock_requests_get) - def test_post_create_url(self): - url = 'http://testserver/mocked/logo.png' - referer = 'http://testserver/' + def test_should_create_pin(self): + url = 'http://testserver.com/mocked/logo-01.png' + create_url = reverse("pin-list") + referer = 'http://testserver.com/' post_data = { - 'submitter': '/api/v1/user/{}/'.format(self.user.pk), 'url': url, 'referer': referer, 'description': 'That\'s an Apple!' } - response = self.api_client.post('/api/v1/pin/', data=post_data) - self.assertHttpCreated(response) - self.assertEqual(Pin.objects.count(), 1) - self.assertEqual(Image.objects.count(), 1) - - # submitter is optional, current user will be used by default - post_data = { - 'url': url, - 'description': 'That\'s an Apple!', - 'origin': None - } - response = self.api_client.post('/api/v1/pin/', data=post_data) - self.assertHttpCreated(response) + response = self.client.post(create_url, data=post_data, format="json") + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + pin = Pin.objects.get(url=url) + self.assertIsNotNone(pin.image.image) @mock.patch('requests.get', mock_requests_get) def test_post_create_url_with_empty_tags(self): - url = 'http://testserver/mocked/logo.png' - referer = 'http://testserver/' + url = 'http://testserver.com/mocked/logo-02.png' + create_url = reverse("pin-list") + referer = 'http://testserver.com/' post_data = { - 'submitter': '/api/v1/user/{}/'.format(self.user.pk), 'url': url, 'referer': referer, 'description': 'That\'s an Apple!', 'tags': [] } - response = self.api_client.post('/api/v1/pin/', data=post_data) - self.assertHttpCreated(response) - self.assertEqual(Pin.objects.count(), 1) + response = self.client.post(create_url, data=post_data, format="json") + self.assertEqual( + response.status_code, status.HTTP_201_CREATED, response.json() + ) self.assertEqual(Image.objects.count(), 1) pin = Pin.objects.get(url=url) + self.assertIsNotNone(pin.image.image) self.assertEqual(pin.tags.count(), 0) - @mock.patch('requests.get', mock_requests_get) - def test_post_create_url_unauthorized(self): - url = 'http://testserver/mocked/logo.png' - referer = 'http://testserver/' + def test_should_post_create_pin_with_existed_image(self): + image = create_image() + create_pin(self.user, image=image, tags=[]) + create_url = reverse("pin-list") + referer = 'http://testserver.com/' post_data = { - 'submitter': '/api/v1/user/2/', - 'url': url, 'referer': referer, - 'description': 'That\'s an Apple!', - 'tags': [] - } - with self.assertRaises(Unauthorized): - response = self.api_client.post('/api/v1/pin/', data=post_data) - self.assertEqual(Pin.objects.count(), 0) - self.assertEqual(Image.objects.count(), 0) - - @mock.patch('requests.get', mock_requests_get) - def test_post_create_url_with_empty_origin(self): - url = 'http://testserver/mocked/logo.png' - referer = 'http://testserver/' - post_data = { - 'submitter': '/api/v1/user/{}/'.format(self.user.pk), - 'url': url, - 'referer': referer, - 'description': 'That\'s an Apple!', - 'origin': None - } - response = self.api_client.post('/api/v1/pin/', data=post_data) - self.assertHttpCreated(response) - self.assertEqual(Pin.objects.count(), 1) - self.assertEqual(Image.objects.count(), 1) - self.assertEqual(Pin.objects.get(url=url).origin, None) - - @mock.patch('requests.get', mock_requests_get) - def test_post_create_url_with_origin(self): - origin = 'http://testserver/mocked/' - url = origin + 'logo.png' - referer = 'http://testserver/' - post_data = { - 'submitter': '/api/v1/user/{}/'.format(self.user.pk), - 'url': url, - 'referer': referer, - 'description': 'That\'s an Apple!', - 'origin': origin - } - response = self.api_client.post('/api/v1/pin/', data=post_data) - self.assertHttpCreated(response) - self.assertEqual(Pin.objects.count(), 1) - self.assertEqual(Image.objects.count(), 1) - self.assertEqual(Pin.objects.get(url=url).origin, origin) - - def test_post_create_obj(self): - image = ImageFactory() - referer = 'http://testserver/' - post_data = { - 'submitter': '/api/v1/user/{}/'.format(self.user.pk), - 'referer': referer, - 'image': '/api/v1/image/{}/'.format(image.pk), + 'image_by_id': image.pk, 'description': 'That\'s something else (probably a CC logo)!', 'tags': ['random', 'tags'], } - response = self.api_client.post('/api/v1/pin/', data=post_data) + response = self.client.post(create_url, data=post_data, format="json") + resp_data = response.json() + self.assertEqual(response.status_code, status.HTTP_201_CREATED, resp_data) self.assertEqual( - self.deserialize(response)['description'], - 'That\'s something else (probably a CC logo)!' + resp_data['description'], + 'That\'s something else (probably a CC logo)!', + resp_data ) - self.assertHttpCreated(response) - # A number of Image objects should stay the same as we are using an existing image - self.assertEqual(Image.objects.count(), 1) - self.assertEqual(Pin.objects.count(), 1) - self.assertEquals(Tag.objects.count(), 2) + self.assertEquals(Pin.objects.count(), 2) - def test_put_detail_unauthenticated(self): - self.api_client.client.logout() - uri = '/api/v1/pin/{}/'.format(PinFactory().pk) - response = self.api_client.put(uri, format='json', data={}) - self.assertHttpUnauthorized(response) + def test_patch_detail_unauthenticated(self): + image = create_image() + pin = create_pin(self.user, image, []) + self.client.logout() + uri = reverse("pin-detail", kwargs={"pk": pin.pk}) + response = self.client.patch(uri, format='json', data={}) + self.assertEqual(response.status_code, 403) - def test_put_detail_unauthorized(self): - uri = '/api/v1/pin/{}/'.format(PinFactory(submitter=self.user).pk) - user = UserFactory(password='password') - self.api_client.client.login(username=user.username, password='password') - response = self.api_client.put(uri, format='json', data={}) - self.assertHttpUnauthorized(response) - - def test_put_detail(self): - pin = PinFactory(submitter=self.user) - uri = '/api/v1/pin/{}/'.format(pin.pk) + def test_patch_detail(self): + image = create_image() + pin = create_pin(self.user, image, []) + uri = reverse("pin-detail", kwargs={"pk": pin.pk}) new = {'description': 'Updated description'} - response = self.api_client.put(uri, format='json', data=new) - self.assertHttpAccepted(response) + response = self.client.patch( + uri, new, format="json", + ) + self.assertEqual(response.status_code, status.HTTP_200_OK, response.json()) self.assertEqual(Pin.objects.count(), 1) self.assertEqual(Pin.objects.get(pk=pin.pk).description, new['description']) def test_delete_detail_unauthenticated(self): - uri = '/api/v1/pin/{}/'.format(PinFactory(submitter=self.user).pk) - self.api_client.client.logout() - self.assertHttpUnauthorized(self.api_client.delete(uri)) - - def test_delete_detail_unauthorized(self): - uri = '/api/v1/pin/{}/'.format(PinFactory(submitter=self.user).pk) - User.objects.create_user('test', 'test@example.com', 'test') - self.api_client.client.login(username='test', password='test') - self.assertHttpUnauthorized(self.api_client.delete(uri)) + image = create_image() + pin = create_pin(self.user, image, []) + uri = reverse("pin-detail", kwargs={"pk": pin.pk}) + self.client.logout() + self.assertEqual(self.client.delete(uri).status_code, 403) def test_delete_detail(self): - uri = '/api/v1/pin/{}/'.format(PinFactory(submitter=self.user).pk) - self.assertHttpAccepted(self.api_client.delete(uri)) + image = create_image() + pin = create_pin(self.user, image, []) + uri = reverse("pin-detail", kwargs={"pk": pin.pk}) + self.client.delete(uri) self.assertEqual(Pin.objects.count(), 0) - - def test_get_list_json_ordered(self): - _, pin = PinFactory(), PinFactory() - response = self.api_client.get('/api/v1/pin/', format='json', data={'order_by': '-id'}) - self.assertValidJSONResponse(response) - self.assertEqual(self.deserialize(response)['objects'][0]['id'], pin.id) - - def test_get_list_json_filtered_by_tags(self): - pin = PinFactory() - response = self.api_client.get('/api/v1/pin/', format='json', data={'tag': pin.tags.all()[0]}) - self.assertValidJSONResponse(response) - self.assertEqual(self.deserialize(response)['objects'][0]['id'], pin.pk) - - def test_get_list_json_filtered_by_submitter(self): - pin = PinFactory(submitter=self.user) - response = self.api_client.get('/api/v1/pin/', format='json', data={'submitter__username': self.user.username}) - self.assertValidJSONResponse(response) - self.assertEqual(self.deserialize(response)['objects'][0]['id'], pin.pk) - - def test_get_list_json(self): - image = ImageFactory() - pin = PinFactory(**{ - 'submitter': self.user, - 'image': image, - 'referer': 'http://testserver/mocked/', - 'url': 'http://testserver/mocked/logo.png', - 'description': u'Mocked Description', - 'origin': None - }) - standard = filter_generator_for('standard')(image) - thumbnail = filter_generator_for('thumbnail')(image) - square = filter_generator_for('square')(image) - response = self.api_client.get('/api/v1/pin/', format='json') - self.assertValidJSONResponse(response) - self.assertDictEqual(self.deserialize(response)['objects'][0], { - u'id': pin.id, - u'submitter': { - u'username': unicode(self.user.username), - u'gravatar': unicode(self.user.gravatar) - }, - u'image': { - u'image': unicode(image.image.url), - u'width': image.width, - u'height': image.height, - u'standard': { - u'image': unicode(standard.image.url), - u'width': standard.width, - u'height': standard.height, - }, - u'thumbnail': { - u'image': unicode(thumbnail.image.url), - u'width': thumbnail.width, - u'height': thumbnail.height, - }, - u'square': { - u'image': unicode(square.image.url), - u'width': square.width, - u'height': square.height, - }, - }, - u'url': pin.url, - u'origin': pin.origin, - u'description': pin.description, - u'tags': [tag.name for tag in pin.tags.all()] - }) diff --git a/core/tests/forms.py b/core/tests/forms.py deleted file mode 100644 index 1166a85..0000000 --- a/core/tests/forms.py +++ /dev/null @@ -1,11 +0,0 @@ -from django.test import TestCase -from ..forms import ImageForm - - -__all__ = ['ImageFormTest'] - -class ImageFormTest(TestCase): - def test_image_field_prefix(self): - """Assert that the image field has a proper name""" - form = ImageForm() - self.assertInHTML("", str(form)) \ No newline at end of file diff --git a/core/tests/helpers.py b/core/tests/helpers.py index 52adeed..be0bd27 100644 --- a/core/tests/helpers.py +++ b/core/tests/helpers.py @@ -1,11 +1,7 @@ from django.conf import settings -from django.contrib.auth.models import Permission from django.core.files.images import ImageFile -from django.db.models.query import QuerySet -from django.test import TestCase from django_images.models import Thumbnail -import factory from taggit.models import Tag from core.models import Pin, Image @@ -15,78 +11,33 @@ from users.models import User TEST_IMAGE_PATH = 'logo.png' -class UserFactory(factory.Factory): - FACTORY_FOR = User - - username = factory.Sequence(lambda n: 'user_{}'.format(n)) - email = factory.Sequence(lambda n: 'user_{}@example.com'.format(n)) - - @factory.post_generation(extract_prefix='password') - def set_password(self, create, extracted, **kwargs): - self.set_password(extracted) - self.save() - - @factory.post_generation(extract_prefix='user_permissions') - def set_user_permissions(self, create, extracted, **kwargs): - self.user_permissions = Permission.objects.filter(codename__in=['add_pin', 'add_image']) +def create_user(username): + user, _ = User.objects.get_or_create( + username='user_{}'.format(username), + defaults={ + "email": 'user_{}@example.com'.format(username) + } + ) + user.set_password("password") + user.save() + return user -class TagFactory(factory.Factory): - FACTORY_FOR = Tag - - name = factory.Sequence(lambda n: 'tag_{}'.format(n)) +def create_tag(name): + return Tag.objects.get_or_create( + name='tag_{}'.format(name), + slug='tag_{}'.format(name), + ) -class ImageFactory(factory.Factory): - FACTORY_FOR = Image - - image = factory.LazyAttribute(lambda a: ImageFile(open(TEST_IMAGE_PATH, 'rb'))) - - @factory.post_generation() - def create_thumbnails(self, create, extracted, **kwargs): - for size in settings.IMAGE_SIZES.keys(): - Thumbnail.objects.get_or_create_at_size(self.pk, size) +def create_image(): + image = Image.objects.create(image=ImageFile(open(TEST_IMAGE_PATH, 'rb'))) + for size in settings.IMAGE_SIZES.keys(): + Thumbnail.objects.get_or_create_at_size(image.pk, size) + return image -class PinFactory(factory.Factory): - FACTORY_FOR = Pin - - submitter = factory.SubFactory(UserFactory) - image = factory.SubFactory(ImageFactory) - - @factory.post_generation(extract_prefix='tags') - def add_tags(self, create, extracted, **kwargs): - if isinstance(extracted, Tag): - self.tags.add(extracted) - elif isinstance(extracted, list): - self.tags.add(*extracted) - elif isinstance(extracted, QuerySet): - self.tags = extracted - else: - self.tags.add(TagFactory()) - - -class PinFactoryTest(TestCase): - def test_default_tags(self): - tags = PinFactory.create().tags.all() - self.assertTrue(all([tag.name.startswith('tag_') for tag in tags])) - self.assertEqual(tags.count(), 1) - - def test_custom_tag(self): - custom = 'custom_tag' - self.assertEqual(PinFactory(tags=Tag.objects.create(name=custom)).tags.get(pk=1).name, custom) - - def test_custom_tags_list(self): - tags = TagFactory.create_batch(2) - PinFactory(tags=tags) - self.assertEqual(Tag.objects.count(), 2) - - def test_custom_tags_queryset(self): - TagFactory.create_batch(2) - tags = Tag.objects.all() - PinFactory(tags=tags) - self.assertEqual(Tag.objects.count(), 2) - - def test_empty_tags(self): - PinFactory(tags=[]) - self.assertEqual(Tag.objects.count(), 0) +def create_pin(user, image, tags): + pin = Pin.objects.create(submitter=user, image=image) + pin.tags.set(*tags) + return pin diff --git a/core/tests/views.py b/core/tests/views.py index e5135b4..a3fadff 100644 --- a/core/tests/views.py +++ b/core/tests/views.py @@ -3,8 +3,9 @@ from django.core.urlresolvers import reverse from django.template import TemplateDoesNotExist from django.test import TestCase -from .api import UserFactory from core.models import Image +from core.tests import create_user +from users.models import User __all__ = ['CreateImageTest'] @@ -12,25 +13,27 @@ __all__ = ['CreateImageTest'] class CreateImageTest(TestCase): def setUp(self): - self.user = UserFactory(password='password') + self.user = create_user("default") self.client.login(username=self.user.username, password='password') - def test_get_browser(self): - response = self.client.get(reverse('core:create-image')) - self.assertRedirects(response, reverse('core:recent-pins')) - - def test_get_xml_http_request(self): - with self.assertRaises(TemplateDoesNotExist): - self.client.get(reverse('core:create-image'), HTTP_X_REQUESTED_WITH='XMLHttpRequest') + def tearDown(self): + User.objects.all().delete() + Image.objects.all().delete() def test_post(self): - with open(settings.SITE_ROOT + 'logo.png', mode='rb') as image: - response = self.client.post(reverse('core:create-image'), {'qqfile': image}) + with open('logo.png', mode='rb') as image: + response = self.client.post(reverse('image-list'), {'image': image}) image = Image.objects.latest('pk') - self.assertJSONEqual(response.content, {'success': {'id': image.pk}}) + self.assertEqual(response.json()['id'], image.pk) def test_post_error(self): - response = self.client.post(reverse('core:create-image'), {'qqfile': None}) - self.assertJSONEqual(response.content, { - 'error': {'image': ['This field is required.']} - }) + response = self.client.post(reverse('image-list'), {'image': None}) + self.assertJSONEqual( + response.content, + { + 'image': [ + 'The submitted data was not a file. ' + 'Check the encoding type on the form.' + ] + } + )