Add basic Sponsors List, Create New Sponsor, Sponsor Public Profile views. Change all properties in Group model as required for now. Remaining TODO: write validation functions for Sponsor edit and create form fields that need additional validation (like address, phone number format).
authorPawel Solyga <Pawel.Solyga@gmail.com>
Wed, 01 Oct 2008 22:19:22 +0000
changeset 259 74eb6b01c82c
parent 258 12f4f7d16fac
child 260 8b393bef717a
Add basic Sponsors List, Create New Sponsor, Sponsor Public Profile views. Change all properties in Group model as required for now. Remaining TODO: write validation functions for Sponsor edit and create form fields that need additional validation (like address, phone number format). Patch by: Pawel Solyga Review by: to-be-reviewed
app/soc/models/group.py
app/soc/views/site/sponsor/__init__.py
app/soc/views/site/sponsor/list.py
app/soc/views/site/sponsor/profile.py
app/soc/views/sponsor/__init__.py
app/soc/views/sponsor/profile.py
--- a/app/soc/models/group.py	Wed Oct 01 22:12:26 2008 +0000
+++ b/app/soc/models/group.py	Wed Oct 01 22:19:22 2008 +0000
@@ -60,23 +60,23 @@
   founder = db.ReferenceProperty(reference_class=soc.models.user.User,
                                  required=True, collection_name="groups")  
   #: Optional field storing a home page URL of the group.
-  home_page = db.LinkProperty(
+  home_page = db.LinkProperty(required=True,
       verbose_name=ugettext_lazy('Home Page URL'))
   
   #: Optional email address used as the "public" contact mechanism for
   #: the Group (as opposed to the founder.id email address which is kept
   #: secret, revealed only to Developers).
-  email = db.EmailProperty(
+  email = db.EmailProperty(required=True,
       verbose_name=ugettext_lazy('Email'))  
   
   #: Optional field storing description of the group.
-  description = db.TextProperty(
+  description = db.TextProperty(required=True,
       verbose_name=ugettext_lazy('Description'))
       
   #: Optional field containing a group street address.
   #: Group street address can only be lower ASCII, not UTF-8 text, 
   #: because, if supplied, it is used as a shipping address.
-  street = db.StringProperty(
+  street = db.StringProperty(required=True,
       verbose_name=ugettext_lazy('Street address'))
   street.help_text = ugettext_lazy(
       'street number and name, lower ASCII characters only')
@@ -84,34 +84,34 @@
   #: Optional field containing group address city.
   #: City can only be lower ASCII, not UTF-8 text, because, if
   #: supplied, it is used as a shipping address.
-  city = db.StringProperty(
+  city = db.StringProperty(required=True,
       verbose_name=ugettext_lazy('City'))
   city.help_text = ugettext_lazy('lower ASCII characters only')
 
   #: Optional field containing group address state or province.
   #: Group state/province can only be lower ASCII, not UTF-8
   #: text, because, if supplied, it is used as a shipping address.
-  state = db.StringProperty(
+  state = db.StringProperty(required=True,
       verbose_name=ugettext_lazy('State/Province'))
   state.help_text = ugettext_lazy(
       'optional if country/territory does not have states or provinces, '
       'lower ASCII characters only')
 
   #: Optional field containing address country or territory of the group.
-  country = db.StringProperty(
+  country = db.StringProperty(required=True,
       verbose_name=ugettext_lazy('Country/Territory'),
       choices=countries.COUNTRIES_AND_TERRITORIES)
 
   #: Optional field containing address postal code of the group (ZIP code in
   #: the United States). Postal code can only be lower ASCII, not UTF-8 
   #: text, because, if supplied, it is used as a shipping address.
-  postalcode = db.StringProperty(
+  postalcode = db.StringProperty(required=True,
       verbose_name=ugettext_lazy('ZIP/Postal Code'))
   postalcode.help_text=ugettext_lazy('lower ASCII characters only')
 
   #: Optional contact phone number that will be, amongst other uses,
   #: supplied to shippers along with the shipping address; kept private.
-  phone = db.PhoneNumberProperty(
+  phone = db.PhoneNumberProperty(required=True,
       verbose_name=ugettext_lazy('Phone Number'))
   phone.help_text = ugettext_lazy(
       'include complete international calling number with country code')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/app/soc/views/site/sponsor/list.py	Wed Oct 01 22:19:22 2008 +0000
@@ -0,0 +1,61 @@
+#!/usr/bin/python2.5
+#
+# Copyright 2008 the Melange authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Developer views for listing Sponsors profiles.
+"""
+
+__authors__ = [
+  '"Pawel Solyga" <pawel.solyga@gmail.com>',
+  ]
+
+
+from soc.logic import sponsor
+from soc.views import simple
+from soc.views.helpers import list_helpers
+from soc.views.helpers import response_helpers
+
+
+DEF_SITE_SPONSOR_LIST_ALL_TMPL = 'soc/group/list/all.html'
+
+def all(request, template=DEF_SITE_SPONSOR_LIST_ALL_TMPL):
+  """Show a list of all Sponsors (limit rows per page).
+  """
+  # 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  
+  
+  offset = request.GET.get('offset')
+  limit = request.GET.get('limit')
+
+  offset, limit = list_helpers.getListParemeters(offset, limit)
+  
+  sponsors = sponsor.getSponsorsForOffsetAndLimit(offset, limit)
+  
+  list_templates = {'list_main': 'soc/list/list_main.html',
+                    'list_pagination': 'soc/list/list_pagination.html',
+                    'list_row': 'soc/group/list/group_row.html',
+                    'list_heading': 'soc/group/list/group_heading.html'}
+                      
+  context = list_helpers.setList(request, context, sponsors, 
+                                 offset, limit, list_templates)
+                                 
+  context['group_type'] = 'Sponsor'
+
+  return response_helpers.respond(request, template, context)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/app/soc/views/site/sponsor/profile.py	Wed Oct 01 22:19:22 2008 +0000
@@ -0,0 +1,186 @@
+#!/usr/bin/python2.5
+#
+# Copyright 2008 the Melange authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Developer views for editing and examining Sponsor profiles.
+"""
+
+__authors__ = [
+  '"Pawel Solyga" <pawel.solyga@gmail.com>',
+  ]
+
+
+from google.appengine.api import users
+
+from django import http
+from django import newforms as forms
+
+from soc.logic import validate
+from soc.logic import out_of_band
+from soc.logic import sponsor
+from soc.logic.site import id_user
+from soc.views import simple
+from soc.views.helpers import custom_widgets
+from soc.views.helpers import forms_helpers
+from soc.views.helpers import response_helpers
+from soc.views.helpers import request_helpers
+from soc.views.user import profile
+
+import soc.models.sponsor
+
+
+class CreateForm(forms_helpers.DbModelForm):
+  """Django form displayed when creating a Sponsor.
+  """
+  class Meta:
+    """Inner Meta class that defines some behavior for the form.
+    """
+    #: db.Model subclass for which the form will gather information
+    model = soc.models.sponsor.Sponsor
+    
+    #: list of model fields which will *not* be gathered by the form
+    exclude = ['founder', 'inheritance_line']
+  
+  # TODO(pawel.solyga): write validation functions for other fields
+  def clean_link_name(self):
+    link_name = self.cleaned_data.get('link_name')
+    if not validate.isLinkNameFormatValid(link_name):
+      raise forms.ValidationError("This link name is in wrong format.")
+    if sponsor.doesLinkNameExist(link_name):
+      raise forms.ValidationError("This link name is already in use.")
+    return link_name
+
+
+class EditForm(CreateForm):
+  """Django form displayed when editing a Sponsor.
+  """
+  link_name = forms.CharField(widget=custom_widgets.ReadOnlyInput())
+
+  def clean_link_name(self):
+    link_name = self.cleaned_data.get('link_name')
+    if not validate.isLinkNameFormatValid(link_name):
+      raise forms.ValidationError("This link name is in wrong format.")
+    return link_name
+
+
+DEF_SITE_SPONSOR_PROFILE_EDIT_TMPL = 'soc/group/profile/edit.html'
+DEF_SPONSOR_NO_LINKNAME_CHANGE_MSG = 'Sponsor link name cannot be changed.'
+DEF_CREATE_NEW_SPONSOR_MSG = ' You can create a new sponsor by visiting' \
+                          ' <a href="/site/sponsor/profile">Create ' \
+                          'a New Sponsor</a> page.'
+
+def edit(request, linkname=None, template=DEF_SITE_SPONSOR_PROFILE_EDIT_TMPL):
+  """View for a Developer to modify the properties of a Sponsor Model entity.
+
+  Args:
+    request: the standard django request object
+    linkname: the Sponsor'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
+
+  logged_in_id = users.get_current_user()
+  user = id_user.getUserFromId(logged_in_id)
+  sponsor_form = None
+  existing_sponsor = None
+
+  # try to fetch Sponsor entity corresponding to linkname if one exists    
+  try:
+    existing_sponsor = soc.logic.sponsor.getSponsorIfLinkName(linkname)
+  except out_of_band.ErrorResponse, error:
+    # show custom 404 page when link name doesn't exist in Datastore
+    error.message = error.message + DEF_CREATE_NEW_SPONSOR_MSG
+    return simple.errorResponse(request, error, template, context)
+     
+  if request.method == 'POST':
+    if existing_sponsor:
+      sponsor_form = EditForm(request.POST)
+    else:
+      sponsor_form = CreateForm(request.POST)
+
+    if sponsor_form.is_valid():
+      if linkname:
+        # Form doesn't allow to change linkname but somebody might want to 
+        # abuse that manually, so we check if form linkname is the same as 
+        # url linkname
+        if sponsor_form.cleaned_data.get('link_name') != linkname:
+          msg = DEF_SPONSOR_NO_LINKNAME_CHANGE_MSG
+          error = out_of_band.ErrorResponse(msg)
+          return simple.errorResponse(request, error, template, context)
+      
+      fields = {}      
+      
+      # Ask for all the fields and pull them out 
+      for field in sponsor_form.cleaned_data:
+        value = sponsor_form.cleaned_data.get(field)
+        fields[field] = value
+      
+      fields['founder'] = user
+      
+      form_ln = fields['link_name']
+      form_sponsor = sponsor.updateOrCreateSponsorFromLinkName(form_ln, 
+                                                               **fields)
+      
+      if not form_sponsor:
+        return http.HttpResponseRedirect('/')
+        
+      # redirect to new /site/sponsor/profile/form_link_name?s=0
+      # (causes 'Profile saved' message to be displayed)
+      return response_helpers.redirectToChangedSuffix(
+          request, None, form_ln,
+          params=profile.SUBMIT_PROFILE_SAVED_PARAMS)
+
+  else: # request.method == 'GET'
+    if existing_sponsor:
+      # 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 request_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'] = (
+          request_helpers.getSingleIndexedParamValue(
+              request, profile.SUBMIT_MSG_PARAM_NAME,
+              values=profile.SUBMIT_MESSAGES))    
+              
+      # populate form with the existing Sponsor entity
+      sponsor_form = EditForm(instance=existing_sponsor)
+    else:
+      if request.GET.get(profile.SUBMIT_MSG_PARAM_NAME):
+        # redirect to aggressively remove 'Profile saved' query parameter
+        return http.HttpResponseRedirect(request.path)
+      
+      # no Sponsor entity exists for this link name, so show a blank form
+      sponsor_form = CreateForm()
+    
+  context.update({'form': sponsor_form,
+                  'existing_group':  existing_sponsor,
+                  'group_type': 'Sponsor'})
+
+  return response_helpers.respond(request, template, context)
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/app/soc/views/sponsor/profile.py	Wed Oct 01 22:19:22 2008 +0000
@@ -0,0 +1,60 @@
+#!/usr/bin/python2.5
+#
+# Copyright 2008 the Melange authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Views for displaying public Sponsor profiles.
+"""
+
+__authors__ = [
+  '"Pawel Solyga" <pawel.solyga@gmail.com>',
+  ]
+
+
+from soc.logic import out_of_band
+from soc.logic import sponsor
+from soc.views import simple
+from soc.views.helpers import response_helpers
+from soc.views.helpers import template_helpers
+
+
+DEF_SPONSOR_PUBLIC_TMPL = 'soc/group/profile/public.html'
+
+def public(request, linkname=None, template=DEF_SPONSOR_PUBLIC_TMPL):
+  """How the "general public" sees the Sponsor profile.
+
+  Args:
+    request: the standard django request object.
+    linkname: the Sponsor's site-unique "linkname" extracted from the URL
+    template: the template path to use for rendering the template.
+
+  Returns:
+    A subclass of django.http.HttpResponse with generated template.
+  """
+  # create default template context for use with any templates
+  context = response_helpers.getUniversalContext(request)
+
+  try:
+    linkname_sponsor = sponsor.getSponsorIfLinkName(linkname)
+  except out_of_band.ErrorResponse, error:
+    # show custom 404 page when link name doesn't exist in Datastore
+    return simple.errorResponse(request, error, template, context)
+
+  linkname_sponsor.description = \
+      template_helpers.unescape(linkname_sponsor.description)
+  
+  context.update({'linkname_group': linkname_sponsor,
+                  'group_type': 'Sponsor'})
+
+  return response_helpers.respond(request, template, context)
\ No newline at end of file