Another major Pinry model rewrite

Generate thumbnail and standard image on request, and use
http://github.com/mirumee/django-images for generating them. Also,
remove the CreatePin page as pin creation is going to be done
in JavaScript. Create UploadImage view for uploading images from
computer.
This commit is contained in:
Krzysztof Klimonda
2013-02-25 04:08:35 +01:00
parent 4f2b94616c
commit d0a71244b5
10 changed files with 76 additions and 179 deletions

2
.gitignore vendored
View File

@@ -9,4 +9,4 @@
/media/
/static/
*.pyc
/.idea/

View File

@@ -1,3 +1,7 @@
from django.conf import settings
from django_images.models import Thumbnail
from tastypie.resources import ModelResource
from tastypie import fields
from tastypie.authorization import DjangoAuthorization
@@ -27,10 +31,15 @@ class PinResource(ModelResource):
submitter = fields.ForeignKey(UserResource, 'submitter', full=True)
def dehydrate_images(self, bundle):
images = {}
for type in ['standard', 'thumbnail', 'original']:
image_obj = getattr(bundle.obj, type, None)
images[type] = {'url': image_obj.image.url, 'width': image_obj.width, 'height': image_obj.height}
original = bundle.obj.image
images = {'original': {
'url': original.get_absolute_url(), 'width': original.width, 'height': original.height}
}
for image in ['standard', 'thumbnail']:
obj = Thumbnail.objects.get_or_create_at_size(original.pk, image)
images[image] = {
'url': obj.get_absolute_url(), 'width': obj.width, 'height': obj.height
}
return images
class Meta:

View File

@@ -1,44 +1,9 @@
from django import forms
from .models import Pin
from django_images.models import Image
class PinForm(forms.ModelForm):
url = forms.CharField(required=False)
image = forms.ImageField(label='or Upload', required=False)
_errors = {
'not_image': 'Requested URL is not an image file. Only images are currently supported.',
'pinned': 'URL has already been pinned!',
'protocol': 'Currently only support HTTP and HTTPS protocols, please be sure you include this in the URL.',
'nothing': 'Need either a URL or Upload',
}
class ImageForm(forms.ModelForm):
class Meta:
model = Pin
fields = ['url', 'image', 'description', 'tags']
def clean(self):
cleaned_data = super(PinForm, self).clean()
url = cleaned_data.get('url')
image = cleaned_data.get('image')
if url:
image_file_types = ['png', 'gif', 'jpeg', 'jpg']
if not url.split('.')[-1].lower() in image_file_types:
raise forms.ValidationError(self._errors['not_image'])
protocol = url.split(':')[0]
if protocol not in ['http', 'https']:
raise forms.ValidationError(self._errors['protocol'])
try:
Pin.objects.get(url=url)
raise forms.ValidationError(self._errors['pinned'])
except Pin.DoesNotExist:
pass
elif image:
pass
else:
raise forms.ValidationError(self._errors['nothing'])
return cleaned_data
model = Image
fields = ('image',)

View File

@@ -1,42 +0,0 @@
import os
import urllib2
from cStringIO import StringIO
from django.conf import settings
from django.core.files.uploadedfile import InMemoryUploadedFile
from django.db import models
from . import utils
class OriginalImageManager(models.Manager):
def create_for_url(self, url):
buf = StringIO()
buf.write(urllib2.urlopen(url).read())
fname = url.split('/')[-1]
temporary_file = InMemoryUploadedFile(buf, "image", fname,
content_type=None, size=buf.tell(), charset=None)
temporary_file.name = fname
return self.create(image=temporary_file)
class BaseImageManager(models.Manager):
image_size = None
def get_or_create_for(self, original):
buf = StringIO()
img = utils.scale_and_crop(original.image, settings.IMAGE_SIZES[self.image_size])
img.save(buf, img.format, **img.info)
original_dir, original_file = os.path.split(original.image.name)
file_obj = InMemoryUploadedFile(buf, "image", original_file, None, buf.tell(), None)
image = self.create(original=original, image=file_obj)
return image
class StandardImageManager(BaseImageManager):
image_size = 'standard'
class ThumbnailManager(BaseImageManager):
image_size = 'thumbnail'

View File

@@ -1,89 +1,21 @@
import hashlib
from django.db import models
from django_images.models import Image
from taggit.managers import TaggableManager
from ..core.models import User
from .managers import OriginalImageManager
from .managers import StandardImageManager
from .managers import ThumbnailManager
def hashed_upload_to(prefix, instance, filename):
md5 = hashlib.md5()
for chunk in instance.image.chunks():
md5.update(chunk)
file_hash = md5.hexdigest()
arguments = {
'prefix': prefix,
'first': file_hash[0],
'second': file_hash[1],
'hash': file_hash,
'filename': filename
}
return "{prefix}/{first}/{second}/{hash}/{filename}".format(**arguments)
def original_upload_to(instance, filename):
return hashed_upload_to('image/original/by-md5', instance, filename)
def thumbnail_upload_to(instance, filename):
return hashed_upload_to('image/thumbnail/by-md5', instance, filename)
def standard_upload_to(instance, filename):
return hashed_upload_to('image/standard/by-md5', instance, filename)
class Image(models.Model):
height = models.PositiveIntegerField(default=0, editable=False)
width = models.PositiveIntegerField(default=0, editable=False)
class Meta:
abstract = True
class OriginalImage(Image):
image = models.ImageField(upload_to=original_upload_to,
height_field='height', width_field='width', max_length=255)
objects = OriginalImageManager()
class StandardImage(Image):
original = models.ForeignKey(OriginalImage, related_name='standard')
image = models.ImageField(upload_to=standard_upload_to,
height_field='height', width_field='width', max_length=255)
objects = StandardImageManager()
class Thumbnail(Image):
original = models.ForeignKey(OriginalImage, related_name='thumbnail')
image = models.ImageField(upload_to=thumbnail_upload_to,
height_field='height', width_field='width', max_length=255)
objects = ThumbnailManager()
class Pin(models.Model):
submitter = models.ForeignKey(User)
url = models.TextField(blank=True, null=True)
description = models.TextField(blank=True, null=True)
original = models.ForeignKey(OriginalImage, related_name='pin')
standard = models.ForeignKey(StandardImage, related_name='pin')
thumbnail = models.ForeignKey(Thumbnail, related_name='pin')
image = models.ForeignKey(Image, related_name='pin')
published = models.DateTimeField(auto_now_add=True)
tags = TaggableManager()
def __unicode__(self):
return self.url
def save(self, *args, **kwargs):
if not self.pk:
self.original = OriginalImage.objects.create_for_url(self.url)
self.standard = StandardImage.objects.get_or_create_for(self.original)
self.thumbnail = Thumbnail.objects.get_or_create_for(self.original)
super(Pin, self).save(*args, **kwargs)
class Meta:
ordering = ['-id']

View File

@@ -2,7 +2,7 @@ from django.template.loader import render_to_string
from django.template import Library
from django.template import RequestContext
from pinry.pins.forms import PinForm
from pinry.pins.forms import ImageForm
register = Library()
@@ -11,5 +11,5 @@ register = Library()
@register.simple_tag
def new_pin(request):
return render_to_string('pins/templatetags/new_pin.html',
{'form': PinForm()},
{'form': ImageForm()},
context_instance=RequestContext(request))

View File

@@ -1,11 +1,11 @@
from django.conf.urls import patterns, url
from .views import RecentPins
from .views import NewPin
from .views import UploadImage
urlpatterns = patterns('pinry.pins.views',
url(r'^$', RecentPins.as_view(), name='recent-pins'),
url(r'^tag/.+/$', RecentPins.as_view(), name='tag'),
url(r'^new-pin/$', NewPin.as_view(), name='new-pin'),
url(r'^upload-pin/$', UploadImage.as_view(), name='new-pin'),
)

View File

@@ -1,31 +1,62 @@
from django.http import HttpResponseRedirect
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.contrib.auth.decorators import login_required
from django.core.urlresolvers import reverse
from django.contrib import messages
from django.utils.decorators import method_decorator
from django.utils.functional import lazy
from django.views.generic.base import TemplateView
from django.views.generic import CreateView
from django.views.generic import (
TemplateView, CreateView)
from .forms import PinForm
from .models import Pin
from django_images.models import Image
from .forms import ImageForm
reverse_lazy = lambda name=None, *args: lazy(reverse, str)(name, args=args)
class RecentPins(TemplateView):
template_name = 'pins/recent_pins.html'
class LoginRequiredMixin(object):
"""
A login required mixin for use with class based views. This Class is a light wrapper around the
`login_required` decorator and hence function parameters are just attributes defined on the class.
Due to parent class order traversal this mixin must be added as the left most
mixin of a view.
The mixin has exactly the same flow as `login_required` decorator:
If the user isn't logged in, redirect to settings.LOGIN_URL, passing the current
absolute path in the query string. Example: /accounts/login/?next=/polls/3/.
If the user is logged in, execute the view normally. The view code is free to
assume the user is logged in.
**Class Settings**
`redirect_field_name - defaults to "next"
`login_url` - the login url of your site
"""
redirect_field_name = REDIRECT_FIELD_NAME
login_url = None
@method_decorator(login_required(redirect_field_name=redirect_field_name, login_url=login_url))
def dispatch(self, request, *args, **kwargs):
return super(LoginRequiredMixin, self).dispatch(request, *args, **kwargs)
class NewPin(CreateView):
model = Pin
form_class = PinForm
success_url = reverse_lazy('pins:recent-pins')
class UploadImage(LoginRequiredMixin, CreateView):
template_name = 'pins/pin_form.html'
model = Image
form_class = ImageForm
def form_valid(self, form):
form.instance.submitter = self.request.user
messages.success(self.request, 'New pin successfully added.')
return super(NewPin, self).form_valid(form)
return super(UploadImage, self).form_valid(form)
def form_invalid(self, form):
messages.error(self.request, 'Pin did not pass validation!')
return super(NewPin, self).form_invalid(form)
return super(UploadImage, self).form_invalid(form)
class RecentPins(TemplateView):
template_name = 'pins/recent_pins.html'

View File

@@ -86,12 +86,13 @@ INSTALLED_APPS = (
'south',
'compressor',
'taggit',
'django_images',
'pinry.core',
'pinry.pins',
'pinry.api',
)
AUTHENTICATION_BACKENDS = ('pinry.core.auth.backends.CombinedAuthBackend', 'django.contrib.auth.backends.ModelBackend',)
Dimensions = namedtuple("Dimensions", ['width', 'height'])
IMAGE_SIZES = {'thumbnail': Dimensions(width=240, height=0), 'standard': Dimensions(width=600, height=0)}
IMAGE_SIZES = {
'thumbnail': {'size': [240, 0]},
'standard': {'size': [600, 0]},
}

View File

@@ -5,4 +5,5 @@ django-tastypie
django_compressor
cssmin
jsmin
-e git://github.com/hcarvalhoalves/django-taggit.git@e0f9642d7b94c8e6c0feb520d96bb6ae4d78a4d0#egg=django-taggit
django-images
http://github.com/hcarvalhoalves/django-taggit/tarball/master#egg=django-taggit