posted by me checkbox
[zzz-floof.git] / floof / controllers / art.py
1 import logging
2
3 from pylons import config, request, response, session, tmpl_context as c, h
4 from pylons.controllers.util import abort, redirect
5 from pylons import url
6 from floof.lib.base import BaseController, render
7
8 log = logging.getLogger(__name__)
9
10 from floof.lib import file_storage as storage
11 from floof.model.users import User
12 from floof.model import Art, Rating, UserRelation
13 from floof.model.comments import Discussion
14 from floof.model.users import User, UserRelationship
15
16 import elixir
17 #import magic
18 import os.path
19 import PIL
20 import PIL.Image
21 from sqlalchemy import func
22 from sqlalchemy.exceptions import IntegrityError
23 from sqlalchemy.orm.exc import NoResultFound
24 from wtforms.validators import ValidationError
25 from wtforms import *
26
27
28 class ArtUploadForm(Form):
29 by = TextField('Artists')
30 by_me = BooleanField('me')
31 file = FileField('Upload')
32 url = TextField('Link')
33
34 # TODO: make this general purpose
35 def validate_file(self, field):
36 if field.data == u'':
37 raise ValidationError('File is required')
38
39 # Also make this into a general User List field validator
40 """ PLEASE NOTE! I just realized that I need to have a __str__ method on User
41 to get it to write the usernames back in the form when it redisplays them, since
42 this validator turns them into user objects instead. This fact actually sounds dangerous
43 to me in the future, since it means I proably shouldn't be changing the data input
44 by the user right here in the validator, or the user will see the post-mangled data instead
45 of what they actually typed. Hm.
46
47 One solution to this could be to only look up the users after normal validation is over,
48 and then manually add validation errors to the form if that fails. But I think that kind of
49 sucks. Perhaps the ideology in Formish, where they keep Validation and Conversion as
50 separate tasks, is a better way of doing it? That way there is less risk of changing the user's
51 input -- you do that at the conversiot stage -- yet it is still encapsulated in the form workflow.
52 Hm. But that means I'd have to query for the users in the validation step and throw them away,
53 or something equally stupid. Guess there's no perfect solution here, but I thought it was
54 worth discussing.
55
56 Btw, this is meant to be used by a field with multi user autocompletion on it (like on stackoverflow tags),
57 so the user should never actually submit anything invalid unless they disable javascript and force it.
58 """
59 def validate_by(self, field):
60 if not field.data:
61 raise ValidationError("Needs at least one creator")
62 user_names = field.data.split()
63 users = []
64 # TODO: Could totally do a filter__in here instead of picking them out individually
65 for user_name in user_names:
66 user = User.get_by(name=user_name)
67 if not user:
68 raise ValidationError("Couldn't find user %s" % user_name)
69 users.append(user)
70 field.data = users
71
72 class ArtController(BaseController):
73 def __before__(self, id=None):
74 super(ArtController, self).__before__()
75 # Awesome refactoring!
76 if id:
77 c.art = h.get_object_or_404(Art, id=id)
78
79 def new(self):
80 """ New Art! """
81 c.form = ArtUploadForm()
82 return render("/art/new.mako")
83
84 # TODO: login required
85 def create(self):
86 c.form = ArtUploadForm(request.params)
87 if not c.form.validate():
88 ## TODO: JavaScript should be added to the upload form so that it is
89 ## impossible to submit the form when it contains any invalid users,
90 ## so this never happens. Only autocompled usernames should be allowed.
91 return render("/art/new.mako")
92
93 # Save the file
94 upload = request.params['file']
95 hash = storage.save_file('art/original', upload.file)
96
97 # Create a thumbnail and a medium view
98 img = PIL.Image.open(
99 config['app_conf']['static_root']
100 + storage.get_path('art/original', hash)[1:]
101 )
102 for subdir, config_size in [('medium', config['medium_size']),
103 ('thumbnail', config['thumbnail_size'])]:
104 path = config['app_conf']['static_root'] + storage.get_path("art/{0}".format(subdir), hash)
105
106 dir, _ = os.path.split(path)
107 if not os.path.exists(dir):
108 os.makedirs(dir)
109
110 size = int(config_size)
111 shrunken_img = img.copy()
112 shrunken_img.thumbnail((size, size), PIL.Image.ANTIALIAS)
113 shrunken_img.save(path, img.format)
114
115 #magicker = magic.Magic(mime=True)
116 buffer = upload.file.read(64)
117 upload.file.seek(0)
118 mimetype = 'image/unknown' #magickery.from_buffer(buffer)
119
120 # XXX Ensure we can actually handle the mimetype
121
122 print mimetype
123 c.art = Art(
124 uploader=c.user,
125 original_filename=upload.filename,
126 hash=hash,
127 mimetype=mimetype,
128 )
129 c.art.discussion = Discussion(count=0)
130
131 if c.form.by_me and c.user not in c.form.by.data:
132 UserRelation(user=c.user, creator=c.user, kind="by", art=c.art)
133
134 for artist in c.form.by.data:
135 UserRelation(user=artist, creator=c.user, kind="by", art=c.art)
136
137
138 try:
139 elixir.session.commit()
140 redirect(url('show_art', id=c.art.id))
141 except IntegrityError:
142 # XXX Check this as early as possible, and clean up the filesystem!
143 # Right now this replaces the original file!
144 hash = c.art.hash
145 elixir.session.rollback()
146 duplicate_art = Art.get_by(hash=hash)
147 h.flash("We already have that one.")
148 redirect(url('show_art', id=duplicate_art.id))
149
150
151
152 def show(self, id):
153 # c.art = h.get_object_or_404(Art, id=id)
154 if c.user:
155 c.your_score = c.art.user_score(c.user)
156 return render("/art/show.mako")
157
158
159 # TODO: login required
160 def rate(self, id):
161 # c.art = h.get_object_or_404(Art, id=id)
162 score = request.params.get("score")
163 if score and score.isnumeric():
164 score = int(score)
165 else:
166 score = Rating.reverse_options.get(score)
167
168 c.art.rate(score, c.user)
169 elixir.session.commit()
170
171 redirect(url('show_art', id=c.art.id))
172
173
174 def watchstream(self, name):
175 """Watchstream for a certain user."""
176 try:
177 c.watching_user = User.query.filter(func.lower(User.name) == name) \
178 .one()
179 except NoResultFound:
180 abort(404)
181
182 # This user has watches which are users which have art
183 # XXX use artist, not uploader
184 c.artwork = Art.query.join(Art.uploader,
185 User.target_of_relationships) \
186 .filter(UserRelationship.user_id == c.watching_user.id)
187
188 return render('/index.mako')