1 #!/usr/bin/python2.5 |
|
2 # |
|
3 # Copyright 2008 the Melange authors. |
|
4 # |
|
5 # Licensed under the Apache License, Version 2.0 (the "License"); |
|
6 # you may not use this file except in compliance with the License. |
|
7 # You may obtain a copy of the License at |
|
8 # |
|
9 # http://www.apache.org/licenses/LICENSE-2.0 |
|
10 # |
|
11 # Unless required by applicable law or agreed to in writing, software |
|
12 # distributed under the License is distributed on an "AS IS" BASIS, |
|
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
14 # See the License for the specific language governing permissions and |
|
15 # limitations under the License. |
|
16 |
|
17 """Developer views for editing and examining User profiles. |
|
18 """ |
|
19 |
|
20 __authors__ = [ |
|
21 '"Todd Larsen" <tlarsen@google.com>', |
|
22 ] |
|
23 |
|
24 |
|
25 from google.appengine.api import users |
|
26 |
|
27 from django import forms |
|
28 from django import http |
|
29 from django.utils.translation import ugettext_lazy |
|
30 |
|
31 from soc.logic import accounts |
|
32 from soc.logic import models |
|
33 from soc.logic import out_of_band |
|
34 from soc.logic import validate |
|
35 from soc.views import simple |
|
36 from soc.views import helper |
|
37 from soc.views.helper import access |
|
38 from soc.views.helper import decorators |
|
39 from soc.views.user import profile |
|
40 |
|
41 import soc.logic |
|
42 import soc.models.user |
|
43 import soc.views.helper.forms |
|
44 import soc.views.helper.lists |
|
45 import soc.views.helper.requests |
|
46 import soc.views.helper.responses |
|
47 |
|
48 |
|
49 class LookupForm(helper.forms.BaseForm): |
|
50 """Django form displayed for a Developer to look up a User. |
|
51 |
|
52 This form is manually specified, instead of using |
|
53 model = soc.models.user.User |
|
54 in the Meta class, because the form behavior is unusual and normally |
|
55 required Properties of the User model need to sometimes be omitted. |
|
56 |
|
57 Also, this form only permits entry and editing of some of the User entity |
|
58 Properties, not all of them. |
|
59 """ |
|
60 account = forms.EmailField(required=False, |
|
61 label=soc.models.user.User.account.verbose_name, |
|
62 help_text=soc.models.user.User.account.help_text) |
|
63 |
|
64 link_id = forms.CharField(required=False, |
|
65 label=soc.models.user.User.link_id.verbose_name, |
|
66 help_text=soc.models.user.User.link_id.help_text) |
|
67 |
|
68 class Meta: |
|
69 model = None |
|
70 |
|
71 def clean_link_id(self): |
|
72 link_id = self.cleaned_data.get('link_id') |
|
73 |
|
74 if not link_id: |
|
75 # link ID not supplied (which is OK), so do not try to validate it |
|
76 return None |
|
77 |
|
78 if not validate.isLinkIdFormatValid(link_id): |
|
79 raise forms.ValidationError('This link ID is in wrong format.') |
|
80 |
|
81 return link_id |
|
82 |
|
83 def clean_account(self): |
|
84 email = self.cleaned_data.get('account') |
|
85 |
|
86 if not email: |
|
87 # email not supplied (which is OK), so do not try to convert it |
|
88 return None |
|
89 |
|
90 try: |
|
91 return users.User(email=email) |
|
92 except users.UserNotFoundError: |
|
93 raise forms.ValidationError('Account not found.') |
|
94 |
|
95 |
|
96 DEF_SITE_USER_PROFILE_LOOKUP_TMPL = 'soc/user/lookup.html' |
|
97 |
|
98 @decorators.view |
|
99 def lookup(request, page_name=None, template=DEF_SITE_USER_PROFILE_LOOKUP_TMPL): |
|
100 """View for a Developer to look up a User Model entity. |
|
101 |
|
102 Args: |
|
103 request: the standard django request object |
|
104 page_name: the page name displayed in templates as page and header title |
|
105 template: the "sibling" template (or a search list of such templates) |
|
106 from which to construct the public.html template name (or names) |
|
107 |
|
108 Returns: |
|
109 A subclass of django.http.HttpResponse which either contains the form to |
|
110 be filled out, or a redirect to the correct view in the interface. |
|
111 """ |
|
112 |
|
113 try: |
|
114 access.checkIsDeveloper(request) |
|
115 except soc.views.out_of_band.AccessViolationResponse, alt_response: |
|
116 return alt_response.response() |
|
117 |
|
118 # create default template context for use with any templates |
|
119 context = helper.responses.getUniversalContext(request) |
|
120 context['page_name'] = page_name |
|
121 |
|
122 user = None # assume that no User entity will be found |
|
123 form = None # assume blank form needs to be displayed |
|
124 lookup_message = ugettext_lazy('Enter information to look up a User.') |
|
125 email_error = None # assume no email look-up errors |
|
126 context['lookup_link'] = None |
|
127 |
|
128 if request.method == 'POST': |
|
129 form = LookupForm(request.POST) |
|
130 |
|
131 if form.is_valid(): |
|
132 form_account = form.cleaned_data.get('account') |
|
133 |
|
134 if form_account: |
|
135 # email provided, so attempt to look up user by email |
|
136 user = models.user.logic.getForFields( |
|
137 {'account': form_account}, unique=True) |
|
138 |
|
139 if user: |
|
140 lookup_message = ugettext_lazy('User found by email.') |
|
141 else: |
|
142 email_error = ugettext_lazy('User with that email not found.') |
|
143 range_width = helper.lists.getPreferredListPagination() |
|
144 nearest_user_range_start = ( |
|
145 models.user.logic.findNearestEntitiesOffset( |
|
146 width, [('account', form_account)])) |
|
147 |
|
148 if nearest_user_range_start is not None: |
|
149 context['lookup_link'] = './list?offset=%s&limit=%s' % ( |
|
150 nearest_user_range_start, range_width) |
|
151 if not user: |
|
152 # user not found yet, so see if link ID was provided |
|
153 link_id = form.cleaned_data.get('link_id') |
|
154 |
|
155 if link_id: |
|
156 # link ID provided, so try to look up by link ID |
|
157 user = models.user.logic.getForFields({'link_id': link_id}, |
|
158 unique=True) |
|
159 if user: |
|
160 lookup_message = ugettext_lazy('User found by link ID.') |
|
161 # clear previous error, since User was found |
|
162 email_error = None |
|
163 # clear previous lookup_link, since User was found, the lookup_link |
|
164 # is not needed to display. |
|
165 context['lookup_link'] = None |
|
166 else: |
|
167 context['link_id_error'] = ugettext_lazy( |
|
168 'User with that link ID not found.') |
|
169 if context['lookup_link'] is None: |
|
170 range_width = helper.lists.getPreferredListPagination() |
|
171 nearest_user_range_start = ( |
|
172 models.user.logic.findNearestEntitiesOffset( |
|
173 width, [('link_id', link_id)])) |
|
174 |
|
175 if nearest_user_range_start is not None: |
|
176 context['lookup_link'] = './list?offset=%s&limit=%s' % ( |
|
177 nearest_user_range_start, range_width) |
|
178 # else: form was not valid |
|
179 # else: # method == 'GET' |
|
180 |
|
181 if user: |
|
182 # User entity found, so populate form with existing User information |
|
183 # context['found_user'] = user |
|
184 form = LookupForm(initial={'account': user.account.email(), |
|
185 'link_id': user.link_id}) |
|
186 |
|
187 if request.path.endswith('lookup'): |
|
188 # convert /lookup path into /profile/link_id path |
|
189 context['edit_link'] = helper.requests.replaceSuffix( |
|
190 request.path, 'lookup', 'profile/%s' % user.link_id) |
|
191 # else: URL is not one that was expected, so do not display edit link |
|
192 elif not form: |
|
193 # no pre-populated form was constructed, so show the empty look-up form |
|
194 form = LookupForm() |
|
195 |
|
196 context.update({'form': form, |
|
197 'found_user': user, |
|
198 'email_error': email_error, |
|
199 'lookup_message': lookup_message}) |
|
200 |
|
201 return helper.responses.respond(request, template, context) |
|
202 |
|
203 |
|
204 class EditForm(helper.forms.BaseForm): |
|
205 """Django form displayed when Developer edits a User. |
|
206 |
|
207 This form is manually specified, instead of using |
|
208 model = soc.models.user.User |
|
209 in the Meta class, because the form behavior is unusual and normally |
|
210 required Properties of the User model need to sometimes be omitted. |
|
211 """ |
|
212 account = forms.EmailField( |
|
213 label=soc.models.user.User.account.verbose_name, |
|
214 help_text=soc.models.user.User.account.help_text) |
|
215 |
|
216 link_id = forms.CharField( |
|
217 label=soc.models.user.User.link_id.verbose_name, |
|
218 help_text=soc.models.user.User.link_id.help_text) |
|
219 |
|
220 nick_name = forms.CharField( |
|
221 label=soc.models.user.User.nick_name.verbose_name) |
|
222 |
|
223 is_developer = forms.BooleanField(required=False, |
|
224 label=soc.models.user.User.is_developer.verbose_name, |
|
225 help_text=soc.models.user.User.is_developer.help_text) |
|
226 |
|
227 key_name = forms.CharField(widget=forms.HiddenInput) |
|
228 |
|
229 class Meta: |
|
230 model = None |
|
231 |
|
232 def clean_link_id(self): |
|
233 link_id = self.cleaned_data.get('link_id') |
|
234 if not validate.isLinkIdFormatValid(link_id): |
|
235 raise forms.ValidationError("This link ID is in wrong format.") |
|
236 |
|
237 key_name = self.data.get('key_name') |
|
238 if key_name: |
|
239 key_name_user = user_logic.logic.getFromKeyName(key_name) |
|
240 |
|
241 if link_id_user and key_name_user and \ |
|
242 link_id_user.account != key_name_user.account: |
|
243 raise forms.ValidationError("This link ID is already in use.") |
|
244 |
|
245 return link_id |
|
246 |
|
247 def clean_account(self): |
|
248 form_account = users.User(email=self.cleaned_data.get('account')) |
|
249 if not accounts.isAccountAvailable( |
|
250 form_account, existing_key_name=self.data.get('key_name')): |
|
251 raise forms.ValidationError("This account is already in use.") |
|
252 if models.user.logic.isFormerAccount(form_account): |
|
253 raise forms.ValidationError("This account is invalid. " |
|
254 "It exists as a former account.") |
|
255 return form_account |
|
256 |
|
257 |
|
258 DEF_SITE_USER_PROFILE_EDIT_TMPL = 'soc/user/edit.html' |
|
259 DEF_CREATE_NEW_USER_MSG = ' You can create a new user by visiting' \ |
|
260 ' <a href="/site/user/profile">Create ' \ |
|
261 'a New User</a> page.' |
|
262 |
|
263 @decorators.view |
|
264 def edit(request, page_name=None, link_id=None, |
|
265 template=DEF_SITE_USER_PROFILE_EDIT_TMPL): |
|
266 """View for a Developer to modify the properties of a User Model entity. |
|
267 |
|
268 Args: |
|
269 request: the standard django request object |
|
270 page_name: the page name displayed in templates as page and header title |
|
271 link_id: the User's site-unique "link_id" extracted from the URL |
|
272 template: the "sibling" template (or a search list of such templates) |
|
273 from which to construct the public.html template name (or names) |
|
274 |
|
275 Returns: |
|
276 A subclass of django.http.HttpResponse which either contains the form to |
|
277 be filled out, or a redirect to the correct view in the interface. |
|
278 """ |
|
279 |
|
280 try: |
|
281 access.checkIsDeveloper(request) |
|
282 except soc.views.out_of_band.AccessViolationResponse, alt_response: |
|
283 return alt_response.response() |
|
284 |
|
285 # create default template context for use with any templates |
|
286 context = helper.responses.getUniversalContext(request) |
|
287 context['page_name'] = page_name |
|
288 |
|
289 user = None # assume that no User entity will be found |
|
290 |
|
291 # try to fetch User entity corresponding to link_id if one exists |
|
292 try: |
|
293 if link_id: |
|
294 user = accounts.getUserFromLinkIdOr404(link_id) |
|
295 except out_of_band.ErrorResponse, error: |
|
296 # show custom 404 page when link ID doesn't exist in Datastore |
|
297 error.message = error.message + DEF_CREATE_NEW_USER_MSG |
|
298 return simple.errorResponse(request, page_name, error, template, context) |
|
299 |
|
300 |
|
301 if request.method == 'POST': |
|
302 form = EditForm(request.POST) |
|
303 |
|
304 if form.is_valid(): |
|
305 key_name = form.cleaned_data.get('key_name') |
|
306 new_link_id = form.cleaned_data.get('link_id') |
|
307 |
|
308 properties = {} |
|
309 properties['account'] = form.cleaned_data.get('account') |
|
310 properties['link_id'] = new_link_id |
|
311 properties['nick_name'] = form.cleaned_data.get('nick_name') |
|
312 properties['is_developer'] = form.cleaned_data.get('is_developer') |
|
313 |
|
314 user = models.user.logic.updateOrCreateFromKeyName(properties, key_name) |
|
315 |
|
316 if not user: |
|
317 return http.HttpResponseRedirect('/') |
|
318 |
|
319 # redirect to new /site/user/profile/new_link_id?s=0 |
|
320 # (causes 'Profile saved' message to be displayed) |
|
321 return helper.responses.redirectToChangedSuffix( |
|
322 request, link_id, new_link_id, |
|
323 params=profile.SUBMIT_PROFILE_SAVED_PARAMS) |
|
324 else: # method == 'GET': |
|
325 # try to fetch User entity corresponding to link ID if one exists |
|
326 if link_id: |
|
327 if user: |
|
328 # is 'Profile saved' parameter present, but referrer was not ourself? |
|
329 # (e.g. someone bookmarked the GET that followed the POST submit) |
|
330 if (request.GET.get(profile.SUBMIT_MSG_PARAM_NAME) |
|
331 and (not helper.requests.isReferrerSelf(request, |
|
332 suffix=link_id))): |
|
333 # redirect to aggressively remove 'Profile saved' query parameter |
|
334 return http.HttpResponseRedirect(request.path) |
|
335 |
|
336 # referrer was us, so select which submit message to display |
|
337 # (may display no message if ?s=0 parameter is not present) |
|
338 context['notice'] = ( |
|
339 helper.requests.getSingleIndexedParamValue( |
|
340 request, profile.SUBMIT_MSG_PARAM_NAME, |
|
341 values=profile.SUBMIT_MESSAGES)) |
|
342 |
|
343 # populate form with the existing User entity |
|
344 form = EditForm(initial={'key_name': user.key().name(), |
|
345 'account': user.account.email(), 'link_id': user.link_id, |
|
346 'nick_name': user.nick_name, 'is_developer': user.is_developer}) |
|
347 else: |
|
348 if request.GET.get(profile.SUBMIT_MSG_PARAM_NAME): |
|
349 # redirect to aggressively remove 'Profile saved' query parameter |
|
350 return http.HttpResponseRedirect(request.path) |
|
351 |
|
352 context['lookup_error'] = ugettext_lazy( |
|
353 'User with that link ID not found.') |
|
354 form = EditForm(initial={'link_id': link_id}) |
|
355 else: # no link ID specified in the URL |
|
356 if request.GET.get(profile.SUBMIT_MSG_PARAM_NAME): |
|
357 # redirect to aggressively remove 'Profile saved' query parameter |
|
358 return http.HttpResponseRedirect(request.path) |
|
359 |
|
360 # no link ID specified, so start with an empty form |
|
361 form = EditForm() |
|
362 |
|
363 context.update({'form': form, |
|
364 'existing_user': user}) |
|
365 |
|
366 return helper.responses.respond(request, template, context) |
|
367 |
|
368 |
|
369 class CreateForm(helper.forms.BaseForm): |
|
370 """Django form displayed when Developer creates a User. |
|
371 |
|
372 This form is manually specified, instead of using |
|
373 model = soc.models.user.User |
|
374 in the Meta class, because the form behavior is unusual and normally |
|
375 required Properties of the User model need to sometimes be omitted. |
|
376 """ |
|
377 account = forms.EmailField( |
|
378 label=soc.models.user.User.account.verbose_name, |
|
379 help_text=soc.models.user.User.account.help_text) |
|
380 |
|
381 link_id = forms.CharField( |
|
382 label=soc.models.user.User.link_id.verbose_name, |
|
383 help_text=soc.models.user.User.link_id.help_text) |
|
384 |
|
385 nick_name = forms.CharField( |
|
386 label=soc.models.user.User.nick_name.verbose_name) |
|
387 |
|
388 is_developer = forms.BooleanField(required=False, |
|
389 label=soc.models.user.User.is_developer.verbose_name, |
|
390 help_text=soc.models.user.User.is_developer.help_text) |
|
391 |
|
392 class Meta: |
|
393 model = None |
|
394 |
|
395 def clean_link_id(self): |
|
396 link_id = self.cleaned_data.get('link_id') |
|
397 if not validate.isLinkIdFormatValid(link_id): |
|
398 raise forms.ValidationError("This link ID is in wrong format.") |
|
399 else: |
|
400 if models.user.logic.getForFields({'link_id': link_id}, |
|
401 unique=True): |
|
402 raise forms.ValidationError("This link ID is already in use.") |
|
403 return link_id |
|
404 |
|
405 def clean_account(self): |
|
406 new_email = self.cleaned_data.get('account') |
|
407 form_account = users.User(email=new_email) |
|
408 if models.user.logic.getForFields({'account': form_account}, unique=True): |
|
409 raise forms.ValidationError("This account is already in use.") |
|
410 if models.user.logic.isFormerAccount(form_account): |
|
411 raise forms.ValidationError("This account is invalid. " |
|
412 "It exists as a former account.") |
|
413 return form_account |
|
414 |
|
415 |
|
416 DEF_SITE_CREATE_USER_PROFILE_TMPL = 'soc/user/edit.html' |
|
417 |
|
418 @decorators.view |
|
419 def create(request, page_name=None, template=DEF_SITE_CREATE_USER_PROFILE_TMPL): |
|
420 """View for a Developer to create a new User Model entity. |
|
421 |
|
422 Args: |
|
423 request: the standard django request object |
|
424 page_name: the page name displayed in templates as page and header title |
|
425 template: the "sibling" template (or a search list of such templates) |
|
426 from which to construct the public.html template name (or names) |
|
427 |
|
428 Returns: |
|
429 A subclass of django.http.HttpResponse which either contains the form to |
|
430 be filled out, or a redirect to the correct view in the interface. |
|
431 """ |
|
432 |
|
433 try: |
|
434 access.checkIsDeveloper(request) |
|
435 except soc.views.out_of_band.AccessViolationResponse, alt_response: |
|
436 return alt_response.response() |
|
437 |
|
438 # create default template context for use with any templates |
|
439 context = helper.responses.getUniversalContext(request) |
|
440 context['page_name'] = page_name |
|
441 |
|
442 if request.method == 'POST': |
|
443 form = CreateForm(request.POST) |
|
444 |
|
445 if form.is_valid(): |
|
446 form_account = form.cleaned_data.get('account') |
|
447 link_id = form.cleaned_data.get('link_id') |
|
448 |
|
449 properties = { |
|
450 'account': form_account, |
|
451 'link_id': link_id, |
|
452 'nick_name': form.cleaned_data.get('nick_name'), |
|
453 'is_developer': form.cleaned_data.get('is_developer'), |
|
454 } |
|
455 |
|
456 key_fields = {'email': form_account.email()} |
|
457 user = models.user.logic.updateOrCreateFromFields(properties, |
|
458 key_fields) |
|
459 |
|
460 if not user: |
|
461 return http.HttpResponseRedirect('/') |
|
462 |
|
463 # redirect to new /site/user/profile/new_link_id?s=0 |
|
464 # (causes 'Profile saved' message to be displayed) |
|
465 return helper.responses.redirectToChangedSuffix( |
|
466 request, 'create', 'edit/' + link_id, |
|
467 params=profile.SUBMIT_PROFILE_SAVED_PARAMS) |
|
468 else: # method == 'GET': |
|
469 # no link ID specified, so start with an empty form |
|
470 form = CreateForm() |
|
471 |
|
472 context['form'] = form |
|
473 |
|
474 return helper.responses.respond(request, template, context) |
|