Add /site/user/profile Developer view for editing arbitrary User entities.
authorTodd Larsen <tlarsen@google.com>
Fri, 19 Sep 2008 05:15:20 +0000
changeset 170 1fadf6e0348d
parent 169 a9b3d6c9d4f9
child 171 b62f1cf5e878
Add /site/user/profile Developer view for editing arbitrary User entities. Update /user/profile edit() view to fix TODO about updating the URL when a POST of the form changes the link_name of the User. Add a Create New User link to the base.html sidebar mock-up.
app/soc/templates/soc/base.html
app/soc/templates/soc/site/user/profile/edit.html
app/soc/templates/soc/site/user/profile/lookup.html
app/soc/views/site/user/profile.py
app/soc/views/user/profile.py
app/urls.py
--- a/app/soc/templates/soc/base.html	Fri Sep 19 05:12:35 2008 +0000
+++ b/app/soc/templates/soc/base.html	Fri Sep 19 05:15:20 2008 +0000
@@ -89,6 +89,9 @@
                        <li>
             <a href="/site/user/lookup">Look Up User</a>
                         </li>
+                       <li>
+            <a href="/site/user/profile">Create New User</a>
+                        </li>
                     </ul>	          
 	      </li>
 	  </ul>
--- a/app/soc/templates/soc/site/user/profile/edit.html	Fri Sep 19 05:12:35 2008 +0000
+++ b/app/soc/templates/soc/site/user/profile/edit.html	Fri Sep 19 05:15:20 2008 +0000
@@ -14,16 +14,16 @@
 {% endcomment %}
 {% load forms_helpers %}
 {% block page_title %}
- {% if linkname_user %}
+ {% if existing_user %}
   Developer: Modify Existing User Profile
  {% else %}
   Developer: Create New User Profile
  {% endif %}
 {% endblock %}
 {% block header_title %}
- {% if linkname_user %}
-  Modify Existing User Profile for {{ linkname_user.nick_name }}
-   <a href="mailto:{{ linkname_user.id }} ">&lt;{{ linkname_user.id }}&gt;</a>
+ {% if existing_user %}
+  Modify Existing User Profile for {{ existing_user.nick_name }}
+   <a href="mailto:{{ existing_user.id }} ">&lt;{{ existing_user.id }}&gt;</a>
  {% else %}
   Create a New User Profile
  {% endif %}
@@ -38,19 +38,20 @@
 <form method="POST">
  <table>
   {% field_as_table_row form.id %}
+{% if lookup_error %}
+<tr>
+ <td>&nbsp;</td>
+ <td colspan="3" class="formfielderror">
+  {{ lookup_error }}
+ </td>
+</tr>
+{% endif %}
   {% field_as_table_row form.link_name %}
+  {% field_as_table_row form.nick_name %}
+  {% field_as_table_row form.is_developer %}
   <tr>
-   <td> 
-    <input type="submit" style="font-weight: bold" name="lookup" value="Look up User"/></span>
-   </td>
-   <td>&nbsp;</td>
-   <td>&nbsp;</td>
-   <td>
-    {% if lookup_message %}<b><i>{{ lookup_message }}</i></b>{% endif %}
-   </td>
-  <tr><td colspan="4">&nbsp;</td></tr>
+   <td colspan="4">&nbsp;</td>
   </tr>
-  {% field_as_table_row form.nick_name %}
   <tr>
    <td> 
     <input type="submit" style="font-weight: bold" name="save" value="Save Changes"/></span>
@@ -58,9 +59,16 @@
    <td>
     <input type="button" onclick="location.href='/'" value="Cancel"/>
    </td>
+   <td>&nbsp;</td>
+{% if submit_error %}
+   <td class="formfielderror">
+    {{ submit_error }}
+   </td>
+{% else %}
    <td>
-    {% if submit_message %}<b><i>{{ submit_message }}</i></b>{% endif %}
+    {% if submit_message %}<b><i>{{ submit_message }}</i></b>{% else %}&nbsp;{% endif %}
    </td>
+{% endif %}
   </tr>
  </table>
 </form>
--- a/app/soc/templates/soc/site/user/profile/lookup.html	Fri Sep 19 05:12:35 2008 +0000
+++ b/app/soc/templates/soc/site/user/profile/lookup.html	Fri Sep 19 05:15:20 2008 +0000
@@ -28,19 +28,35 @@
 </p>
 <form method="POST">
  <table>
+{% if email_error %}
+<tr>
+ <td>&nbsp;</td>
+ <td colspan="3" class="formfielderror">
+  {{ email_error }}
+ </td>
+</tr>
+{% endif %}
   {% field_as_table_row form.id %}
+{% if linkname_error %}
+<tr>
+ <td>&nbsp;</td>
+ <td colspan="3" class="formfielderror">
+  {{ linkname_error }}
+ </td>
+</tr>
+{% endif %}
   {% field_as_table_row form.link_name %}
 {% if found_user %}
  <tr>
   <td class="formfieldlabel">Nick name</td>
+  <td>{{ found_user.nick_name }}</td>
   <td class="formfieldrequired">&nbsp;</td>
-  <td>{{ found_user.nick_name }}</td>
   <td class="formfieldhelptext">&nbsp;</td>
  </tr>
  <tr>
   <td class="formfieldlabel">Is Developer</td>
+  <td>{{ found_user.is_developer }}</td>
   <td class="formfieldrequired">&nbsp;</td>
-  <td>{{ found_user.is_developer }}</td>
   <td class="formfieldhelptext">&nbsp;</td>
  </tr>
 {% endif %}
@@ -51,16 +67,10 @@
    <td> 
     <input type="submit" style="font-weight: bold" name="lookup" value="Look up User"/></span>
    </td>
-   <td>&nbsp;</td>
-{% if lookup_error %}
-   <td colspan="2" class="formfielderror">
-    {{ lookup_error }}
+   <td colspan="2">&nbsp;</td>
+   <td>
+    {% if lookup_message %}<b><i>{{ lookup_message }}</i></b>{% else %}&nbsp;{% endif %}
    </td>
-{% else %}
-   <td colspan="2">
-    {% if lookup_message %}<b><i>{{ lookup_message }}</i></b>{% else %}%nbsp;{% endif %}
-   </td>
-{% endif %}
   </tr>
 {% if edit_link %}
   <tr>
--- a/app/soc/views/site/user/profile.py	Fri Sep 19 05:12:35 2008 +0000
+++ b/app/soc/views/site/user/profile.py	Fri Sep 19 05:15:20 2008 +0000
@@ -21,13 +21,12 @@
   '"Todd Larsen" <tlarsen@google.com>',
   ]
 
-import re
-import logging
 
 from google.appengine.api import users
+
 from django import http
-from django import shortcuts
 from django import newforms as forms
+from django.utils.translation import ugettext_lazy
 
 from soc.logic import out_of_band
 from soc.logic.site import id_user
@@ -35,15 +34,29 @@
 from soc.views.helpers import forms_helpers
 from soc.views.helpers import response_helpers
 from soc.views.helpers import template_helpers
+from soc.views.user import profile
 
 import soc.models.user
 
 
 class LookupForm(forms_helpers.DbModelForm):
   """Django form displayed for a Developer to look up a User.
+  
+  This form is manually specified, instead of using
+    model = soc.models.user.User
+  in the Meta class, because the form behavior is unusual and normally
+  required Properties of the User model need to sometimes be omitted.
+  
+  Also, this form only permits entry and editing  of some of the User entity
+  Properties, not all of them.
   """
-  id = forms.EmailField(required=False)
-  link_name = forms.CharField(required=False)
+  id = forms.EmailField(required=False,
+      label=soc.models.user.User.id.verbose_name,
+      help_text=soc.models.user.User.id.help_text)
+
+  link_name = forms.CharField(required=False,
+      label=soc.models.user.User.link_name.verbose_name,
+      help_text=soc.models.user.User.link_name.help_text)
 
   class Meta:
     model = None
@@ -90,25 +103,15 @@
   # create default template context for use with any templates
   context = response_helpers.getUniversalContext(request)
 
-  logged_in_id = users.get_current_user()
-
-  alt_response = simple.getAltResponseIfNotDeveloper(request, context, 
-                                                        id = logged_in_id)
+  alt_response = simple.getAltResponseIfNotDeveloper(request,
+                                                     context=context)
   if alt_response:
-    # not a developer
-    return alt_response
-  
-  alt_response = simple.getAltResponseIfNotLoggedIn(request, context, 
-                                                        id = logged_in_id)
-  if alt_response:
-    # not logged in
     return alt_response
 
   user = None  # assume that no User entity will be found
   form = None  # assume blank form needs to be displayed
-  lookup_message = 'Enter information to look up a User.'
-  lookup_error = None  # assume no look-up errors
-  edit_link = None  # assume no User entity found to be edited
+  lookup_message = ugettext_lazy('Enter information to look up a User.')
+  email_error = None  # assume no email look-up errors
 
   if request.method == 'POST':
     form = LookupForm(request.POST)
@@ -121,9 +124,9 @@
         user = id_user.getUserFromId(form_id)
 
         if user:
-          lookup_message = 'User found by email.'
+          lookup_message = ugettext_lazy('User found by email.')
         else:
-          lookup_error = 'User with that email not found.'
+          email_error = ugettext_lazy('User with that email not found.')
 
       if not user:
         # user not found yet, so see if link name was provided
@@ -134,15 +137,11 @@
           user = id_user.getUserFromLinkName(linkname)
         
           if user:
-            lookup_message = 'User found by link name.'
-            lookup_error = None  # clear previous error, now that User was found
+            lookup_message = ugettext_lazy('User found by link name.')
+            email_error = None  # clear previous error, since User was found
           else:
-            if form_id:
-              # email was provided, so look up failure is due to both            
-              lookup_error = 'User with that email or link name not found.'            
-            else:
-              # email was not provided, so look up failure is due to link name            
-              lookup_error = 'User with that link name not found.'            
+            context['linkname_error'] = ugettext_lazy(
+                'User with that link name not found.')            
     # else: form was not valid
   # else:  # method == 'GET'
 
@@ -154,17 +153,149 @@
 
     if request.path.endswith('lookup'):
       # convert /lookup path into /profile/link_name path
-      edit_link = '%sprofile/%s' % (request.path[:-len('lookup')],
-                                    user.link_name) 
+      context['edit_link'] = response_helpers.replaceSuffix(
+          request.path, 'lookup', 'profile/%s' % user.link_name)
     # else: URL is not one that was expected, so do not display edit link
   elif not form:
     # no pre-populated form was constructed, so show the empty look-up form
     form = LookupForm()
 
   context.update({'form': form,
-                  'edit_link': edit_link,
                   'found_user': user,
-                  'lookup_error': lookup_error,
+                  'email_error': email_error,
                   'lookup_message': lookup_message})
 
   return response_helpers.respond(request, template, context)
+
+
+class EditForm(forms_helpers.DbModelForm):
+  """Django form displayed when Developer creates or edits a User.
+  
+  This form is manually specified, instead of using
+    model = soc.models.user.User
+  in the Meta class, because the form behavior is unusual and normally
+  required Properties of the User model need to sometimes be omitted.
+  """
+  id = forms.EmailField(
+      label=soc.models.user.User.id.verbose_name,
+      help_text=soc.models.user.User.id.help_text)
+
+  link_name = forms.CharField(
+      label=soc.models.user.User.link_name.verbose_name,
+      help_text=soc.models.user.User.link_name.help_text)
+
+  nick_name = forms.CharField(
+      label=soc.models.user.User.nick_name.verbose_name)
+
+  is_developer = forms.BooleanField(required=False,
+      label=soc.models.user.User.is_developer.verbose_name,
+      help_text=soc.models.user.User.is_developer.help_text)
+
+  class Meta:
+    model = None
+ 
+  def clean_link_name(self):
+    link_name = self.cleaned_data.get('link_name')
+    if not id_user.isLinkNameFormatValid(link_name):
+      raise forms.ValidationError("This link name is in wrong format.")
+    else:
+      if not id_user.isLinkNameAvailableForId(
+          link_name, id=self.cleaned_data.get('id')):
+        raise forms.ValidationError("This link name is already in use.")
+    return link_name
+
+  def clean_id(self):
+    try:
+      return users.User(email=self.cleaned_data.get('id'))
+    except users.UserNotFoundError:
+      raise forms.ValidationError('Account not found.')
+    
+
+DEF_SITE_USER_PROFILE_EDIT_TMPL = 'soc/site/user/profile/edit.html'
+
+def edit(request, linkname=None, template=DEF_SITE_USER_PROFILE_EDIT_TMPL):
+  """View for a Developer to modify the properties of a User Model entity.
+
+  Args:
+    request: the standard django request object
+    linkname: the User's site-unique "linkname" extracted from the URL
+    template: the "sibling" template (or a search list of such templates)
+      from which to construct the public.html template name (or names)
+
+  Returns:
+    A subclass of django.http.HttpResponse which either contains the form to
+    be filled out, or a redirect to the correct view in the interface.
+  """
+  # create default template context for use with any templates
+  context = response_helpers.getUniversalContext(request)
+
+  alt_response = simple.getAltResponseIfNotDeveloper(request,
+                                                     context=context)
+  if alt_response:
+    return alt_response
+
+  user = None  # assume that no User entity will be found
+
+  if request.method == 'POST':
+    form = EditForm(request.POST)
+
+    if form.is_valid():
+      form_id = form.cleaned_data.get('id')
+      new_linkname = form.cleaned_data.get('link_name')
+      nickname = form.cleaned_data.get('nick_name')
+      is_developer = form.cleaned_data.get('is_developer')
+      
+      user = id_user.updateOrCreateUserFromId(
+        form_id, link_name=new_linkname, nick_name=nickname,
+        is_developer=is_developer)
+
+      # redirect to new /site/user/profile/new_linkname&s=0
+      # (causes 'Profile saved' message to be displayed)
+      return response_helpers.redirectToChangedSuffix(
+          request, linkname, new_linkname,
+          params=profile.SUBMIT_PROFILE_SAVED_PARAMS)
+  else: # method == 'GET':
+    # try to fetch User entity corresponding to link name if one exists
+    if linkname:
+      user = id_user.getUserFromLinkName(linkname)
+
+      if user:
+        # is 'Profile saved' parameter present, but referrer was not ourself?
+        # (e.g. someone bookmarked the GET that followed the POST submit) 
+        if (request.GET.get(profile.SUBMIT_MSG_PARAM_NAME)
+            and (not response_helpers.isReferrerSelf(request,
+                                                    suffix=linkname))):
+          # redirect to aggressively remove 'Profile saved' query parameter
+          return http.HttpResponseRedirect(request.path)
+    
+        # referrer was us, so select which submit message to display
+        # (may display no message if ?s=0 parameter is not present)
+        context['submit_message'] = (
+            template_helpers.getSingleIndexedParamValue(
+                request, profile.SUBMIT_MSG_PARAM_NAME,
+                values=profile.SUBMIT_MESSAGES))
+
+        # populate form with the existing User entity
+        form = EditForm(initial={
+            'id': user.id, 'link_name': user.link_name,
+            'nick_name': user.nick_name, 'is_developer': user.is_developer})       
+      else:
+        if request.GET.get(profile.SUBMIT_MSG_PARAM_NAME):
+          # redirect to aggressively remove 'Profile saved' query parameter
+          return http.HttpResponseRedirect(request.path)
+          
+        context['lookup_error'] = ugettext_lazy(
+            'User with that link name not found.')
+        form = EditForm(initial={'link_name': linkname})
+    else:  # no link name specified in the URL
+      if request.GET.get(profile.SUBMIT_MSG_PARAM_NAME):
+        # redirect to aggressively remove 'Profile saved' query parameter
+        return http.HttpResponseRedirect(request.path)
+
+      # no link name specified, so start with an empty form
+      form = EditForm()
+
+  context.update({'form': form,
+                  'existing_user': user})
+
+  return response_helpers.respond(request, template, context)
--- a/app/soc/views/user/profile.py	Fri Sep 19 05:12:35 2008 +0000
+++ b/app/soc/views/user/profile.py	Fri Sep 19 05:15:20 2008 +0000
@@ -21,13 +21,12 @@
   '"Pawel Solyga" <pawel.solyga@gmail.com>',
   ]
 
-import re
-import logging
 
 from google.appengine.api import users
 from django import http
 from django import shortcuts
 from django import newforms as forms
+from django.utils.translation import ugettext_lazy
 
 from soc.logic import out_of_band
 from soc.logic.site import id_user
@@ -62,6 +61,18 @@
 
 DEF_USER_PROFILE_EDIT_TMPL = 'soc/user/profile/edit.html'
 
+SUBMIT_MSG_PARAM_NAME = 's'
+
+SUBMIT_MESSAGES = (
+  ugettext_lazy('Profile saved.'),
+)
+
+SUBMIT_MSG_PROFILE_SAVED = 0
+
+SUBMIT_PROFILE_SAVED_PARAMS = {
+  SUBMIT_MSG_PARAM_NAME: SUBMIT_MSG_PROFILE_SAVED,
+}
+
 def edit(request, linkname=None, template=DEF_USER_PROFILE_EDIT_TMPL):
   """View for a User to modify the properties of a User Model entity.
 
@@ -96,10 +107,10 @@
   try:
     linkname_user = id_user.getUserIfLinkName(linkname)
   except out_of_band.ErrorResponse, error:
-    # show custom 404 page when linkname doesn't exist in Datastore
+    # show custom 404 page when link name doesn't exist in Datastore
     return simple.errorResponse(request, error, template, context)
   
-  # linkname_user will be None here if linkname was already None...
+  # linkname_user will be None here if link name was already None...
   if linkname_user and (linkname_user.id != id):
     # linkname_user exists but is not the currently logged in Google Account,
     # so show public view for that (other) User entity
@@ -109,24 +120,42 @@
     form = UserForm(request.POST)
 
     if form.is_valid():
-      linkname = form.cleaned_data.get('link_name')
+      new_linkname = form.cleaned_data.get('link_name')
       nickname = form.cleaned_data.get("nick_name")
 
       user = id_user.updateOrCreateUserFromId(
-          id, link_name=linkname, nick_name=nickname)
+          id, link_name=new_linkname, nick_name=nickname)
 
-      # TODO(tlarsen):
-      # if old_linkname:  redirect to new /user/profile/new_linkname
-      #   (how to preserve displaying the "Profile saved" message?)
-      context.update({'submit_message': 'Profile saved.'})
+      # redirect to new /user/profile/new_linkname&s=0
+      # (causes 'Profile saved' message to be displayed)
+      return response_helpers.redirectToChangedSuffix(
+          request, linkname, new_linkname, params=SUBMIT_PROFILE_SAVED_PARAMS)
   else: # request.method == 'GET'
     # try to fetch User entity corresponding to Google Account if one exists    
     user = id_user.getUserFromId(id)
 
     if user:
+      # is 'Profile saved' parameter present, but referrer was not ourself?
+      # (e.g. someone bookmarked the GET that followed the POST submit) 
+      if (request.GET.get(SUBMIT_MSG_PARAM_NAME)
+          and (not response_helpers.isReferrerSelf(request,
+                                                   suffix=linkname))):
+        # redirect to aggressively remove 'Profile saved' query parameter
+        return http.HttpResponseRedirect(request.path)
+    
+      # referrer was us, so select which submit message to display
+      # (may display no message if ?s=0 parameter is not present)
+      context['submit_message'] = (
+          template_helpers.getSingleIndexedParamValue(
+              request, SUBMIT_MSG_PARAM_NAME, values=SUBMIT_MESSAGES))
+
       # populate form with the existing User entity
       form = UserForm(instance=user)
     else:
+      if request.GET.get(SUBMIT_MSG_PARAM_NAME):
+        # redirect to aggressively remove 'Profile saved' query parameter
+        return http.HttpResponseRedirect(request.path)
+
       # no User entity exists for this Google Account, so show a blank form
       form = UserForm()
 
--- a/app/urls.py	Fri Sep 19 05:12:35 2008 +0000
+++ b/app/urls.py	Fri Sep 19 05:15:20 2008 +0000
@@ -37,13 +37,9 @@
     #  'soc.views.user.roles.dashboard'),
 
     (r'^site/user/lookup$', 'soc.views.site.user.profile.lookup'),
+    (r'^site/user/profile$', 'soc.views.site.user.profile.edit'),
     (r'^site/user/profile/(?P<linkname>[_0-9a-z]+)$',
      'soc.views.site.user.profile.edit'),
-     
-    # TODO(tlarsen): uncomment these when the view functions are committed
-    # (r'^site/user/profile$', 'soc.views.site.user.profile.create'),
-    # (r'^site/user/profile/(?P<linkname>[_0-9a-z]+)$',
-    #  'soc.views.site.user.profile.edit'),
 
     (r'^user/profile$', 'soc.views.user.profile.edit'),
     (r'^user/profile/(?P<linkname>[_0-9a-z]+)$',