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
|
bgneal@612
|
14 import pytz
|
bgneal@612
|
15
|
gremmie@1
|
16 from bio.models import UserProfile
|
bgneal@1011
|
17 from core.html import image_check
|
bgneal@1011
|
18 from core.html import ImageCheckError
|
bgneal@1011
|
19 from core.images.utils import parse_image, downscale_image_square
|
bgneal@1011
|
20 from core.markup import site_markup
|
bgneal@149
|
21 from core.widgets import AutoCompleteUserInput
|
gremmie@1
|
22
|
gremmie@1
|
23
|
gremmie@1
|
24 class EditUserForm(forms.ModelForm):
|
gremmie@1
|
25 """Form for editing the fields of the User model."""
|
gremmie@1
|
26 email = forms.EmailField(label='Email', required=True)
|
gremmie@1
|
27 class Meta:
|
gremmie@1
|
28 model = User
|
gremmie@1
|
29 fields = ('first_name', 'last_name', 'email')
|
gremmie@1
|
30
|
gremmie@1
|
31
|
gremmie@1
|
32 class EditUserProfileForm(forms.ModelForm):
|
gremmie@1
|
33 """Form for editing the fields of the UserProfile model."""
|
bgneal@609
|
34 location = forms.CharField(required=False, widget=forms.TextInput(attrs={'size': 64 }))
|
bgneal@609
|
35 occupation = forms.CharField(required=False, widget=forms.TextInput(attrs={'size': 64 }))
|
bgneal@609
|
36 interests = forms.CharField(required=False, widget=forms.TextInput(attrs={'size': 64 }))
|
bgneal@70
|
37 time_zone = forms.CharField(required=False, widget=forms.HiddenInput())
|
bgneal@120
|
38 use_24_time = forms.BooleanField(label='Show times in 24-hour mode', required=False)
|
bgneal@312
|
39 profile_text = forms.CharField(required=False,
|
bgneal@390
|
40 widget=forms.Textarea(attrs={'class': 'markItUp'}))
|
bgneal@312
|
41 signature = forms.CharField(required=False,
|
bgneal@390
|
42 widget=forms.Textarea(attrs={'class': 'markItUp'}))
|
bgneal@390
|
43 auto_favorite = forms.BooleanField(
|
bgneal@390
|
44 label='Automatically favorite every forum topic I create or reply to', required=False)
|
bgneal@390
|
45 auto_subscribe = forms.BooleanField(
|
bgneal@390
|
46 label='Automatically subscribe to every forum topic I create or reply to', required=False)
|
gremmie@1
|
47
|
gremmie@1
|
48 class Meta:
|
gremmie@1
|
49 model = UserProfile
|
bgneal@609
|
50 fields = ('location', 'country', 'birthday', 'occupation', 'interests',
|
bgneal@206
|
51 'profile_text', 'hide_email', 'signature', 'time_zone',
|
bgneal@390
|
52 'use_24_time', 'auto_favorite', 'auto_subscribe')
|
gremmie@1
|
53
|
gremmie@1
|
54 class Media:
|
gremmie@1
|
55 css = {
|
bgneal@484
|
56 'all': (settings.GPP_THIRD_PARTY_CSS['markitup'] +
|
bgneal@484
|
57 settings.GPP_THIRD_PARTY_CSS['jquery-ui'])
|
gremmie@1
|
58 }
|
bgneal@484
|
59 js = (settings.GPP_THIRD_PARTY_JS['markitup'] +
|
bgneal@484
|
60 settings.GPP_THIRD_PARTY_JS['jquery-ui'] +
|
bgneal@484
|
61 ['js/bio.js', 'js/timezone.js'])
|
gremmie@1
|
62
|
bgneal@612
|
63 def clean_time_zone(self):
|
bgneal@612
|
64 """Ensure the timezone is valid and will work with pytz.
|
bgneal@1011
|
65
|
bgneal@612
|
66 A blank (empty) value is allowed.
|
bgneal@612
|
67 """
|
bgneal@612
|
68
|
bgneal@612
|
69 tz = self.cleaned_data['time_zone'].strip()
|
bgneal@612
|
70 if tz:
|
bgneal@612
|
71 try:
|
bgneal@612
|
72 pytz.timezone(tz)
|
bgneal@612
|
73 except pytz.UnknownTimeZoneError:
|
bgneal@612
|
74 raise forms.ValidationError('Invalid timezone')
|
bgneal@612
|
75
|
bgneal@612
|
76 return tz
|
bgneal@612
|
77
|
bgneal@1011
|
78 def _image_check(self, field_name):
|
bgneal@1011
|
79 text = self.cleaned_data[field_name]
|
bgneal@1011
|
80 if text:
|
bgneal@1011
|
81 html = site_markup(text)
|
bgneal@1011
|
82 try:
|
bgneal@1011
|
83 image_check(html)
|
bgneal@1011
|
84 except ImageCheckError as ex:
|
bgneal@1011
|
85 raise forms.ValidationError(str(ex))
|
bgneal@1011
|
86 return text
|
bgneal@1011
|
87
|
bgneal@1011
|
88 def clean_profile_text(self):
|
bgneal@1011
|
89 return self._image_check('profile_text')
|
bgneal@1011
|
90
|
bgneal@1011
|
91 def clean_signature(self):
|
bgneal@1011
|
92 return self._image_check('signature')
|
bgneal@1011
|
93
|
gremmie@1
|
94
|
gremmie@1
|
95 class UploadAvatarForm(forms.Form):
|
gremmie@1
|
96 """Form used to change a user's avatar"""
|
gremmie@1
|
97 avatar_file = forms.ImageField(required=False)
|
gremmie@1
|
98 image = None
|
gremmie@1
|
99
|
gremmie@1
|
100 def clean_avatar_file(self):
|
bgneal@265
|
101 f = self.cleaned_data['avatar_file']
|
bgneal@265
|
102 if f is not None:
|
bgneal@265
|
103 if f.size > settings.MAX_AVATAR_SIZE_BYTES:
|
bgneal@265
|
104 raise forms.ValidationError("Please upload a file smaller than "
|
bgneal@338
|
105 "%s bytes." % settings.MAX_AVATAR_SIZE_BYTES)
|
bgneal@265
|
106 try:
|
bgneal@265
|
107 self.image = parse_image(f)
|
bgneal@265
|
108 except IOError:
|
bgneal@265
|
109 raise forms.ValidationError("Please upload a valid image. "
|
bgneal@265
|
110 "The file you uploaded was either not an image or a "
|
bgneal@265
|
111 "corrupted image.")
|
bgneal@265
|
112 self.file_type = self.image.format
|
bgneal@265
|
113 return f
|
gremmie@1
|
114
|
bgneal@265
|
115 def save(self):
|
bgneal@265
|
116 """
|
bgneal@265
|
117 Perform any down-scaling needed on the new file, then return a tuple of
|
bgneal@265
|
118 (filename, file object). Note that the file object returned may not
|
bgneal@265
|
119 have a name; use the returned filename instead.
|
bgneal@265
|
120
|
bgneal@265
|
121 """
|
bgneal@265
|
122 if not self.cleaned_data['avatar_file']:
|
bgneal@265
|
123 return None, None
|
bgneal@265
|
124
|
bgneal@265
|
125 name = self.cleaned_data['avatar_file'].name
|
bgneal@265
|
126 dim = settings.MAX_AVATAR_SIZE_PIXELS
|
bgneal@265
|
127 max_size = (dim, dim)
|
bgneal@265
|
128 if self.image and self.image.size > max_size:
|
bgneal@265
|
129 self.image = downscale_image_square(self.image, dim)
|
bgneal@265
|
130
|
bgneal@265
|
131 # We need to return a Django File now. To get that from here,
|
bgneal@265
|
132 # write the image data info a StringIO and then construct a
|
bgneal@265
|
133 # Django ContentFile from that. The ContentFile has no name,
|
bgneal@265
|
134 # that is why we return one ourselves explicitly.
|
gremmie@1
|
135 s = StringIO()
|
bgneal@265
|
136 self.image.save(s, self.file_type)
|
bgneal@265
|
137 return name, ContentFile(s.getvalue())
|
bgneal@312
|
138
|
bgneal@265
|
139 return name, self.cleaned_data['avatar_file']
|
gremmie@1
|
140
|
bgneal@149
|
141
|
bgneal@149
|
142 class SearchUsersForm(forms.Form):
|
bgneal@149
|
143 """
|
bgneal@149
|
144 A form to search for users.
|
bgneal@149
|
145 """
|
bgneal@149
|
146 username = forms.CharField(max_length=30, widget=AutoCompleteUserInput())
|
bgneal@149
|
147
|
bgneal@197
|
148 class Media:
|
bgneal@197
|
149 css = {
|
bgneal@197
|
150 'all': settings.GPP_THIRD_PARTY_CSS['jquery-ui']
|
bgneal@197
|
151 }
|
bgneal@197
|
152 js = settings.GPP_THIRD_PARTY_JS['jquery-ui']
|
bgneal@197
|
153
|
bgneal@149
|
154 def clean_username(self):
|
bgneal@463
|
155 username = self.cleaned_data['username'].strip()
|
bgneal@149
|
156 try:
|
bgneal@149
|
157 User.objects.get(username=username, is_active=True)
|
bgneal@149
|
158 except User.DoesNotExist:
|
bgneal@149
|
159 raise forms.ValidationError("That username does not exist.")
|
bgneal@149
|
160 return username
|