# HG changeset patch # User Sverre Rabbelier # Date 1226876688 0 # Node ID 87afae6e4c5107e2ddbb8dd0c71e343ac5873814 # Parent 5e9c656a1b68e0fd4cfc647ecae673de4ad46317 Added basic 'invite' functionality Currently the invite can only be created, existing ones can be listed and edited, or deleted if desired. diff -r 5e9c656a1b68 -r 87afae6e4c51 app/soc/logic/models/request.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/soc/logic/models/request.py Sun Nov 16 23:04:48 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. + +"""Host (Model) query functions. +""" + +__authors__ = [ + '"Sverre Rabbelier" ', + ] + + +from soc.logic import key_name +from soc.logic.models import base + +import soc.models.request + + +class Logic(base.Logic): + """Logic methods for the Request model + """ + + def __init__(self): + """Defines the name, key_name and model for this entity. + """ + + base.Logic.__init__(self, soc.models.request.Request) + + def getKeyValues(self, entity): + """See base.Logic.getKeyNameValues. + """ + + return [entity.role, entity.to.link_name, entity.requester.link_name] + + def getKeyValuesFromFields(self, fields): + """See base.Logic.getKeyValuesFromFields. + """ + + return [fields['role'], fields['group_ln'], fields['user_ln']] + + def getKeyFieldNames(self): + """See base.Logic.getKeyFieldNames + """ + + return ['role', 'group_ln', 'user_ln'] + + +logic = Logic() diff -r 5e9c656a1b68 -r 87afae6e4c51 app/soc/models/request.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/soc/models/request.py Sun Nov 16 23:04:48 2008 +0000 @@ -0,0 +1,59 @@ +#!/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. + +"""This module contains the Request Model.""" + +__authors__ = [ + '"Sverre Rabbelier" ', +] + + +import polymodel + +from google.appengine.ext import db + +from django.utils.translation import ugettext_lazy + +import soc.models.user +import soc.models.group + + +class Request(polymodel.PolyModel): + """A request is made to allow a person to create a new Role entity. + """ + + requester = db.ReferenceProperty(reference_class=soc.models.user.User, + required=True, collection_name="requests") + requester.help_text = ugettext_lazy( + 'This is the user who the request is made for') + + role = db.StringProperty() + role.help_text = ugettext_lazy( + 'This should be the type of the role that is requested') + + to = db.ReferenceProperty(reference_class=soc.models.group.Group, + required=True, collection_name="requests") + to.help_text = ugettext_lazy( + 'The group that the request should be made to ' + '(this group should have the authority to grant the request)') + + accepted = db.BooleanProperty() + accepted.help_text = ugettext_lazy( + 'Field used to indicate whether a request has been accepted') + + declined = db.BooleanProperty() + declined.help_text = ugettext_lazy( + 'Field used to indicate that a request has been rejected by the user') diff -r 5e9c656a1b68 -r 87afae6e4c51 app/soc/templates/soc/models/create_invite.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/soc/templates/soc/models/create_invite.html Sun Nov 16 23:04:48 2008 +0000 @@ -0,0 +1,18 @@ +{% extends "soc/models/list.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 %} + +{% block instructions %} +Please use this form to invite someone to become a {{ entity_type }}. +{% endblock %} diff -r 5e9c656a1b68 -r 87afae6e4c51 app/soc/templates/soc/models/invite.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/soc/templates/soc/models/invite.html Sun Nov 16 23:04:48 2008 +0000 @@ -0,0 +1,27 @@ +{% extends "soc/models/edit.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 %} + +{% block instructions %} +Hit 'Send Invitation' to send the invite. +{% endblock %} + +{% block submit_buttons %} + + + + + + +{% endblock %} diff -r 5e9c656a1b68 -r 87afae6e4c51 app/soc/templates/soc/request/list/request_heading.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/soc/templates/soc/request/list/request_heading.html Sun Nov 16 23:04:48 2008 +0000 @@ -0,0 +1,7 @@ + + Requester + Role + To + Accepted + Declined + diff -r 5e9c656a1b68 -r 87afae6e4c51 app/soc/templates/soc/request/list/request_row.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/soc/templates/soc/request/list/request_row.html Sun Nov 16 23:04:48 2008 +0000 @@ -0,0 +1,11 @@ + + + +
{{ data_element.role }}
+
{{ data_element.to.link_name }}
+
{{ data_element.accepted }}
+
{{ data_element.declined }}
+ diff -r 5e9c656a1b68 -r 87afae6e4c51 app/soc/templates/soc/request/public.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/soc/templates/soc/request/public.html Sun Nov 16 23:04:48 2008 +0000 @@ -0,0 +1,32 @@ +{% 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_name }} +{% endblock %} + +{% block body %} +

+ + {% readonly_field_as_table_row entity.fields.role.label entity.role %} + {% readonly_field_as_table_row entity.fields.requester.label entity.requester.link_name %} + {% readonly_field_as_table_row entity.fields.to.label entity.to.link_name %} + {% readonly_field_as_table_row entity.fields.accepted.label entity.accepted %} + {% readonly_field_as_table_row entity.fields.declined.label entity.declined %} + +
+

+{% endblock %} diff -r 5e9c656a1b68 -r 87afae6e4c51 app/soc/templates/soc/sponsor/edit.html --- a/app/soc/templates/soc/sponsor/edit.html Sun Nov 16 23:03:37 2008 +0000 +++ b/app/soc/templates/soc/sponsor/edit.html Sun Nov 16 23:04:48 2008 +0000 @@ -15,4 +15,7 @@ {% block submit_buttons %} {{ block.super }} + + + {% endblock %} diff -r 5e9c656a1b68 -r 87afae6e4c51 app/soc/templates/soc/user/list/user_row.html --- a/app/soc/templates/soc/user/list/user_row.html Sun Nov 16 23:03:37 2008 +0000 +++ b/app/soc/templates/soc/user/list/user_row.html Sun Nov 16 23:04:48 2008 +0000 @@ -1,7 +1,7 @@ +onclick="document.location.href='/{{redirect_action|lower}}/{{ data_element.link_name }}'" name="name"> diff -r 5e9c656a1b68 -r 87afae6e4c51 app/soc/views/models/base.py --- a/app/soc/views/models/base.py Sun Nov 16 23:03:37 2008 +0000 +++ b/app/soc/views/models/base.py Sun Nov 16 23:04:48 2008 +0000 @@ -105,6 +105,8 @@ 'soc.views.models.%s.public', 'Show %(name)s'), (r'^%(name_lower)s/create$', 'soc.views.models.%s.create', 'Create %(name)s'), + (r'^%(name_lower)s/create/%(key_fields)s$', + 'soc.views.models.%s.create', 'Create %(name)s'), (r'^%(name_lower)s/delete/%(key_fields)s$', 'soc.views.models.%s.delete', 'Delete %(name)s'), (r'^%(name_lower)s/edit/%(key_fields)s$', @@ -113,6 +115,8 @@ 'soc.views.models.%s.list', 'List %(name_plural)s'), ] + new_params['list_redirect_action'] = params['name_short'] + '/edit' + self._rights = dicts.merge(rights, new_rights) self._params = dicts.merge(params, new_params) @@ -186,7 +190,7 @@ if not kwargs: return self.edit(request, page_name=page_name, params=params, **empty_kwargs) else: - return self.edit(request, page_name=page_name, params=params, seed=kwargs) + return self.edit(request, page_name=page_name, params=params, seed=kwargs, **empty_kwargs) def edit(self, request, page_name=None, params=None, seed=None, **kwargs): """Displays the edit page for the entity specified by **kwargs @@ -295,6 +299,7 @@ self._editGet(request, entity, form) else: if seed: + self._editSeed(request, seed) form = params['create_form'](initial=seed) else: form = params['create_form']() @@ -336,6 +341,7 @@ context['entity_type'] = params['name'] context['entity_type_plural'] = params['name_plural'] + context['redirect_action'] = params['list_redirect_action'] template = params['list_template'] diff -r 5e9c656a1b68 -r 87afae6e4c51 app/soc/views/models/host.py --- a/app/soc/views/models/host.py Sun Nov 16 23:03:37 2008 +0000 +++ b/app/soc/views/models/host.py Sun Nov 16 23:04:48 2008 +0000 @@ -31,6 +31,7 @@ import soc.models.host import soc.logic.models.host +import soc.logic.models.sponsor import soc.views.helper @@ -71,7 +72,7 @@ pass -class View(base.View): +class View(role.RoleView): """View methods for the Host model """ @@ -89,6 +90,10 @@ params = {} rights = {} + params['logic'] = soc.logic.models.host.logic + params['group_logic'] = soc.logic.models.sponsor.logic + params['invite_filter'] = {'group_ln': 'link_name'} + params['name'] = "Host" params['name_short'] = "Host" params['name_plural'] = "Hosts" @@ -100,6 +105,7 @@ params['edit_template'] = 'soc/models/edit.html' params['public_template'] = 'soc/host/public.html' params['list_template'] = 'soc/models/list.html' + params['invite_template'] = 'soc/models/invite.html' params['lists_template'] = { 'list_main': 'soc/list/list_main.html', @@ -109,7 +115,7 @@ } params['delete_redirect'] = '/host/list' - params['invite_redirect'] = '/host/list' + params['invite_redirect'] = '/request/list' params['save_message'] = [ugettext_lazy('Profile saved.')] @@ -123,14 +129,7 @@ params = dicts.merge(original_params, params) rights = dicts.merge(original_rights, rights) - base.View.__init__(self, rights=rights, params=params) - - def _editPost(self, request, entity, fields): - """See base.View._editPost(). - """ - - fields['sponsor_ln'] = fields['sponsor'].link_name - fields['user_ln'] = fields['user'].link_name + role.RoleView.__init__(self, original_rights=rights, original_params=params) view = View() diff -r 5e9c656a1b68 -r 87afae6e4c51 app/soc/views/models/request.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/soc/views/models/request.py Sun Nov 16 23:04:48 2008 +0000 @@ -0,0 +1,172 @@ +#!/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 Requests. +""" + +__authors__ = [ + '"Sverre Rabbelier" ', + '"Lennard de Rijk" ', + '"Pawel Solyga" ', + ] + + +from google.appengine.api import users + +from django import forms +from django.utils.translation import ugettext_lazy + +from soc.logic import dicts +from soc.logic import validate +from soc.logic.models import sponsor as sponsor_logic +from soc.logic.models import user as user_logic +from soc.views import helper +from soc.views.helper import widgets +from soc.views.models import base + +import soc.models.request +import soc.logic.models.request +import soc.logic.dicts +import soc.views.helper +import soc.views.helper.widgets + +class CreateForm(helper.forms.BaseForm): + """Django form displayed when Developer creates a Request. + """ + + class Meta: + model = soc.models.request.Request + + #: list of model fields which will *not* be gathered by the form + exclude = ['inheritance_line', 'requester', 'to', 'role', 'declined'] + + role = forms.CharField(widget=helper.widgets.ReadOnlyInput()) + + user = forms.CharField( + label=soc.models.request.Request.requester.verbose_name, + help_text=soc.models.request.Request.requester.help_text, + widget=helper.widgets.ReadOnlyInput()) + + group = forms.CharField( + label=soc.models.request.Request.to.verbose_name, + help_text=soc.models.request.Request.to.help_text, + widget=helper.widgets.ReadOnlyInput()) + + def clean_user(self): + self.cleaned_data['requester'] = user_logic.logic.getForFields( + {'link_name': self.cleaned_data['user']}, unique=True) + return self.cleaned_data['user'] + + def clean_group(self): + self.cleaned_data['to'] = sponsor_logic.logic.getFromFields( + link_name=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 + """ + + def __init__(self, original_params=None, original_rights=None): + """Defines the fields and methods required for the base View class + to provide the user with list, public, create, edit and delete views. + + Params: + original_params: a dict with params for this View + original_rights: a dict with right definitions for this View + """ + + self._logic = soc.logic.models.request.logic + + params = {} + rights = {} + + params['name'] = "Request" + params['name_short'] = "Request" + params['name_plural'] = "Requests" + + params['edit_form'] = EditForm + params['create_form'] = CreateForm + + # TODO(tlarsen) Add support for Django style template lookup + params['edit_template'] = 'soc/models/edit.html' + params['public_template'] = 'soc/request/public.html' + params['list_template'] = 'soc/models/list.html' + + params['lists_template'] = { + 'list_main': 'soc/list/list_main.html', + 'list_pagination': 'soc/list/list_pagination.html', + 'list_row': 'soc/request/list/request_row.html', + 'list_heading': 'soc/request/list/request_heading.html', + } + + params['sidebar_defaults'] = [('/%s/list', 'List %(plural)s')] + + params['delete_redirect'] = '/request/list' + params['create_redirect'] = '/request' + + params['save_message'] = [ugettext_lazy('Request saved.')] + + params['edit_params'] = { + self.DEF_SUBMIT_MSG_PARAM_NAME: self.DEF_SUBMIT_MSG_PROFILE_SAVED, + } + + rights['list'] = [helper.access.checkIsDeveloper] + rights['delete'] = [helper.access.checkIsDeveloper] + + params = dicts.merge(original_params, params) + rights = dicts.merge(original_rights, rights) + + base.View.__init__(self, rights=rights, params=params) + + def _editSeed(self, request, seed): + """See base.View._editGet(). + """ + + # fill in the email field with the data from the entity + seed['user'] = seed['user_ln'] + seed['group'] = seed['group_ln'] + + 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.requester.link_name + form.fields['group'].initial = entity.to.link_name + + def _editPost(self, request, entity, fields): + """See base.View._editPost(). + """ + + # fill in the account field with the user created from email + fields['user_ln'] = fields['requester'].link_name + fields['group_ln'] = fields['to'].link_name + + +view = View() + +create = view.create +edit = view.edit +delete = view.delete +list = view.list +public = view.public diff -r 5e9c656a1b68 -r 87afae6e4c51 app/soc/views/models/role.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/soc/views/models/role.py Sun Nov 16 23:04:48 2008 +0000 @@ -0,0 +1,202 @@ +#!/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 Sponsor profiles. +""" + +__authors__ = [ + '"Sverre Rabbelier" ', + ] + + +from google.appengine.api import users + +from django import forms +from django import http +from django.utils.translation import ugettext_lazy + +from soc.models import request as request_model +from soc.logic import dicts +from soc.logic.models import request as request_logic +from soc.logic.models import user as user_logic +from soc.views import helper +from soc.views.models import base +from soc.views.models import user as user_view + +import soc.models.request +import soc.views.helper.widgets + + +class RequestForm(helper.forms.BaseForm): + """Django form displayed when creating a new Invite + """ + + 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.request.Request + + #: exclude pretty much everything, model=None would also remove the help text etc. + exclude = ['inheritance_line', 'requester', 'to', 'role', 'accepted', 'declined'] + + requester = forms.CharField(widget=helper.widgets.ReadOnlyInput()) + + role = forms.CharField(widget=helper.widgets.ReadOnlyInput()) + + to = forms.CharField(widget=helper.widgets.ReadOnlyInput()) + + +class RoleView(base.View): + """Views for all entities that inherit from Role + + All views that only Role entities have are defined in this subclass. + """ + + def __init__(self, original_params=None, original_rights=None): + """ + + Args: + rights: This dictionary should be filled with the access check + functions that should be called, it will be modified in-place. + params: This dictionary should be filled with the parameters + """ + + params = {} + rights = {} + + params = dicts.merge(original_params, params) + rights = dicts.merge(original_rights, rights) + + base.View.__init__(self, rights=rights, params=params) + + def invite(self, request, page_name=None, params=None, **kwargs): + """Displays the request promotion to Role page. + """ + + new_params = {} + + new_params['list_template'] = 'soc/models/create_invite.html' + new_params['list_redirect_action'] = 'request/create/%s/%s' % ( + self._params['name_short'].lower(), + kwargs['link_name']) + new_params['list_redirect_entity'] = self._params['name'] + new_params['name'] = self._params['name'] + new_params['name_short'] = self._params['name_short'] + new_params['name_plural'] = self._params['name_plural'] + + params = dicts.merge(params, new_params) + + try: + self.checkAccess('invite', request) + except soc.views.out_of_band.AccessViolationResponse, alt_response: + return alt_response.response() + + return user_view.list(request, page_name=page_name, params=params) + + def promote(self, request, page_name=None, **kwargs): + """Displays the promote to Role page. + + Args: + request: the standard Django HTTP request object + page: a soc.logic.site.page.Page object which is abstraction + that combines a Django view with sidebar menu info + kwargs: the Key Fields for the specified entity + """ + + properties = { + 'accepted': True, + } + + entity = request_logic.logic.updateOrCreateFromFields(properties, **kwargs) + + # TODO(SRabbelier) finish this + + def accept(self, request, page_name=None, params=None, **kwargs): + """Displays the accept a Role request page. + + Args: + request: the standard Django HTTP request object + page: a soc.logic.site.page.Page object which is abstraction + that combines a Django view with sidebar menu info + kwargs: the Key Fields for the specified entity + """ + + entity = request_logic.logic.getFromFields(**kwargs) + + if entity.declined: + properties = { + 'declined': False, + } + + request_logic.logic.updateModelProperties(entity, **properties) + + if not entity.accepted: + raise Error("The request has not yet been accepted") + + id = users.get_current_user() + user = models.user.logic.getFromFields(email=id.email()) + + if entity.user != user: + raise Error("The request is being accepted by the wrong person") + + if entity.role != params['name'].lower(): + raise Error("The wrong module is handling the request") + + redirect = params['accept_redirect'] + suffix = self._logic.getKeySuffix(entity) + + return helper.responses.redirectToChangedSuffix( + request, suffix, suffix) + + def decline(self, request, page_name=None, **kwargs): + """Displays the decline a Role request page. + + Args: + request: the standard Django HTTP request object + page: a soc.logic.site.page.Page object which is abstraction + that combines a Django view with sidebar menu info + kwargs: the Key Fields for the specified entity + """ + + properties = { + 'declined': True, + } + + request_logic.logic.updateOrCreateFromFields(properties, **kwargs) + + redirect = self._params['decline_redirect'] + suffix = self._logic.getKeySuffix(entity) + + return helper.responses.redirectToChangedSuffix( + request, suffix, suffix) + + def getDjangoURLPatterns(self): + """see base.View.getDjangoURLPatterns() + """ + + params = {} + default_patterns = self._params['django_patterns_defaults'] + default_patterns += [ + (r'^%(name_lower)s/invite/%(lnp)s$', + 'soc.views.models.%s.invite', 'Invite %(name)s')] + + params['django_patterns_defaults'] = default_patterns + patterns = super(RoleView, self).getDjangoURLPatterns(params) + + return patterns + diff -r 5e9c656a1b68 -r 87afae6e4c51 app/soc/views/models/user.py --- a/app/soc/views/models/user.py Sun Nov 16 23:03:37 2008 +0000 +++ b/app/soc/views/models/user.py Sun Nov 16 23:04:48 2008 +0000 @@ -184,7 +184,6 @@ """ # fill in the email field with the data from the entity form.fields['email'].initial = entity.account.email() - def _editPost(self, request, entity, fields): """See base.View._editPost().