app/soc/views/site/user/profile.py
changeset 517 661ab830e921
parent 516 ec1dcd70b97e
child 518 d9d31d316a74
equal deleted inserted replaced
516:ec1dcd70b97e 517:661ab830e921
     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)