# HG changeset patch # User Lennard de Rijk # Date 1232712048 0 # Node ID 39badbfb80be2856ebbd17e89f6ea5285355d694 # Parent 3f9072bab1d41d843f820b6affcdd7e19a92c854 Added the new way to process invites for club_admin only. This patch contains the separability needed to ensure that we can successfully do the access checks we need. Host invites can for the time being only be completed by developers due to the reorganization. Patch by: Lennard de Rijk Reviewed by: to-be-reviewed diff -r 3f9072bab1d4 -r 39badbfb80be app/soc/templates/soc/request/process_invite.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/soc/templates/soc/request/process_invite.html Fri Jan 23 12:00:48 2009 +0000 @@ -0,0 +1,37 @@ +{% extends "soc/base.html" %} +{% comment %} +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. +{% endcomment %} +{% load forms_helpers %} + +{% block header_title %} +{{ page_name }} for {{ entity.requester.link_id }} +{% endblock %} + +{% block body %} +

+ +You have been invited to become a {{ entity.role_verbose }} for {{entity.scope.name }}. +Please select your response. + +

+ + + + + + + + + +{% endblock %} diff -r 3f9072bab1d4 -r 39badbfb80be app/soc/views/helper/redirects.py --- a/app/soc/views/helper/redirects.py Fri Jan 23 11:40:35 2009 +0000 +++ b/app/soc/views/helper/redirects.py Fri Jan 23 12:00:48 2009 +0000 @@ -91,9 +91,15 @@ """Returns the redirect for accepting an invite. """ - return '/%s/create/%s/%s' % ( + return '/%s/accept_invite/%s/%s' % ( entity.role, entity.scope_path, entity.link_id) +def inviteProcessRedirect(entity, _): + """Returns the redirect for processing an invite + """ + + return '/request/process_invite/%s/%s/%s' % ( + entity.scope_path, entity.role, entity.link_id) def getApplicantRedirect(entity, params): """Returns the redirect for processing accepted Applications. diff -r 3f9072bab1d4 -r 39badbfb80be app/soc/views/models/club_admin.py --- a/app/soc/views/models/club_admin.py Fri Jan 23 11:40:35 2009 +0000 +++ b/app/soc/views/models/club_admin.py Fri Jan 23 12:00:48 2009 +0000 @@ -19,22 +19,33 @@ __authors__ = [ '"Sverre Rabbelier" ', + '"Lennard de Rijk" ' ] +from django import http from django import forms from soc.logic import cleaning from soc.logic import dicts +from soc.logic.models import club as club_logic +from soc.logic.models import user as user_logic +from soc.logic.models import request as request_logic +from soc.views.helper import access +from soc.views.helper import decorators +from soc.views.helper import dynaform from soc.views.helper import redirects +from soc.views.helper import responses +from soc.views.helper import widgets from soc.views.models import base from soc.views.models import club as club_view +from soc.views.models import request as request_view import soc.logic.models.club_admin class View(base.View): - """View methods for the Program model. + """View methods for the Club Admin model. """ def __init__(self, params=None): @@ -45,27 +56,258 @@ params: a dict with params for this View """ + rights = {} + rights['create'] = [access.checkIsHost] + rights['edit'] = [access.checkIsMyActiveRole(soc.logic.models.club_admin)] + rights['delete'] = [access.checkIsHost] + rights['invite'] = [access.checkIsClubAdminForClub] + rights['accept_invite'] = [access.checkCanCreateFromRequest('club_admin')] + new_params = {} new_params['logic'] = soc.logic.models.club_admin.logic + new_params['rights'] = rights new_params['scope_view'] = club_view new_params['scope_redirect'] = redirects.getCreateRedirect new_params['name'] = "Club Admin" - new_params['extra_dynaexclude'] = ['user', 'org'] + new_params['extra_dynaexclude'] = ['user', 'club', 'state'] + + new_params['create_extra_dynafields'] = { + 'scope_path': forms.CharField(widget=forms.HiddenInput, + required=True), + 'clean_link_id' : cleaning.clean_existing_user('link_id'), + 'clean_home_page' : cleaning.clean_url('home_page'), + 'clean_blog' : cleaning.clean_url('blog'), + 'clean_photo_url' : cleaning.clean_url('photo_url')} + patterns = [(r'^%(url_name)s/(?Pinvite)/%(lnp)s$', + 'soc.views.models.%(module_name)s.invite', + 'Create invite for %(name_plural)s'), + (r'^%(url_name)s/(?Paccept_invite)/%(scope)s/%(lnp)s$', + 'soc.views.models.%(module_name)s.acceptInvite', + 'Accept invite for %(name_plural)s')] + + new_params['extra_django_patterns'] = patterns + params = dicts.merge(params, new_params) super(View, self).__init__(params=params) + # create and store the special form for invited users + updated_fields = { + 'link_id': forms.CharField(widget=widgets.ReadOnlyInput(), + required=False)} + + invited_create_form = dynaform.extendDynaForm( + dynaform = self._params['create_form'], + dynafields = updated_fields) + + params['invited_create_form'] = invited_create_form + + + def _editPost(self, request, entity, fields): + """See base.View._editPost(). + """ + + fields['user'] = fields['link_id'] + fields['link_id'] = fields['user'].link_id + + club = club_logic.logic.getFromKeyName(fields['scope_path']) + fields['club'] = club + + super(View, self)._editPost(request, entity, fields) + + + @decorators.merge_params + @decorators.check_access + def acceptInvite(self, request, access_type, + page_name=None, params=None, **kwargs): + """Creates the page process an invite into a Club Admin. + + Args: + request: the standard Django HTTP request object + access_type : the name of the access type which should be checked + context: dictionary containing the context for this view + params: a dict with params for this View + kwargs: the Key Fields for the specified entity + """ + + # get the context for this webpage + context = responses.getUniversalContext(request) + context['page_name'] = page_name + + if request.method == 'POST': + return self.acceptInvitePost(request, context, params, **kwargs) + else: + # request.method == 'GET' + return self.acceptInviteGet(request, context, params, **kwargs) + + def acceptInviteGet(self, request, context, params, **kwargs): + """Handles the GET request concerning the creation of a Club Admin via an + invite. + + Args: + request: the standard Django HTTP request object + context: dictionary containing the context for this view + params: a dict with params for this View + kwargs: the Key Fields for the specified entity + """ + + # create the form using the scope_path and link_id from kwargs as initial value + fields = {'link_id' : kwargs['link_id'], + 'scope_path' : kwargs['scope_path']} + form = params['invited_create_form'](initial=fields) + + # construct the appropriate response + return super(View, self)._constructResponse(request, entity=None, + context=context, form=form, params=params) + + def acceptInvitePost(self, request, context, params, **kwargs): + """Handles the POST request concerning the creation of a Club Admin via an + invite. + + Args: + request: the standard Django HTTP request object + context: dictionary containing the context for this view + params: a dict with params for this View + kwargs: the Key Fields for the specified entity + """ + + # populate the form using the POST data + form = params['invited_create_form'](request.POST) + + if not form.is_valid(): + # return the invalid form response + return self._constructResponse(request, entity=None, context=context, + form=form, params=params) + + # collect the cleaned data from the valid form + key_name, fields = soc.views.helper.forms.collectCleanedFields(form) + + # fill in the appropriate fields that were missing in the form + fields['user'] = fields['link_id'] + fields['link_id'] = fields['user'].link_id + + club = club_logic.logic.getFromKeyName(fields['scope_path']) + fields['club'] = club + fields['scope'] = club + + # make sure that this role becomes active once more in case this user + # has been reinvited + fields ['state'] = 'active' + + # get the key_name for the new entity + key_fields = self._logic.getKeyFieldsFromDict(fields) + key_name = self._logic.getKeyNameForFields(key_fields) + + # create new Club Admin entity + entity = self._logic.updateOrCreateFromKeyName(fields, key_name) + + # redirect to the roles overview page + return http.HttpResponseRedirect('/user/roles') + + + @decorators.merge_params + @decorators.check_access + def invite(self, request, access_type, + page_name=None, params=None, **kwargs): + """Creates the page upon which a Club Admin can invite another Club Admin. + + Args: + request: the standard Django HTTP request object + access_type : the name of the access type which should be checked + context: dictionary containing the context for this view + params: a dict with params for this View + kwargs: the Key Fields for the specified entity + """ + + # get the context for this webpage + context = responses.getUniversalContext(request) + context['page_name'] = page_name + + if request.method == 'POST': + return self.invitePost(request, context, params, **kwargs) + else: + # request.method == 'GET' + return self.inviteGet(request, context, params, **kwargs) + + def inviteGet(self, request, context, params, **kwargs): + """Handles the GET request concerning the view that creates an invite + for becoming a Club Admin. + + Args: + request: the standard Django HTTP request object + page_name: the page name displayed in templates as page and header title + params: a dict with params for this View + kwargs: the Key Fields for the specified entity + """ + + # set the role to the right name + fields = {'role' : '%(module_name)s' %(params)} + + # get the request view parameters and initialize the create form + request_params = request_view.view.getParams() + form = request_params['create_form'](initial=fields) + + # construct the appropriate response + return super(View, self)._constructResponse(request, entity=None, + context=context, form=form, params=params) + + def invitePost(self, request, context, params, **kwargs): + """Handles the POST request concerning the view that creates an invite + for becoming a Club Admin. + + Args: + request: the standard Django HTTP request object + page_name: the page name displayed in templates as page and header title + params: a dict with params for this View + kwargs: the Key Fields for the specified entity + """ + + # get the request view parameters and populate the form using POST data + request_params = request_view.view.getParams() + form = request_params['create_form'](request.POST) + + if not form.is_valid(): + # return the invalid form response + return self._constructResponse(request, entity=None, context=context, + form=form, params=params) + + # collect the cleaned data from the valid form + key_name, form_fields = soc.views.helper.forms.collectCleanedFields(form) + + # get the club entity for which this request is for from link_id in kwargs + club = club_logic.logic.getForFields({'link_id' : kwargs['link_id']}, unique=True) + + # create the fields for the new request entity + request_fields = {'link_id' : form_fields['link_id'].link_id, + 'scope' : club, + 'scope_path' : club.link_id, + 'role' : params['module_name'], + 'role_verbose' : params['name'], + 'group_accepted' : True, + 'completed' : False} + + # extract the key_name for the new request entity + key_fields = request_logic.logic.getKeyFieldsFromDict(request_fields) + key_name = request_logic.logic.getKeyNameForFields(key_fields) + + # create the request entity + entity = request_logic.logic.updateOrCreateFromKeyName(request_fields, key_name) + + # TODO(ljvderijk) redirect to a more useful place like the club homepage + return http.HttpResponseRedirect('/') view = View() +acceptInvite = view.acceptInvite create = view.create delete = view.delete edit = view.edit +invite = view.invite list = view.list public = view.public export = view.export diff -r 3f9072bab1d4 -r 39badbfb80be app/soc/views/models/host.py --- a/app/soc/views/models/host.py Fri Jan 23 11:40:35 2009 +0000 +++ b/app/soc/views/models/host.py Fri Jan 23 12:00:48 2009 +0000 @@ -78,6 +78,9 @@ pass +# TODO(ljvderijk) rewrite the Host View module to comply with the new request system +# so that non-developers can create Hosts + class View(role.View): """View methods for the Host model. """ @@ -91,8 +94,9 @@ """ rights = {} - rights['create'] = [access.checkIsHost] - rights['edit'] = [access.checkIsHost] + rights['create'] = [access.checkIsDeveloper] + # TODO(ljvderijk) write the edit check + #rights['edit'] = [access.checkIsMyHost] rights['list'] = [access.checkIsHost] new_params = {} diff -r 3f9072bab1d4 -r 39badbfb80be app/soc/views/models/request.py --- a/app/soc/views/models/request.py Fri Jan 23 11:40:35 2009 +0000 +++ b/app/soc/views/models/request.py Fri Jan 23 12:00:48 2009 +0000 @@ -27,8 +27,11 @@ from google.appengine.api import users from django import forms +from django import http +from django.core import urlresolvers from django.utils.translation import ugettext_lazy +from soc.logic import cleaning from soc.logic import dicts from soc.logic.models import sponsor as sponsor_logic from soc.logic.models import user as user_logic @@ -37,6 +40,8 @@ from soc.views.helper import access from soc.views.helper import decorators from soc.views.helper import redirects +from soc.views.helper import responses +from soc.views.helper import widgets from soc.views.models import base import soc.models.request @@ -45,53 +50,10 @@ import soc.views.helper import soc.views.helper.lists import soc.views.helper.responses -import soc.views.helper.widgets - - -class CreateForm(helper.forms.BaseForm): - """Django form displayed when Developer creates a Request. - """ - - class Meta: - """Inner Meta class that defines some behavior for the form. - """ - model = soc.models.request.Request - - #: list of model fields which will *not* be gathered by the form - exclude = ['scope', 'scope_path', 'link_id', 'role', 'declined'] - - role = forms.CharField(widget=helper.widgets.ReadOnlyInput()) - - user = forms.CharField( - label=soc.models.request.Request.link_id.verbose_name, - help_text=soc.models.request.Request.link_id.help_text, - widget=helper.widgets.ReadOnlyInput()) - - group = forms.CharField( - label=soc.models.request.Request.scope.verbose_name, - help_text=soc.models.request.Request.scope.help_text, - widget=helper.widgets.ReadOnlyInput()) - - def clean_user(self): - self.cleaned_data['requester'] = user_logic.logic.getForFields( - {'link_id': self.cleaned_data['user']}, unique=True) - return self.cleaned_data['user'] - - def clean_group(self): - self.cleaned_data['to'] = sponsor_logic.logic.getFromFields( - link_id=self.cleaned_data['group']) - return self.cleaned_data['group'] - - -class EditForm(CreateForm): - """Django form displayed when Developer edits a Request. - """ - - pass class View(base.View): - """View methods for the Docs model. + """View methods for the Request model. """ def __init__(self, params=None): @@ -104,7 +66,11 @@ rights = {} rights['listSelf'] = [access.checkAgreesToSiteToS] - rights['create'] = [access.checkCanInvite] + rights['create'] = [access.allow] # TODO(ljvderijk) Set to deny once host has been converted + rights['edit'] = [access.checkIsDeveloper] + rights['process_invite'] = [access.checkIsMyUncompletedRequest] + rights['list'] = [access.checkIsDeveloper] + rights['delete'] = [access.checkIsDeveloper] new_params = {} new_params['rights'] = rights @@ -112,17 +78,87 @@ new_params['name'] = "Request" - new_params['edit_form'] = EditForm - new_params['create_form'] = CreateForm - new_params['sidebar_defaults'] = [('/%s/list', 'List %(name_plural)s', 'list')] new_params['save_message'] = [ugettext_lazy('Request saved.')] + + new_params['extra_dynaexclude'] = ['group_accepted', 'user_accepted', + 'role_verbose', 'completed'] + + # TODO(ljvderijk) add clean field that checks to see if the user already has + # the role that's been entered in the create form fields + new_params['create_extra_dynafields'] = { + 'role' : forms.CharField(widget=widgets.ReadOnlyInput(), + required=True), + 'clean_link_id': cleaning.clean_existing_user('link_id') + } + + patterns = [(r'^%(url_name)s/(?Pinvite)/%(lnp)s$', + 'soc.views.models.%(module_name)s.invite', + 'Create invite for %(name_plural)s'), + (r'^%(url_name)s/(?Pprocess_invite)/%(key_fields)s$', + 'soc.views.models.%(module_name)s.processInvite', + 'Process Invite to for a Role')] + + new_params['extra_django_patterns'] = patterns + + new_params['invite_processing_template'] = 'soc/request/process_invite.html' params = dicts.merge(params, new_params) super(View, self).__init__(params=params) + + @decorators.merge_params + @decorators.check_access + def processInvite(self, request, access_type, + page_name=None, params=None, **kwargs): + """Creates the page upon which an invite can be processed. + + Args: + request: the standard Django HTTP request object + access_type : the name of the access type which should be checked + context: dictionary containing the context for this view + params: a dict with params for this View + kwargs: the Key Fields for the specified entity + """ + + # get the context for this webpage + context = responses.getUniversalContext(request) + context['page_name'] = page_name + + request_logic = params['logic'] + + # get the request entity using the information from kwargs + fields = {'link_id' : kwargs['link_id'], + 'scope_path' : kwargs['scope_path'], + 'role' : kwargs['role'], + 'group_accepted' : True, + 'user_accepted' : False, + 'completed' : False} + request_entity = request_logic.getForFields(fields, unique=True) + + get_dict = request.GET + + if 'status' in get_dict.keys(): + if get_dict['status'] == 'rejected': + # this invite has been rejected mark accepted as False and mark completed as True + request_logic.updateModelProperties(request_entity, { + 'user_accepted' : False, 'completed' : True}) + + # redirect to user role overview + return http.HttpResponseRedirect('/user/roles') + + # put the entity in the context + context['entity'] = request_entity + context['module_name'] = params['module_name'] + + #display the invite processing page using the appropriate template + template = params['invite_processing_template'] + + return responses.respond(request, template, context=context) + + @decorators.merge_params @decorators.check_access def listSelf(self, request, access_type, @@ -140,26 +176,29 @@ properties = {'account': users.get_current_user()} user_entity = user_logic.logic.getForFields(properties, unique=True) - # construct the Unhandled Requests list + # construct the Unhandled Invites list - # only select the requests for this user that haven't been handled yet + # only select the Invites for this user that haven't been handled yet filter = {'link_id': user_entity.link_id, - 'group_accepted' : True} + 'group_accepted' : True, + 'user_accepted' : False, + 'completed' : False} uh_params = params.copy() - uh_params['list_action'] = (redirects.inviteAcceptedRedirect, None) + uh_params['list_action'] = (redirects.inviteProcessRedirect, None) uh_params['list_description'] = ugettext_lazy( - "An overview of your unhandled requests.") + "An overview of your unhandled invites.") uh_list = helper.lists.getListContent( request, uh_params, filter, 0) # construct the Open Requests list - # only select the requests for the user + # only select the requests from the user # that haven't been accepted by an admin yet filter = {'link_id' : user_entity.link_id, - 'group_accepted' : False} + 'group_accepted' : False, + 'completed' : False} ar_params = params.copy() ar_params['list_description'] = ugettext_lazy( @@ -174,32 +213,20 @@ # call the _list method from base to display the list return self._list(request, params, contents, page_name) - def _editSeed(self, request, seed): - """See base.View._editGet(). - """ - - # fill in the email field with the data from the entity - seed['user'] = seed['link_id'] - seed['group'] = seed['scope_path'] - - def _editGet(self, request, entity, form): - """See base.View._editGet(). - """ - - # fill in the email field with the data from the entity - form.fields['user'].initial = entity.link_id - form.fields['group'].initial = entity.scope_path - - super(View, self)._editGet(request, entity, form) def _editPost(self, request, entity, fields): """See base.View._editPost(). """ - # fill in the account field with the user created from email - fields['link_id'] = fields['requester'].link_id - fields['scope_path'] = fields['to'].link_id - fields['scope'] = fields['to'] + # TODO(ljvderijk) remove this once host has been rewritten + callback, args, kwargs = urlresolvers.resolve(request.path) + + # fill in the fields via kwargs + fields['link_id'] = kwargs['link_id'] + fields['scope_path'] = kwargs['scope_path'] + fields['role'] = kwargs['role'] + fields['role_verbose'] = 'Some Role' + fields['group_accepted'] = True super(View, self)._editPost(request, entity, fields) @@ -211,6 +238,7 @@ delete = view.delete list = view.list list_self = view.listSelf +processInvite = view.processInvite public = view.public export = view.export