mirror of
https://github.com/pinry/pinry.git
synced 2025-11-13 16:45:41 +01:00
Refactor apps to be in repo folder
This commit is contained in:
0
core/__init__.py
Normal file
0
core/__init__.py
Normal file
10
core/admin.py
Normal file
10
core/admin.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from django.contrib import admin
|
||||
|
||||
from .models import Pin
|
||||
|
||||
|
||||
class PinAdmin(admin.ModelAdmin):
|
||||
pass
|
||||
|
||||
admin.site.register(Pin, PinAdmin)
|
||||
|
||||
150
core/api.py
Normal file
150
core/api.py
Normal file
@@ -0,0 +1,150 @@
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from tastypie import fields
|
||||
from tastypie.authorization import DjangoAuthorization
|
||||
from tastypie.constants import ALL, ALL_WITH_RELATIONS
|
||||
from tastypie.exceptions import Unauthorized
|
||||
from tastypie.resources import ModelResource
|
||||
from django_images.models import Thumbnail
|
||||
|
||||
from .models import Pin, Image
|
||||
from users.models import User
|
||||
|
||||
|
||||
class PinryAuthorization(DjangoAuthorization):
|
||||
"""
|
||||
Pinry-specific Authorization backend with object-level permission checking.
|
||||
"""
|
||||
def update_detail(self, object_list, bundle):
|
||||
klass = self.base_checks(bundle.request, bundle.obj.__class__)
|
||||
|
||||
if klass is False:
|
||||
raise Unauthorized("You are not allowed to access that resource.")
|
||||
|
||||
permission = '%s.change_%s' % (klass._meta.app_label, klass._meta.model_name)
|
||||
|
||||
if not bundle.request.user.has_perm(permission, bundle.obj):
|
||||
raise Unauthorized("You are not allowed to access that resource.")
|
||||
|
||||
return True
|
||||
|
||||
def delete_detail(self, object_list, bundle):
|
||||
klass = self.base_checks(bundle.request, bundle.obj.__class__)
|
||||
|
||||
if klass is False:
|
||||
raise Unauthorized("You are not allowed to access that resource.")
|
||||
|
||||
permission = '%s.delete_%s' % (klass._meta.app_label, klass._meta.model_name)
|
||||
|
||||
if not bundle.request.user.has_perm(permission, bundle.obj):
|
||||
raise Unauthorized("You are not allowed to access that resource.")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class UserResource(ModelResource):
|
||||
gravatar = fields.CharField(readonly=True)
|
||||
|
||||
def dehydrate_gravatar(self, bundle):
|
||||
return bundle.obj.gravatar
|
||||
|
||||
class Meta:
|
||||
list_allowed_methods = ['get']
|
||||
filtering = {
|
||||
'username': ALL
|
||||
}
|
||||
queryset = User.objects.all()
|
||||
resource_name = 'user'
|
||||
fields = ['username']
|
||||
include_resource_uri = False
|
||||
|
||||
|
||||
def filter_generator_for(size):
|
||||
def wrapped_func(bundle, **kwargs):
|
||||
if hasattr(bundle.obj, '_prefetched_objects_cache') and 'thumbnail' in bundle.obj._prefetched_objects_cache:
|
||||
for thumbnail in bundle.obj._prefetched_objects_cache['thumbnail']:
|
||||
if thumbnail.size == size:
|
||||
return thumbnail
|
||||
raise ObjectDoesNotExist()
|
||||
else:
|
||||
return bundle.obj.get_by_size(size)
|
||||
return wrapped_func
|
||||
|
||||
|
||||
class ThumbnailResource(ModelResource):
|
||||
class Meta:
|
||||
list_allowed_methods = ['get']
|
||||
fields = ['image', 'width', 'height']
|
||||
queryset = Thumbnail.objects.all()
|
||||
resource_name = 'thumbnail'
|
||||
include_resource_uri = False
|
||||
|
||||
|
||||
class ImageResource(ModelResource):
|
||||
standard = fields.ToOneField(ThumbnailResource, full=True,
|
||||
attribute=lambda bundle: filter_generator_for('standard')(bundle))
|
||||
thumbnail = fields.ToOneField(ThumbnailResource, full=True,
|
||||
attribute=lambda bundle: filter_generator_for('thumbnail')(bundle))
|
||||
square = fields.ToOneField(ThumbnailResource, full=True,
|
||||
attribute=lambda bundle: filter_generator_for('square')(bundle))
|
||||
|
||||
class Meta:
|
||||
fields = ['image', 'width', 'height']
|
||||
include_resource_uri = False
|
||||
resource_name = 'image'
|
||||
queryset = Image.objects.all()
|
||||
authorization = DjangoAuthorization()
|
||||
|
||||
|
||||
class PinResource(ModelResource):
|
||||
submitter = fields.ToOneField(UserResource, 'submitter', full=True)
|
||||
image = fields.ToOneField(ImageResource, 'image', full=True)
|
||||
tags = fields.ListField()
|
||||
|
||||
def hydrate_image(self, bundle):
|
||||
url = bundle.data.get('url', None)
|
||||
if url:
|
||||
image = Image.objects.create_for_url(url)
|
||||
bundle.data['image'] = '/api/v1/image/{}/'.format(image.pk)
|
||||
return bundle
|
||||
|
||||
def hydrate(self, bundle):
|
||||
"""Run some early/generic processing
|
||||
|
||||
Make sure that user is authorized to create Pins first, before
|
||||
we hydrate the Image resource, creating the Image object in process
|
||||
"""
|
||||
submitter = bundle.data.get('submitter', None)
|
||||
if not submitter:
|
||||
bundle.data['submitter'] = '/api/v1/user/{}/'.format(bundle.request.user.pk)
|
||||
else:
|
||||
if not '/api/v1/user/{}/'.format(bundle.request.user.pk) == submitter:
|
||||
raise Unauthorized("You are not authorized to create Pins for other users")
|
||||
return bundle
|
||||
|
||||
def dehydrate_tags(self, bundle):
|
||||
return map(str, bundle.obj.tags.all())
|
||||
|
||||
def build_filters(self, filters=None):
|
||||
orm_filters = super(PinResource, self).build_filters(filters)
|
||||
if filters and 'tag' in filters:
|
||||
orm_filters['tags__name__in'] = filters['tag'].split(',')
|
||||
return orm_filters
|
||||
|
||||
def save_m2m(self, bundle):
|
||||
tags = bundle.data.get('tags', None)
|
||||
if tags:
|
||||
bundle.obj.tags.set(*tags)
|
||||
return super(PinResource, self).save_m2m(bundle)
|
||||
|
||||
class Meta:
|
||||
fields = ['id', 'url', 'origin', 'description']
|
||||
ordering = ['id']
|
||||
filtering = {
|
||||
'submitter': ALL_WITH_RELATIONS
|
||||
}
|
||||
queryset = Pin.objects.all().select_related('submitter', 'image'). \
|
||||
prefetch_related('image__thumbnail_set', 'tags')
|
||||
resource_name = 'pin'
|
||||
include_resource_uri = False
|
||||
always_return_data = True
|
||||
authorization = PinryAuthorization()
|
||||
7
core/apps.py
Normal file
7
core/apps.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class CoreConfig(AppConfig):
|
||||
name = 'core'
|
||||
8
core/context_processors.py
Normal file
8
core/context_processors.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
def template_settings(request):
|
||||
return {
|
||||
'API_LIMIT_PER_PAGE': settings.API_LIMIT_PER_PAGE,
|
||||
}
|
||||
|
||||
18
core/forms.py
Normal file
18
core/forms.py
Normal file
@@ -0,0 +1,18 @@
|
||||
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',)
|
||||
51
core/migrations/0001_initial.py
Normal file
51
core/migrations/0001_initial.py
Normal file
@@ -0,0 +1,51 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
import taggit.managers
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('taggit', '0002_auto_20150616_2121'),
|
||||
('users', '__first__'),
|
||||
('django_images', '__first__'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Pin',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('url', models.URLField(null=True)),
|
||||
('origin', models.URLField(null=True)),
|
||||
('description', models.TextField(null=True, blank=True)),
|
||||
('published', models.DateTimeField(auto_now_add=True)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Image',
|
||||
fields=[
|
||||
],
|
||||
options={
|
||||
'proxy': True,
|
||||
},
|
||||
bases=('django_images.image',),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='pin',
|
||||
name='image',
|
||||
field=models.ForeignKey(related_name='pin', to='core.Image'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='pin',
|
||||
name='submitter',
|
||||
field=models.ForeignKey(to='users.User'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='pin',
|
||||
name='tags',
|
||||
field=taggit.managers.TaggableManager(to='taggit.Tag', through='taggit.TaggedItem', help_text='A comma-separated list of tags.', verbose_name='Tags'),
|
||||
),
|
||||
]
|
||||
0
core/migrations/__init__.py
Normal file
0
core/migrations/__init__.py
Normal file
51
core/models.py
Normal file
51
core/models.py
Normal file
@@ -0,0 +1,51 @@
|
||||
import requests
|
||||
|
||||
from io import BytesIO
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.files.uploadedfile import InMemoryUploadedFile
|
||||
from django.db import models, transaction
|
||||
|
||||
from django_images.models import Image as BaseImage, Thumbnail
|
||||
from taggit.managers import TaggableManager
|
||||
|
||||
from users.models import User
|
||||
|
||||
|
||||
class ImageManager(models.Manager):
|
||||
# FIXME: Move this into an asynchronous task
|
||||
def create_for_url(self, url):
|
||||
file_name = url.split("/")[-1].split('#')[0].split('?')[0]
|
||||
buf = BytesIO()
|
||||
response = requests.get(url)
|
||||
buf.write(response.content)
|
||||
obj = InMemoryUploadedFile(buf, 'image', file_name,
|
||||
None, buf.tell(), None)
|
||||
# create the image and its thumbnails in one transaction, removing
|
||||
# a chance of getting Database into a inconsistent state when we
|
||||
# try to create thumbnails one by one later
|
||||
image = self.create(image=obj)
|
||||
for size in settings.IMAGE_SIZES.keys():
|
||||
Thumbnail.objects.get_or_create_at_size(image.pk, size)
|
||||
return image
|
||||
|
||||
|
||||
class Image(BaseImage):
|
||||
objects = ImageManager()
|
||||
|
||||
class Meta:
|
||||
proxy = True
|
||||
|
||||
|
||||
class Pin(models.Model):
|
||||
submitter = models.ForeignKey(User)
|
||||
url = models.URLField(null=True)
|
||||
origin = models.URLField(null=True)
|
||||
description = models.TextField(blank=True, null=True)
|
||||
image = models.ForeignKey(Image, related_name='pin')
|
||||
published = models.DateTimeField(auto_now_add=True)
|
||||
tags = TaggableManager()
|
||||
|
||||
def __unicode__(self):
|
||||
return '%s - %s' % (self.submitter, self.published)
|
||||
|
||||
0
core/templatetags/__init__.py
Normal file
0
core/templatetags/__init__.py
Normal file
10
core/templatetags/bootstrap_field.py
Normal file
10
core/templatetags/bootstrap_field.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from django.template import loader, Context, Library
|
||||
|
||||
|
||||
register = Library()
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def bootstrap_field(field):
|
||||
template = loader.get_template('core/templatetags/bootstrap_field.html')
|
||||
return template.render(Context({'field': field}))
|
||||
5
core/tests/__init__.py
Normal file
5
core/tests/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from .api import *
|
||||
from .forms import *
|
||||
from .helpers import PinFactoryTest
|
||||
from .views import *
|
||||
|
||||
270
core/tests/api.py
Normal file
270
core/tests/api.py
Normal file
@@ -0,0 +1,270 @@
|
||||
import mock
|
||||
|
||||
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 ..models import Pin, Image
|
||||
from ...users.models import User
|
||||
|
||||
|
||||
__all__ = ['ImageResourceTest', 'PinResourceTest']
|
||||
|
||||
|
||||
def filter_generator_for(size):
|
||||
def wrapped_func(obj):
|
||||
return Thumbnail.objects.get_or_create_at_size(obj.pk, size)
|
||||
return wrapped_func
|
||||
|
||||
|
||||
def mock_requests_get(url):
|
||||
response = mock.Mock(content=open('logo.png', 'rb').read())
|
||||
return response
|
||||
|
||||
|
||||
class ImageResourceTest(ResourceTestCase):
|
||||
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,
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
class PinResourceTest(ResourceTestCase):
|
||||
def setUp(self):
|
||||
super(PinResourceTest, self).setUp()
|
||||
self.user = UserFactory(password='password')
|
||||
self.api_client.client.login(username=self.user.username, password='password')
|
||||
|
||||
@mock.patch('requests.get', mock_requests_get)
|
||||
def test_post_create_url(self):
|
||||
url = 'http://testserver/mocked/logo.png'
|
||||
post_data = {
|
||||
'submitter': '/api/v1/user/{}/'.format(self.user.pk),
|
||||
'url': url,
|
||||
'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)
|
||||
|
||||
@mock.patch('requests.get', mock_requests_get)
|
||||
def test_post_create_url_with_empty_tags(self):
|
||||
url = 'http://testserver/mocked/logo.png'
|
||||
post_data = {
|
||||
'submitter': '/api/v1/user/{}/'.format(self.user.pk),
|
||||
'url': url,
|
||||
'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)
|
||||
self.assertEqual(Image.objects.count(), 1)
|
||||
pin = Pin.objects.get(url=url)
|
||||
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'
|
||||
post_data = {
|
||||
'submitter': '/api/v1/user/2/',
|
||||
'url': url,
|
||||
'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'
|
||||
post_data = {
|
||||
'submitter': '/api/v1/user/{}/'.format(self.user.pk),
|
||||
'url': url,
|
||||
'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'
|
||||
post_data = {
|
||||
'submitter': '/api/v1/user/{}/'.format(self.user.pk),
|
||||
'url': url,
|
||||
'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()
|
||||
post_data = {
|
||||
'submitter': '/api/v1/user/{}/'.format(self.user.pk),
|
||||
'image': '/api/v1/image/{}/'.format(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)
|
||||
self.assertEqual(
|
||||
self.deserialize(response)['description'],
|
||||
'That\'s something else (probably a CC logo)!'
|
||||
)
|
||||
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)
|
||||
|
||||
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_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)
|
||||
new = {'description': 'Updated description'}
|
||||
|
||||
response = self.api_client.put(uri, format='json', data=new)
|
||||
self.assertHttpAccepted(response)
|
||||
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))
|
||||
|
||||
def test_delete_detail(self):
|
||||
uri = '/api/v1/pin/{}/'.format(PinFactory(submitter=self.user).pk)
|
||||
self.assertHttpAccepted(self.api_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,
|
||||
'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()]
|
||||
})
|
||||
11
core/tests/forms.py
Normal file
11
core/tests/forms.py
Normal file
@@ -0,0 +1,11 @@
|
||||
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("<input id='id_qqfile' name='qqfile' type='file' />", str(form))
|
||||
92
core/tests/helpers.py
Normal file
92
core/tests/helpers.py
Normal file
@@ -0,0 +1,92 @@
|
||||
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 ..models import Pin, Image
|
||||
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'])
|
||||
|
||||
|
||||
class TagFactory(factory.Factory):
|
||||
FACTORY_FOR = Tag
|
||||
|
||||
name = factory.Sequence(lambda n: 'tag_{}'.format(n))
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
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)
|
||||
36
core/tests/views.py
Normal file
36
core/tests/views.py
Normal file
@@ -0,0 +1,36 @@
|
||||
from django.conf import settings
|
||||
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
|
||||
|
||||
|
||||
__all__ = ['CreateImageTest']
|
||||
|
||||
|
||||
class CreateImageTest(TestCase):
|
||||
def setUp(self):
|
||||
self.user = UserFactory(password='password')
|
||||
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 test_post(self):
|
||||
with open(settings.SITE_ROOT + 'logo.png', mode='rb') as image:
|
||||
response = self.client.post(reverse('core:create-image'), {'qqfile': image})
|
||||
image = Image.objects.latest('pk')
|
||||
self.assertJSONEqual(response.content, {'success': {'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.']}
|
||||
})
|
||||
31
core/urls.py
Normal file
31
core/urls.py
Normal file
@@ -0,0 +1,31 @@
|
||||
from django.conf.urls import patterns, include, url
|
||||
from django.views.generic import TemplateView
|
||||
|
||||
from tastypie.api import Api
|
||||
|
||||
from .api import ImageResource, ThumbnailResource, PinResource, UserResource
|
||||
from .views import CreateImage
|
||||
|
||||
|
||||
v1_api = Api(api_name='v1')
|
||||
v1_api.register(ImageResource())
|
||||
v1_api.register(ThumbnailResource())
|
||||
v1_api.register(PinResource())
|
||||
v1_api.register(UserResource())
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^api/', include(v1_api.urls, namespace='api')),
|
||||
|
||||
url(r'^pins/pin-form/$', TemplateView.as_view(template_name='core/pin_form.html'),
|
||||
name='pin-form'),
|
||||
url(r'^pins/create-image/$', CreateImage.as_view(), name='create-image'),
|
||||
|
||||
url(r'^pins/tag/(?P<tag>(\w|-)+)/$', TemplateView.as_view(template_name='core/pins.html'),
|
||||
name='tag-pins'),
|
||||
url(r'^pins/user/(?P<user>(\w|-)+)/$', TemplateView.as_view(template_name='core/pins.html'),
|
||||
name='user-pins'),
|
||||
url(r'^(?P<pin>[0-9]+)/$', TemplateView.as_view(template_name='core/pins.html'),
|
||||
name='recent-pins'),
|
||||
url(r'^$', TemplateView.as_view(template_name='core/pins.html'),
|
||||
name='recent-pins'),
|
||||
)
|
||||
16
core/utils.py
Normal file
16
core/utils.py
Normal file
@@ -0,0 +1,16 @@
|
||||
import hashlib
|
||||
import os
|
||||
|
||||
def upload_path(instance, filename, **kwargs):
|
||||
hasher = hashlib.md5()
|
||||
for chunk in instance.image.chunks():
|
||||
hasher.update(chunk)
|
||||
hash = hasher.hexdigest()
|
||||
base, ext = os.path.splitext(filename)
|
||||
return '%(first)s/%(second)s/%(hash)s/%(base)s%(ext)s' % {
|
||||
'first': hash[0],
|
||||
'second': hash[1],
|
||||
'hash': hash,
|
||||
'base': base,
|
||||
'ext': ext,
|
||||
}
|
||||
34
core/views.py
Normal file
34
core/views.py
Normal file
@@ -0,0 +1,34 @@
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.conf import settings
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.views.generic import CreateView
|
||||
from django_images.models import Image
|
||||
|
||||
from braces.views import JSONResponseMixin, LoginRequiredMixin
|
||||
from django_images.models import Thumbnail
|
||||
|
||||
from .forms import ImageForm
|
||||
|
||||
|
||||
class CreateImage(JSONResponseMixin, LoginRequiredMixin, CreateView):
|
||||
template_name = None # JavaScript-only view
|
||||
model = Image
|
||||
form_class = ImageForm
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
if not request.is_ajax():
|
||||
return HttpResponseRedirect(reverse('core:recent-pins'))
|
||||
return super(CreateImage, self).get(request, *args, **kwargs)
|
||||
|
||||
def form_valid(self, form):
|
||||
image = form.save()
|
||||
for size in settings.IMAGE_SIZES:
|
||||
Thumbnail.objects.get_or_create_at_size(image.pk, size)
|
||||
return self.render_json_response({
|
||||
'success': {
|
||||
'id': image.id
|
||||
}
|
||||
})
|
||||
|
||||
def form_invalid(self, form):
|
||||
return self.render_json_response({'error': form.errors})
|
||||
Reference in New Issue
Block a user