# HG changeset patch # User Pawel Solyga # Date 1222899562 0 # Node ID 74eb6b01c82c97741f77ad13552c187223b90238 # Parent 12f4f7d16face06d3eb0acc449c8e1cd31f49fe3 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 diff -r 12f4f7d16fac -r 74eb6b01c82c app/soc/models/group.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') diff -r 12f4f7d16fac -r 74eb6b01c82c app/soc/views/site/sponsor/__init__.py diff -r 12f4f7d16fac -r 74eb6b01c82c app/soc/views/site/sponsor/list.py --- /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" ', + ] + + +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) diff -r 12f4f7d16fac -r 74eb6b01c82c app/soc/views/site/sponsor/profile.py --- /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" ', + ] + + +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' \ + ' Create ' \ + 'a New Sponsor 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 diff -r 12f4f7d16fac -r 74eb6b01c82c app/soc/views/sponsor/__init__.py diff -r 12f4f7d16fac -r 74eb6b01c82c app/soc/views/sponsor/profile.py --- /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" ', + ] + + +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