annotate gpp/bio/forms.py @ 265:1ba2c6bf6eb7

Closing #98. Animated GIFs were losing their transparency and animated properties when saved as avatars. Reworked the avatar save process to only run the avatar through PIL if it is too big. This preserves the original uploaded file if it is within the desired size settings. This may still mangle big animated gifs. If this becomes a problem, then maybe look into calling the PIL Image.resize() method directly. Moved the PIL image specific functions from bio.forms to a new module: core.image for better reusability in the future.
author Brian Neal <bgneal@gmail.com>
date Fri, 24 Sep 2010 02:12:09 +0000
parents 272d3a8c98e8
children 88b2b9cb8c1f
rev   line source
gremmie@1 1 """
gremmie@1 2 This file contains the forms used by the bio application.
gremmie@1 3 """
gremmie@1 4 try:
gremmie@1 5 from cStringIO import StringIO
gremmie@1 6 except:
gremmie@1 7 from StringIO import StringIO
gremmie@1 8
gremmie@1 9 from django import forms
gremmie@1 10 from django.conf import settings
gremmie@1 11 from django.core.files.base import ContentFile
gremmie@1 12 from django.contrib.auth.models import User
gremmie@1 13
gremmie@1 14 from bio.models import UserProfile
bgneal@149 15 from core.widgets import AutoCompleteUserInput
bgneal@265 16 from core.image import parse_image, downscale_image_square
gremmie@1 17
gremmie@1 18
gremmie@1 19 class EditUserForm(forms.ModelForm):
gremmie@1 20 """Form for editing the fields of the User model."""
gremmie@1 21 email = forms.EmailField(label='Email', required=True)
gremmie@1 22 class Meta:
gremmie@1 23 model = User
gremmie@1 24 fields = ('first_name', 'last_name', 'email')
gremmie@1 25
gremmie@1 26
gremmie@1 27 class EditUserProfileForm(forms.ModelForm):
gremmie@1 28 """Form for editing the fields of the UserProfile model."""
gremmie@1 29 location = forms.CharField(required=False, widget=forms.TextInput(attrs={'size' : 64 }))
gremmie@1 30 occupation = forms.CharField(required=False, widget=forms.TextInput(attrs={'size' : 64 }))
gremmie@1 31 interests = forms.CharField(required=False, widget=forms.TextInput(attrs={'size' : 64 }))
bgneal@70 32 time_zone = forms.CharField(required=False, widget=forms.HiddenInput())
bgneal@120 33 use_24_time = forms.BooleanField(label='Show times in 24-hour mode', required=False)
bgneal@133 34 profile_text = forms.CharField(required=False,
bgneal@133 35 widget=forms.Textarea(attrs={'class': 'markItUp'}))
bgneal@133 36 signature = forms.CharField(required=False,
bgneal@133 37 widget=forms.Textarea(attrs={'class': 'markItUp'}))
gremmie@1 38
gremmie@1 39 class Meta:
gremmie@1 40 model = UserProfile
bgneal@206 41 fields = ('location', 'birthday', 'occupation', 'interests',
bgneal@206 42 'profile_text', 'hide_email', 'signature', 'time_zone',
bgneal@206 43 'use_24_time', )
gremmie@1 44
gremmie@1 45 class Media:
gremmie@1 46 css = {
bgneal@6 47 'all': settings.GPP_THIRD_PARTY_CSS['markitup'] + \
bgneal@6 48 settings.GPP_THIRD_PARTY_CSS['jquery-ui']
gremmie@1 49 }
bgneal@12 50 js = settings.GPP_THIRD_PARTY_JS['markitup'] + \
bgneal@6 51 settings.GPP_THIRD_PARTY_JS['jquery-ui'] + \
bgneal@70 52 ('js/bio.js', 'js/timezone.js')
gremmie@1 53
gremmie@1 54
gremmie@1 55 class UploadAvatarForm(forms.Form):
gremmie@1 56 """Form used to change a user's avatar"""
gremmie@1 57 avatar_file = forms.ImageField(required=False)
gremmie@1 58 image = None
gremmie@1 59
gremmie@1 60 def clean_avatar_file(self):
bgneal@265 61 f = self.cleaned_data['avatar_file']
bgneal@265 62 if f is not None:
bgneal@265 63 if f.size > settings.MAX_AVATAR_SIZE_BYTES:
bgneal@265 64 raise forms.ValidationError("Please upload a file smaller than "
bgneal@265 65 "%s bytes." % settings.MAX_AVATAR_SIZE)
bgneal@265 66 try:
bgneal@265 67 self.image = parse_image(f)
bgneal@265 68 except IOError:
bgneal@265 69 raise forms.ValidationError("Please upload a valid image. "
bgneal@265 70 "The file you uploaded was either not an image or a "
bgneal@265 71 "corrupted image.")
bgneal@265 72 self.file_type = self.image.format
bgneal@265 73 return f
gremmie@1 74
bgneal@265 75 def save(self):
bgneal@265 76 """
bgneal@265 77 Perform any down-scaling needed on the new file, then return a tuple of
bgneal@265 78 (filename, file object). Note that the file object returned may not
bgneal@265 79 have a name; use the returned filename instead.
bgneal@265 80
bgneal@265 81 """
bgneal@265 82 if not self.cleaned_data['avatar_file']:
bgneal@265 83 return None, None
bgneal@265 84
bgneal@265 85 name = self.cleaned_data['avatar_file'].name
bgneal@265 86 dim = settings.MAX_AVATAR_SIZE_PIXELS
bgneal@265 87 max_size = (dim, dim)
bgneal@265 88 if self.image and self.image.size > max_size:
bgneal@265 89 self.image = downscale_image_square(self.image, dim)
bgneal@265 90
bgneal@265 91 # We need to return a Django File now. To get that from here,
bgneal@265 92 # write the image data info a StringIO and then construct a
bgneal@265 93 # Django ContentFile from that. The ContentFile has no name,
bgneal@265 94 # that is why we return one ourselves explicitly.
gremmie@1 95 s = StringIO()
bgneal@265 96 self.image.save(s, self.file_type)
bgneal@265 97 return name, ContentFile(s.getvalue())
bgneal@265 98
bgneal@265 99 return name, self.cleaned_data['avatar_file']
gremmie@1 100
bgneal@149 101
bgneal@149 102 class SearchUsersForm(forms.Form):
bgneal@149 103 """
bgneal@149 104 A form to search for users.
bgneal@149 105 """
bgneal@149 106 username = forms.CharField(max_length=30, widget=AutoCompleteUserInput())
bgneal@149 107
bgneal@197 108 class Media:
bgneal@197 109 css = {
bgneal@197 110 'all': settings.GPP_THIRD_PARTY_CSS['jquery-ui']
bgneal@197 111 }
bgneal@197 112 js = settings.GPP_THIRD_PARTY_JS['jquery-ui']
bgneal@197 113
bgneal@149 114 def clean_username(self):
bgneal@149 115 username = self.cleaned_data['username']
bgneal@149 116 try:
bgneal@149 117 User.objects.get(username=username, is_active=True)
bgneal@149 118 except User.DoesNotExist:
bgneal@149 119 raise forms.ValidationError("That username does not exist.")
bgneal@149 120 return username