You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@allura.apache.org by st...@apache.org on 2013/03/11 21:55:08 UTC
git commit: organization neighborhood and project
Updated Branches:
refs/heads/sg/5566 f7cc73d4e -> c4e7da6ec
organization neighborhood and project
Signed-off-by: Stefano Invernizzi <st...@apache.org>
Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/c4e7da6e
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/c4e7da6e
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/c4e7da6e
Branch: refs/heads/sg/5566
Commit: c4e7da6ec3601989690be78cbab779ad0cb581cb
Parents: f7cc73d
Author: Simone Gatti <si...@gmail.com>
Authored: Sun Mar 10 21:49:19 2013 +0100
Committer: Stefano Invernizzi <st...@apache.org>
Committed: Mon Mar 11 21:42:50 2013 +0100
----------------------------------------------------------------------
Allura/allura/controllers/root.py | 4 +-
Allura/allura/lib/app_globals.py | 2 +-
Allura/allura/model/project.py | 28 ++-
Allura/allura/nf/allura/css/allura.css | 13 +
Allura/allura/websetup/bootstrap.py | 7 +
.../organization/controller/organization.py | 271 +--------------
.../organization/model/organization.py | 26 +-
.../organization/templates/edit_profile.html | 118 ------
.../templates/organization_profile.html | 185 ----------
.../organization/templates/user_memberships.html | 3 +-
.../organization/widgets/forms.py | 42 +--
.../organization_profile/__init__.py | 1 +
.../organization_profile/organization_main.py | 277 +++++++++++++++
.../templates/edit_profile.html | 111 ++++++
.../templates/organization_index.html | 206 +++++++++++
.../forgeorganization/tests/test_organizations.py | 123 ++-----
.../tool/controller/organizationtool.py | 6 +-
ForgeOrganization/setup.py | 2 +-
18 files changed, 704 insertions(+), 721 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/c4e7da6e/Allura/allura/controllers/root.py
----------------------------------------------------------------------
diff --git a/Allura/allura/controllers/root.py b/Allura/allura/controllers/root.py
index 9c2b2ba..d286437 100644
--- a/Allura/allura/controllers/root.py
+++ b/Allura/allura/controllers/root.py
@@ -70,8 +70,8 @@ class RootController(WsgiDispatchController):
n.bind_controller(self)
self.browse = ProjectBrowseController()
- ep = g.entry_points["organizations"].get('organization')
- if ep and g.show_organizations:
+ if g.show_organizations:
+ ep = g.entry_points["organizations"].get('organization')
self.organization = ep().root
super(RootController, self).__init__()
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/c4e7da6e/Allura/allura/lib/app_globals.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/app_globals.py b/Allura/allura/lib/app_globals.py
index ad1f62b..845832a 100644
--- a/Allura/allura/lib/app_globals.py
+++ b/Allura/allura/lib/app_globals.py
@@ -171,7 +171,7 @@ class Globals(object):
# Zarkov logger
self._zarkov = None
- self.show_organizations = config.get('organizations.enable')=='true'
+ self.show_organizations = 'organization' in self.entry_points['organizations']
@LazyProperty
def spam_checker(self):
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/c4e7da6e/Allura/allura/model/project.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/project.py b/Allura/allura/model/project.py
index 9b24036..0a279c8 100644
--- a/Allura/allura/model/project.py
+++ b/Allura/allura/model/project.py
@@ -290,6 +290,10 @@ class Project(MappedClass, ActivityNode, ActivityObject):
def is_user_project(self):
return self.shortname.startswith('u/')
+ @property
+ def is_organization_project(self):
+ return self.shortname.startswith('o/')
+
@LazyProperty
def user_project_of(self):
'''
@@ -301,6 +305,17 @@ class Project(MappedClass, ActivityNode, ActivityObject):
return user
@LazyProperty
+ def organization_project_of(self):
+ '''
+ If this is a organization-project, return the Organization, else None
+ '''
+ user = None
+ if self.is_organization_project:
+ from forgeorganization.organization.model import Organization
+ organization = Organization.query.get(shortname=self.shortname[2:])
+ return organization
+
+ @LazyProperty
def root_project(self):
if self.is_root: return self
return self.parent_project.root_project
@@ -407,6 +422,11 @@ class Project(MappedClass, ActivityNode, ActivityObject):
max_ordinal = delta_ordinal
delta_ordinal = delta_ordinal + 1
+ if self.is_organization_project:
+ entries.append({'ordinal': delta_ordinal, 'entry':SitemapEntry('Profile', "%sorganizationprofile/" % self.url(), ui_icon="tool-home")})
+ max_ordinal = delta_ordinal
+ delta_ordinal = delta_ordinal + 1
+
for sub in self.direct_subprojects:
ordinal = sub.ordinal + delta_ordinal
if ordinal > max_ordinal:
@@ -629,6 +649,10 @@ class Project(MappedClass, ActivityNode, ActivityObject):
for mount in mounts:
if 'ac' in mount and mount['ac'].tool_name == 'profile':
return mount
+ if self.is_organization_project:
+ for mount in mounts:
+ if 'ac' in mount and mount['ac'].tool_name == 'organizationprofile':
+ return mount
if mounts and required_access is None:
return mounts[0]
for mount in mounts:
@@ -720,8 +744,10 @@ class Project(MappedClass, ActivityNode, ActivityObject):
apps = [('admin', 'admin', 'Admin'),
('search', 'search', 'Search'),
('activity', 'activity', 'Activity')]
- if g.show_organizations:
+ if g.show_organizations and not (self.is_organization_project or is_user_project):
apps+=[('organizationstool', 'organizationstool', 'Organizations')]
+ if self.is_organization_project:
+ apps=[('organizationprofile', 'organizationprofile', 'Profile')]+apps
with h.push_config(c, project=self, user=users[0]):
# Install default named roles (#78)
root_project_id=self.root_project._id
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/c4e7da6e/Allura/allura/nf/allura/css/allura.css
----------------------------------------------------------------------
diff --git a/Allura/allura/nf/allura/css/allura.css b/Allura/allura/nf/allura/css/allura.css
index 224ad0a..3edeb03 100644
--- a/Allura/allura/nf/allura/css/allura.css
+++ b/Allura/allura/nf/allura/css/allura.css
@@ -41,6 +41,11 @@ b.ico.ico-vote-down { background-image: url('../images/vote_down.png'); }
background-repeat: no-repeat;
}
+.ui-icon-tool-organizationprofile {
+ background-image: url("../images/home_24.png");
+ background-repeat: no-repeat;
+}
+
.ui-icon-tool-wiki {
background-image: url("../images/wiki_24.png");
background-repeat: no-repeat;
@@ -120,6 +125,11 @@ b.ico.ico-vote-down { background-image: url('../images/vote_down.png'); }
{
background-image: url("../images/code_32.png");
}
+
+.ui-icon-tool-organizationprofile{
+ background-image: url("../images/home_32.png");
+ background-repeat: no-repeat;
+}
#top_nav .ui-icon-tool-stats {
background-image: url("../images/stats_32.png");
}
@@ -153,6 +163,9 @@ b.ico.ico-vote-down { background-image: url('../images/vote_down.png'); }
.big_icon.ui-icon-tool-home {
background-image: url("../images/home_48.png");
}
+.big_icon.ui-icon-tool-organizationprofile {
+ background-image: url("../images/home_48.png");
+}
.big_icon.ui-icon-tool-wiki {
background-image: url("../images/wiki_48.png");
}
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/c4e7da6e/Allura/allura/websetup/bootstrap.py
----------------------------------------------------------------------
diff --git a/Allura/allura/websetup/bootstrap.py b/Allura/allura/websetup/bootstrap.py
index 59d240d..75150ba 100644
--- a/Allura/allura/websetup/bootstrap.py
+++ b/Allura/allura/websetup/bootstrap.py
@@ -93,6 +93,12 @@ def bootstrap(command, conf, vars):
max_projects = None,
css = 'none',
google_analytics = False))
+ n_organizations = M.Neighborhood(name='Organizations', url_prefix='/o/',
+ shortname_prefix='o/',
+ features=dict(private_projects = True,
+ max_projects = None,
+ css = 'none',
+ google_analytics = False))
n_adobe = M.Neighborhood(name='Adobe', url_prefix='/adobe/', project_list_url='/adobe/',
features=dict(private_projects = True,
max_projects = None,
@@ -103,6 +109,7 @@ def bootstrap(command, conf, vars):
p_projects = project_reg.register_neighborhood_project(n_projects, [root], allow_register=True)
p_users = project_reg.register_neighborhood_project(n_users, [root])
p_adobe = project_reg.register_neighborhood_project(n_adobe, [root])
+ p_organizations = project_reg.register_neighborhood_project(n_organizations, [root])
ThreadLocalORMSession.flush_all()
ThreadLocalORMSession.close_all()
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/c4e7da6e/ForgeOrganization/forgeorganization/organization/controller/organization.py
----------------------------------------------------------------------
diff --git a/ForgeOrganization/forgeorganization/organization/controller/organization.py b/ForgeOrganization/forgeorganization/organization/controller/organization.py
index f9f4213..1e2bada 100644
--- a/ForgeOrganization/forgeorganization/organization/controller/organization.py
+++ b/ForgeOrganization/forgeorganization/organization/controller/organization.py
@@ -19,10 +19,6 @@ from pkg_resources import get_entry_info
class Forms(object):
registration_form = forms.RegistrationForm(action='/organization/save_new')
search_form = forms.SearchForm(action='/organization/search')
- update_profile = forms.UpdateProfile()
- add_work_field = forms.AddWorkField()
- remove_work_field = forms.RemoveWorkField()
- invite_user_form = forms.InviteUser()
admission_request_form = forms.RequestAdmissionForm()
request_collaboration_form = forms.RequestCollaborationForm()
@@ -38,14 +34,6 @@ class Forms(object):
F = Forms()
class OrganizationController(object):
- @expose()
- def _lookup(self, shortname, *remainder):
- org = Organization.query.get(shortname=shortname)
- if not org:
- return OrganizationController(), remainder
- else:
- return OrganizationProfileController(organization=org), remainder
-
@expose('jinja:forgeorganization:organization/templates/user_memberships.html')
def index(self, **kw):
require_authenticated()
@@ -74,169 +62,31 @@ class OrganizationController(object):
@require_post()
@validate(F.registration_form, error_handler=register)
def save_new(self, fullname, shortname, orgtype, role, **kw):
- o = Organization.register(shortname, fullname, orgtype)
+ o = Organization.register(shortname, fullname, orgtype, c.user)
if o is None:
flash(
'The short name "%s" has been taken by another organization.' \
% shortname, 'error')
redirect('/organization/register')
- m = Membership.insert('admin', role, 'active', o._id, c.user._id)
+ m = Membership.insert(role, 'active', o._id, c.user._id)
flash('Organization "%s" correctly created!' % fullname)
- redirect('/organization/%s/edit_profile' % shortname)
-
-class OrganizationProfileController(BaseController):
- def __init__(self, organization):
- self.organization = organization
- super(OrganizationProfileController, self).__init__()
-
- @with_trailing_slash
- @expose('jinja:forgeorganization:organization/templates/organization_profile.html')
- def index(self, **kw):
- activecoll=[coll for coll in self.organization.project_involvements
- if coll.status=='active']
- closedcoll=[p for p in self.organization.project_involvements
- if p.status=='closed']
-
- role = self.organization.userPermissions(c.user)
- mlist=[m for m in self.organization.memberships if m.status=='active']
- plist=[m for m in self.organization.memberships if m.status=='closed']
-
- is_admin = (role == 'admin')
-
- return dict(
- workfields = WorkFields.query.find(),
- members = mlist,
- past_members=plist,
- active_collaborations=activecoll,
- closed_collaborations=closedcoll,
- organization = self.organization,
- is_member = (role is not None),
- is_admin = (role =='admin'),
- forms = F)
-
- @expose('jinja:forgeorganization:organization/templates/edit_profile.html')
- def edit_profile(self, **kw):
- require_authenticated()
-
- mlist=[m for m in self.organization.memberships if m.status!='closed']
- clist=[el for el in self.organization.project_involvements
- if el.status!='closed']
-
- return dict(
- permissions = self.organization.userPermissions(c.user),
- organization = self.organization,
- members = mlist,
- collaborations= clist,
- forms = F)
-
- @expose()
- @require_post()
- @validate(F.update_profile, error_handler=edit_profile)
- def change_data(self, **kw):
- require_authenticated()
- if self.organization.userPermissions(c.user) != 'admin':
- flash(
- "You don't have the permission to perform this action.",
- "error")
- redirect(self.organization.url())
- self.organization.organization_type = kw['organization_type']
- self.organization.fullname = kw['fullname']
- self.organization.description = kw['description']
- self.organization.headquarters = kw['headquarters']
- self.organization.dimension = kw['dimension']
- self.organization.website = kw['website']
-
- flash('The organization profile has been successfully updated.')
- redirect(self.organization.url()+'edit_profile')
-
- @expose()
- @require_post()
- @validate(F.remove_work_field, error_handler=edit_profile)
- def remove_work_field(self, **kw):
- require_authenticated()
- if self.organization.userPermissions(c.user) != 'admin':
- flash(
- "You don't have the permission to perform this action.",
- "error")
- redirect(self.organization.url())
- self.organization.removeWorkField(kw['workfield'])
- flash('The organization profile has been successfully updated.')
- redirect(self.organization.url()+'edit_profile')
-
- @expose()
- @require_post()
- @validate(V.NullValidator(), error_handler=edit_profile)
- def add_work_field(self, workfield, **kw):
- require_authenticated()
- workfield = WorkFields.getById(workfield)
-
- if workfield is None:
- flash("Invalid workfield. Select a valid value.", "error")
- redirect(self.organization.url()+'edit_profile')
-
- if self.organization.userPermissions(c.user)!='admin':
- flash(
- "You don't have the permission to perform this action.",
- "error")
- redirect(self.organization.url())
- self.organization.addWorkField(workfield)
- flash('The organization profile has been successfully updated.')
- redirect(self.organization.url()+'edit_profile')
-
- @expose()
- @require_post()
- @validate(F.invite_user_form, error_handler=edit_profile)
- def invite_user(self, **kw):
- require_authenticated()
- username = kw['username']
- if self.organization.userPermissions(c.user) != 'admin':
- flash(
- "You don't have the permission to perform this action.",
- "error")
- redirect(self.organization.url())
- user = M.User.query.get(username=kw['username'])
- if not user:
- flash(
- '''The username "%s" doesn't belong to any user on the forge'''\
- % username, "error")
- redirect(self.organization.url() + 'edit_profile')
-
- invitation = Membership.insert('member', kw['role'], 'invitation',
- self.organization._id, user._id)
- if invitation:
- flash(
- 'The user '+ username +' has been successfully invited to '+ \
- 'become a member of the organization.')
- else:
- flash(
- username+' is already a member of the organization.', 'error')
-
- redirect(self.organization.url()+'edit_profile')
+ redirect('%sadmin/organizationprofile' % o.url())
@expose()
@require_post()
@validate(V.NullValidator(), error_handler=index)
- def change_membership(self, **kw):
- require_authenticated()
-
+ def change_membership(self, **kw):
membershipid = kw['membershipid']
memb = Membership.getById(membershipid)
status = kw['status']
- new_permission = kw['permission']
-
- if memb:
- user_permission = memb.organization.userPermissions(c.user)
- if memb is None or (memb.member!=c.user and user_permission!='admin'):
+
+ return_url = '/organization'
+
+ if c.user != memb.member:
flash(
"You don't have the permission to perform this action.",
"error")
- redirect('/organization')
- return
-
- if kw.get('requestfrom') == 'user':
- return_url = '/organization'
- else:
- return_url = memb.organization.url() + 'edit_profile'
+ redirect(return_url)
if status == 'remove':
old_status = memb.status
@@ -252,117 +102,16 @@ class OrganizationProfileController(BaseController):
redirect(return_url)
return
- if status == 'closed' and memb.membertype == 'admin':
- if len(memb.organization.getAdministrators())==1:
- flash(
- 'This user is the only administrator of the organization, '+\
- 'therefore, before closing this enrollment, another '+\
- 'administrator has to be set.', 'error')
- redirect(return_url)
- return
-
- if status == 'closed':
- new_permission = memb.membertype
-
allowed=True
if memb.status=='closed' and status!='closed':
allowed=False
- if memb.status=='request' and status=='active' and user_permission!='admin':
- allowed=False
- if memb.status=='invitation' and status=='active' and memb.member!=c.user:
+ if memb.status=='request' and status=='active':
allowed=False
- if new_permission != memb.membertype:
- if user_permission!='admin' and memb.member != c.user:
- allowed = False
- admins = memb.organization.getAdministrators()
- if new_permission == 'member' and len(admins)==1:
- flash(
- 'This user is the only administrator of the organization, '+\
- 'therefore, before changing his permission level, another '+\
- 'administrator has to be set.', 'error')
- redirect(return_url)
- return
if allowed:
memb.setStatus(status)
- memb.membertype = new_permission
memb.role = kw.get('role')
- if new_permission=='member' and memb.member == c.user:
- return_url = '/organization'
flash('The membership has been successfully updated.')
else:
flash("You are not allowed to perform this action.")
redirect(return_url)
-
- @expose()
- @require_post()
- @validate(F.admission_request_form, error_handler=index)
- def admission_request(self, role, **kw):
- require_authenticated()
- m=Membership.insert(
- 'member', role, 'request', self.organization._id, c.user._id)
- flash('Request sent')
- redirect('/organization/')
-
- @expose()
- @require_post()
- @validate(F.request_collaboration_form, error_handler=edit_profile)
- def send_collaboration_request(self, project_url_name, collaboration_type, **kw):
- require_authenticated()
- user_permission = self.organization.userPermissions(c.user)
- if user_permission != 'admin':
- flash(
- "You don't have the permission to perform this action.",
- "error")
- redirect('/organization')
- return
- project=M.Project.query.get(shortname=project_url_name)
- if not project:
- flash(
- "Invalid URL name. Please, insert the URL name of an existing "+\
- "project.", "error")
- else:
- ProjectInvolvement.insert('request', collaboration_type,
- self.organization._id, project._id)
- flash("Collaboration request successfully sent.")
- redirect('%sedit_profile' % self.organization.url())
-
- @expose()
- @require_post()
- @validate(V.NullValidator(), error_handler=edit_profile)
- def update_collaboration_status(self, collaborationid, collaborationtype, status, **kw):
- require_authenticated()
-
- coll = ProjectInvolvement.getById(collaborationid)
- user_permission = coll.organization.userPermissions(c.user)
- if user_permission != 'admin':
- flash(
- "You don't have the permission to perform this action.",
- "error")
- redirect('/organization')
- return
-
- allowed = True
- if coll.status != status:
- if coll.status=='invitation' and status not in ['active','remove']:
- allowed=False
- elif coll.status=='closed':
- allowed=False
- elif coll.status=='active' and status!='closed':
- allowed=False
- elif coll.status=='request' and status !='remove':
- allowed=False
-
- if allowed:
- if status=='closed':
- collaborationtype=coll.collaborationtype
-
- if status=='remove':
- ProjectInvolvement.delete(coll._id)
- else:
- coll.collaborationtype=collaborationtype
- coll.setStatus(status)
- flash('The information about this collaboration has been updated')
- else:
- flash("You are not allowed to perform this action", "error")
- redirect('%sedit_profile' % coll.organization.url())
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/c4e7da6e/ForgeOrganization/forgeorganization/organization/model/organization.py
----------------------------------------------------------------------
diff --git a/ForgeOrganization/forgeorganization/organization/model/organization.py b/ForgeOrganization/forgeorganization/organization/model/organization.py
index 104f198..907f07a 100644
--- a/ForgeOrganization/forgeorganization/organization/model/organization.py
+++ b/ForgeOrganization/forgeorganization/organization/model/organization.py
@@ -49,10 +49,14 @@ class Organization(MappedClass):
project_involvements=RelationProperty('ProjectInvolvement')
def url(self):
- return '/organization/' + self.shortname.replace('_', '-') + '/'
+ return ('/o/' + self.shortname.replace('_', '-') + '/').encode('ascii','ignore')
+
+ def project(self):
+ return M.Project.query.get(
+ shortname='o/'+self.shortname.replace('_', '-'))
@classmethod
- def register(cls, shortname, fullname, orgtype):
+ def register(cls, shortname, fullname, orgtype, user):
o=cls.query.get(shortname=shortname)
if o is not None: return None
try:
@@ -64,7 +68,9 @@ class Organization(MappedClass):
except pymongo.errors.DuplicateKeyError:
session(o).expunge(o)
return None
-
+ if o is not None:
+ n = M.Neighborhood.query.get(name='Organizations')
+ n.register_project('o/'+shortname, user=user, user_project=False)
return o
@classmethod
@@ -97,12 +103,6 @@ class Organization(MappedClass):
if wfid in self.workfields:
del self.workfields[self.workfields.index(wfid)]
- def userPermissions(self, user):
- for rel in self.memberships:
- if rel.member_id == user._id and rel.status=='active':
- return rel.membertype
- return None
-
def getActiveCooperations(self):
return [c for c in self.project_involvements if c.status=='active' and
c.collaborationtype == 'cooperation']
@@ -119,10 +119,6 @@ class Organization(MappedClass):
return [c for c in self.project_involvements if c.status=='closed' and
c.collaborationtype == 'participation']
- def getAdministrators(self):
- return [m for m in self.memberships
- if m.membertype=='admin' and m.status =='active']
-
def getEnrolledUsers(self):
return [m for m in self.memberships if m.status=='active']
@@ -160,7 +156,6 @@ class Membership(MappedClass):
name='organization_membership'
_id=FieldProperty(S.ObjectId)
- membertype=FieldProperty(S.OneOf('admin', 'member'))
status=FieldProperty(S.OneOf('active', 'closed', 'invitation', 'request'))
role=FieldProperty(str)
organization_id=ForeignIdProperty('Organization')
@@ -172,7 +167,7 @@ class Membership(MappedClass):
member = RelationProperty('User')
@classmethod
- def insert(cls, membertype, role, status, organization_id, member_id):
+ def insert(cls, role, status, organization_id, member_id):
m = cls.query.find(dict(organization_id=organization_id, member_id=member_id))
for el in m:
if el.status!='closed':
@@ -181,7 +176,6 @@ class Membership(MappedClass):
m = cls(
organization_id=organization_id,
member_id=member_id,
- membertype=membertype,
role=role,
startdate=None,
status=status)
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/c4e7da6e/ForgeOrganization/forgeorganization/organization/templates/edit_profile.html
----------------------------------------------------------------------
diff --git a/ForgeOrganization/forgeorganization/organization/templates/edit_profile.html b/ForgeOrganization/forgeorganization/organization/templates/edit_profile.html
deleted file mode 100644
index df90567..0000000
--- a/ForgeOrganization/forgeorganization/organization/templates/edit_profile.html
+++ /dev/null
@@ -1,118 +0,0 @@
-{% set hide_left_bar = True %}
-{% extends g.theme.master %}
-
-{% block title %}Change organization data{% endblock %}
-
-{% block header %}Change organization data{% endblock %}
-
-{% block content %}
-
-{% if permissions == 'admin' %}
-
- <ul><li><a href="{{organization.url()}}">Click here</a> to check your public profile.</ul>
- <div class="grid-20">
- <h2>Data to be included in {{organization.fullname}}'s profile</h2>
- {{forms.update_profile.display(organization=organization, action=organization.url()+'change_data') }}
- </div>
-
- <div class="grid-20" style="clear:both;">
- <h2>Work Fields</h2>
- {% if organization.workfields %}
- <table>
- <tr>
- <thead>
- <th>Work field</th>
- <th>Description</th>
- <th>Actions</th>
- </thead>
- </tr>
- {% for wf in organization.getWorkfields() %}
- {{forms.remove_work_field.display(workfield=wf, action=organization.url()+'remove_work_field')}}
- {%endfor%}
- </table>
- {% else %}
- <p>At the moment, there are no working fields set for this organization.</p>
- {% endif %}
- <h3>Add a new work field</h3>
- <div class="grid-20" style="margin:0;">
- {{forms.add_work_field.display(organization=organization, action=organization.url()+'add_work_field') }}
- </div>
- </div>
-
- <div class="grid-20">
- <h2>Members</h2>
-
- {% if members %}
- <table>
- <thead>
- <tr>
- <th>Name</th>
- <th>Permission level</th>
- <th>Role</th>
- <th>Status</th>
- <th>Actions</th>
- </tr>
- </thead>
- <tbody>
- {% for membership in members %}
- <tr>
- {{forms.new_change_membership_from_organization().display(
- membership=membership,
- action=membership.organization.url()+'change_membership')}}
- </tr>
- {% endfor %}
- </tbody>
- </table>
- {% else %}
- <p>This organization doesn't have any enrolled member.</p>
- {% endif %}
- <h3>Add a new user</h3>
- <p>
- You can add a member of your organization to the above list by filling the following form with his or her
- username and the user's role within the organization.
- </p>
- {{forms.invite_user_form.display(action=organization.url()+'invite_user')}}
-
- </div>
-
- <div class="grid-20">
- <h2>Collaborations</h2>
-
- {% if collaborations %}
- <h3>Edit existing collaborations</h3>
- <table>
- <thead>
- <tr>
- <th>Project</th>
- <th>Collaboration type</th>
- <th>Status</th>
- <th>Actions</th>
- </tr>
- </thead>
- <tbody>
- {% for org in collaborations %}
- <tr>
- {{forms.new_change_collaboration_status().display(
- collaboration=org,
- action=org.organization.url()+'update_collaboration_status')}}
- </tr>
- {% endfor %}
- </tbody>
- </table>
- {% else %}
- <p>At the moment, this organization doesn't collaborate in any project.</p>
- {% endif %}
-
- <h3>Add a new collaboration</h3>
- <p>
- If you want to include a new collaboration in your profile, you can look for the project and send an admission request.
- Otherwise, you can add it using the following form.
- </p>
- {{forms.request_collaboration_form.display(action=organization.url()+'send_collaboration_request')}}
- </div>
-
-{% else %}
- <div class="grid-20">You don't have the permissions to edit the profile of this organization.</div>
-{% endif %}
-
-{% endblock %}
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/c4e7da6e/ForgeOrganization/forgeorganization/organization/templates/organization_profile.html
----------------------------------------------------------------------
diff --git a/ForgeOrganization/forgeorganization/organization/templates/organization_profile.html b/ForgeOrganization/forgeorganization/organization/templates/organization_profile.html
deleted file mode 100644
index c825113..0000000
--- a/ForgeOrganization/forgeorganization/organization/templates/organization_profile.html
+++ /dev/null
@@ -1,185 +0,0 @@
-{% set hide_left_bar = True %}
-{% extends g.theme.master %}
-{% block title %}Organization profile{% endblock %}
-{% block header %}Organization profile{% endblock %}
-
-{% block content %}
-
- {% if not organization %}
- <div class="grid-20">
- This organization doesn't exists.
- </div>
- {% else %}
- {% if is_admin %}
- <ul><li><a href="{{organization.url()+'edit_profile'}}">Click here</a> to update this profile</li></ul>
- {% endif %}
- <h2>{{organization.fullname}} – General data</h2>
-
- <div class="grid-20" style="margin-top:5px;margin-left:5px;">
- <label class="grid-4">Organization Type:</label>
- <label class="grid-14">{{organization.organization_type}}</label>
- </div>
- {% if organization.description %}
- <div class="grid-20" style="margin-top:5px;margin-left:5px;">
- <label class="grid-4">Description:</label>
- <label class="grid-14">{{organization.description}}</label>
- </div>
- {% endif %}
- {% if organization.dimension and organization.dimension != 'Unknown' %}
- <div class="grid-20" style="margin-top:5px;margin-left:5px;">
- <label class="grid-4">Dimension:</label>
- <label class="grid-14">
- {% if organization.dimension == 'Small' %}Small – No more than 50 members{% endif %}
- {% if organization.dimension == 'Medium' %}Medium – Between 51 and 250 members{% endif %}
- {% if organization.dimension == 'Large' %}Big – More than 250 members{% endif %}
- </label>
- </div>
- {% endif %}
- {% if organization.headquarters %}
- <div class="grid-20" style="margin-top:5px;margin-left:5px;">
- <label class="grid-4">Headquarters:</label>
- <label class="grid-14">{{organization.headquarters}}</label>
- </div>
- {% endif %}
- {% if organization.website %}
- <div class="grid-20" style="margin-top:5px;margin-left:5px;">
- <label class="grid-4">Website:</label>
- <label class="grid-14"><a href="{{organization.website}}">{{organization.website}}</a></label>
- </div>
- {% endif %}
- {% if organization.getWorkfields() %}
- <div class="grid-20" style="margin-top:5px;margin-left:5px;">
- <label class="grid-4">Workfields:</label>
- <div class="grid-14">
- <ul>
- {% for wf in organization.getWorkfields() %}
- <li>{{wf.name}} – {{wf.description}}</li>
- {% endfor %}
- </ul>
- </div>
- </div>
- {% endif %}
-
- <h2 style="clear:both;">Members</h2>
- <div class="grid-20">
- {% if members %}
- <h3>Currently enrolled members</h3>
- <table>
- <thead>
- <tr>
- <th>Name</th>
- <th>Role</th>
- <th>Admission date on the forge</th>
- </tr>
- </thead>
- <tbody>
- {% for member in members -%}
- <tr>
- <td><a href="{{member.member.url()}}">{{member.member.display_name}}</a></td>
- <td>{{member.role}}</td>
- <td>{{member.startdate.strftime("%d %B %Y")}}</td>
- </tr>
- {% endfor %}
- </tbody>
- </table>
- {% else %}
- <p>Currently, this organization doesn't have any member.</p>
- {% endif %}
- </div>
-
- <div class="grid-20">
- {% if past_members %}
- <h3>Past members of the organization</h3>
- <table>
- <thead>
- <tr>
- <th>Name</th>
- <th>Role</th>
- <th>Admission date on the forge</th>
- <th>Closing membership date</th>
- </tr>
- </thead>
- <tbody>
- {% for member in past_members -%}
- <tr>
- <td><a href="{{member.member.url()}}">{{member.member.display_name}}</a></td>
- <td>{{member.role}}</td>
- <td>{{member.startdate.strftime("%d %B %Y")}}</td>
- <td>{{member.closeddate.strftime("%d %B %Y")}}</td>
- </tr>
- {% endfor %}
- </tbody>
- </table>
- {% endif %}
- </div>
-
- {% if not is_member %}
- <div class="grid-20" style="clear:both;">
- <h3>Are you a member of this organization?</h3>
- <p>
- If you are a member of this organization, you can send a request to appear in the list above. Before being admitted
- to the organization, an administrator of the organization profile has to confirm your enrollment.
- </p>
- <div class="grid-20" style="margin:0;clear:both;">
- {{forms.admission_request_form.display(action=organization.url()+'admission_request')}}
- </div>
- </div>
- {% endif %}
-
- <h2 style="clear:both;">Projects and collaborations</h2>
- {%if active_collaborations %}
- <div class="grid-20">
- <h3>Active collaborations</h3>
- </div>
- <div class="grid-20">
- <table>
- <thead>
- <th>Project</th>
- <th>Collaboration type</th>
- <th>Start date</th>
- </thead>
- <tbody>
- {% for collaboration in active_collaborations %}
- <tr>
- <td><a href="{{collaboration.project.url()}}">{{collaboration.project.name}}</a></td>
- <td>{{collaboration.collaborationtype.capitalize()}}</td>
- <td>{{collaboration.startdate.strftime("%d %B %Y")}}</td>
- </tr>
- {% endfor %}
- </tbody>
- </table>
- </div>
- {% endif %}
-
- {%if closed_collaborations %}
- <div class="grid-20">
- <h3>Past projects collaborations</h3>
- </div>
- <div class="grid-20">
- <table>
- <thead>
- <th>Project</th>
- <th>Collaboration type</th>
- <th>Start date</th>
- <th>End date</th>
- </thead>
- <tbody>
- {% for collaboration in closed_collaborations %}
- <tr>
- <td><a href="{{collaboration.project.url()}}">{{collaboration.project.name}}</a></td>
- <td>{{collaboration.collaborationtype.capitalize()}}</td>
- <td>{{collaboration.startdate.strftime("%d %B %Y")}}</td>
- <td>{{collaboration.closeddate.strftime("%d %B %Y")}}</td>
- </tr>
- {% endfor %}
- </tbody>
- </table>
- </div>
- {% endif %}
-
- {% if not (closed_collaborations or active_collaborations) %}
- <div class="grid-18"><p>This organization has never collaborated to any project.</p></div>
- {% endif %}
-
- {% endif %}
-{% endblock %}
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/c4e7da6e/ForgeOrganization/forgeorganization/organization/templates/user_memberships.html
----------------------------------------------------------------------
diff --git a/ForgeOrganization/forgeorganization/organization/templates/user_memberships.html b/ForgeOrganization/forgeorganization/organization/templates/user_memberships.html
index 649b637..f130cea 100644
--- a/ForgeOrganization/forgeorganization/organization/templates/user_memberships.html
+++ b/ForgeOrganization/forgeorganization/organization/templates/user_memberships.html
@@ -12,7 +12,6 @@
<tr>
<th>Name</th>
<th>Organization type</th>
- <th>Permission level</th>
<th>Role</th>
<th>Status</th>
<th>Actions</th>
@@ -22,7 +21,7 @@
{% for membership in memberships %}
{{forms.new_change_membership_from_user_form().display(
membership=membership,
- action=membership.organization.url()+'change_membership')}}
+ action='organization/change_membership')}}
{% endfor %}
</tbody>
</table>
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/c4e7da6e/ForgeOrganization/forgeorganization/organization/widgets/forms.py
----------------------------------------------------------------------
diff --git a/ForgeOrganization/forgeorganization/organization/widgets/forms.py b/ForgeOrganization/forgeorganization/organization/widgets/forms.py
index 91cb3d1..f0403e1 100644
--- a/ForgeOrganization/forgeorganization/organization/widgets/forms.py
+++ b/ForgeOrganization/forgeorganization/organization/widgets/forms.py
@@ -1,6 +1,6 @@
import logging
import warnings
-from pylons import g
+from pylons import g, c
from allura.lib import validators as V
from allura.lib import helpers as h
from allura.lib import plugin
@@ -206,9 +206,9 @@ class ChangeMembershipFromUser(ForgeForm):
m = kw.get('membership')
org = m.organization
- orgnamefield = '<a href="%s">%s</a>' % (org.url(), org.fullname)
- if m.membertype == 'admin':
- orgnamefield+=' (<a href="%sedit_profile">edit</a>)'%org.url()
+ orgnamefield = '<a href="%s">%s</a>' % (org.url()+"organizationprofile", org.fullname)
+ if c.user.username in m.organization.project().admins():
+ orgnamefield+=' (<a href="%sadmin/organizationprofile">edit</a>)'%org.url()
if m.status == 'active':
statusoptions = [
ew.Option(py_value='active',label='Active',selected=True),
@@ -231,23 +231,6 @@ class ChangeMembershipFromUser(ForgeForm):
ew.Option(
py_value='remove',label='Remove request',selected=False)]
- if m.membertype=='admin':
- permission = ew.SingleSelectField(
- name='permission',
- show_errors=False,
- options = [
- ew.Option(
- py_value='admin',label='Admin',selected=True),
- ew.Option(
- py_value='member',label='Member',selected=False)])
- additional_hidden = []
- else:
- permission = ew.HTMLField(
- text=m.membertype.capitalize(), show_errors=False)
- additional_hidden = [ew.HiddenField(
- name="permission",
- show_errors=False,
- attrs={'value':m.membertype})]
self.fields = [
ew.RowField(
show_errors=False,
@@ -260,7 +243,7 @@ class ChangeMembershipFromUser(ForgeForm):
name="requestfrom",
attrs={'value':'user'},
show_errors=False)
- ] + additional_hidden,
+ ],
fields=[
ew.HTMLField(
text=orgnamefield,
@@ -268,7 +251,6 @@ class ChangeMembershipFromUser(ForgeForm):
ew.HTMLField(
text=org.organization_type,
show_errors=False),
- permission,
ew.TextField(
name='role',
attrs=dict(value=m.role),
@@ -332,18 +314,6 @@ class ChangeMembershipFromOrganization(ForgeForm):
text='<a href="%s">%s</a>' % (
user.url(), user.display_name),
show_errors=False),
- ew.SingleSelectField(
- name='permission',
- show_errors=False,
- options = [
- ew.Option(
- py_value='admin',
- label='Admin',
- selected=m.membertype=='admin'),
- ew.Option(
- py_value='member',
- label='Member',
- selected=m.membertype=='member')]),
ew.TextField(
name='role',
attrs=dict(value=m.role),
@@ -431,5 +401,3 @@ class ChangeCollaborationStatusForm(ForgeForm):
attrs={'value':'Save'},
show_errors=False)])]
return super(ChangeCollaborationStatusForm, self).display(**kw)
-
-
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/c4e7da6e/ForgeOrganization/forgeorganization/organization_profile/__init__.py
----------------------------------------------------------------------
diff --git a/ForgeOrganization/forgeorganization/organization_profile/__init__.py b/ForgeOrganization/forgeorganization/organization_profile/__init__.py
new file mode 100644
index 0000000..0eae989
--- /dev/null
+++ b/ForgeOrganization/forgeorganization/organization_profile/__init__.py
@@ -0,0 +1 @@
+from .organization_main import OrganizationProfileApp
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/c4e7da6e/ForgeOrganization/forgeorganization/organization_profile/organization_main.py
----------------------------------------------------------------------
diff --git a/ForgeOrganization/forgeorganization/organization_profile/organization_main.py b/ForgeOrganization/forgeorganization/organization_profile/organization_main.py
new file mode 100644
index 0000000..a74fddb
--- /dev/null
+++ b/ForgeOrganization/forgeorganization/organization_profile/organization_main.py
@@ -0,0 +1,277 @@
+import logging
+from pprint import pformat
+
+import pkg_resources
+from pylons import tmpl_context as c, app_globals as g
+from pylons import request
+from formencode import validators
+from tg import expose, redirect, validate, response, flash
+from webob import exc
+
+from allura import version
+from allura.app import Application, SitemapEntry
+from allura.lib import helpers as h
+from allura.lib.helpers import DateTimeConverter
+from allura.lib.security import require_access
+import allura.model as M
+from allura.model import User, Feed, ACE
+from allura.controllers import BaseController
+from allura.lib.decorators import require_post
+
+from forgeorganization.organization.model import Organization, WorkFields, Membership, ProjectInvolvement
+
+import forgeorganization.organization.widgets.forms as forms
+from allura.lib import validators as V
+from allura.lib.security import require_authenticated
+from allura.lib.decorators import require_post
+
+log = logging.getLogger(__name__)
+
+class Forms(object):
+ update_profile = forms.UpdateProfile()
+ add_work_field = forms.AddWorkField()
+ remove_work_field = forms.RemoveWorkField()
+ invite_user_form = forms.InviteUser()
+ admission_request_form = forms.RequestAdmissionForm()
+ request_collaboration_form = forms.RequestCollaborationForm()
+ def new_change_collaboration_status(self):
+ return forms.ChangeCollaborationStatusForm()
+ def new_change_membership_from_organization(self):
+ return forms.ChangeMembershipFromOrganization()
+
+F = Forms()
+
+class OrganizationProfileApp(Application):
+ __version__ = version.__version__
+ installable = False
+ icons={
+ 24:'images/home_24.png',
+ 32:'images/home_32.png',
+ 48:'images/home_48.png'
+ }
+
+ def __init__(self, user, config):
+ Application.__init__(self, user, config)
+ self.root = OrganizationProfileController()
+ self.admin = OrganizationProfileAdminController()
+
+ def admin_menu(self):
+ links = [SitemapEntry(
+ 'Edit',
+ '%sadmin/organizationprofile' % c.project.organization_project_of.url())]
+ return links
+
+ def install(self, project):
+ pr = c.user.project_role()
+ if pr:
+ self.config.acl = [
+ ACE.allow(pr._id, perm)
+ for perm in self.permissions ]
+
+ def uninstall(self, project):
+ pass
+
+ @property
+ @h.exceptionless([], log)
+ def sitemap(self):
+ return []
+
+class OrganizationProfileController(BaseController):
+
+ @expose('jinja:forgeorganization:organization_profile/templates/organization_index.html')
+ def index(self, **kw):
+ organization = c.project.organization_project_of
+ if not organization:
+ raise exc.HTTPNotFound()
+ activecoll=[coll for coll in organization.project_involvements
+ if coll.status=='active']
+ closedcoll=[p for p in organization.project_involvements
+ if p.status=='closed']
+ mlist=[m for m in organization.memberships if m.status=='active']
+ plist=[m for m in organization.memberships if m.status=='closed']
+ return dict(
+ forms = F,
+ ask_admission = (c.user not in [m.member for m in mlist]) and c.user != M.User.anonymous(),
+ workfields = WorkFields.query.find(),
+ organization=organization,
+ members = mlist,
+ past_members=plist,
+ active_collaborations=activecoll,
+ closed_collaborations=closedcoll)
+
+ @expose()
+ @require_post()
+ @validate(F.admission_request_form, error_handler=index)
+ def admission_request(self, role, **kw):
+ require_access(c.project, 'read')
+ m=Membership.insert(
+ role, 'request', c.project.organization_project_of._id, c.user._id)
+ flash('Request sent')
+ redirect(c.project.organization_project_of.url()+'organizationprofile')
+
+class OrganizationProfileAdminController(BaseController):
+ @expose('jinja:forgeorganization:organization_profile/templates/edit_profile.html')
+ def index(self, **kw):
+ require_access(c.project, 'admin')
+
+ organization = c.project.organization_project_of
+ mlist=[m for m in organization.memberships if m.status!='closed']
+ clist=[el for el in organization.project_involvements
+ if el.status!='closed']
+
+ return dict(
+ organization = organization,
+ members = mlist,
+ collaborations= clist,
+ forms = F)
+
+ @expose()
+ @require_post()
+ @validate(F.remove_work_field, error_handler=index)
+ def remove_work_field(self, **kw):
+ require_access(c.project, 'admin')
+ c.project.organization_project_of.removeWorkField(kw['workfield'])
+ flash('The organization profile has been successfully updated.')
+ redirect(c.project.organization_project_of.url()+'admin/organizationprofile')
+
+ @expose()
+ @require_post()
+ @validate(V.NullValidator(), error_handler=index)
+ def add_work_field(self, workfield, **kw):
+ require_access(c.project, 'admin')
+ workfield = WorkFields.getById(workfield)
+
+ if workfield is None:
+ flash("Invalid workfield. Select a valid value.", "error")
+ redirect(c.project.organization_project_of.url()+'admin/organizationprofile')
+ c.project.organization_project_of.addWorkField(workfield)
+ flash('The organization profile has been successfully updated.')
+ redirect(c.project.organization_project_of.url()+'admin/organizationprofile')
+
+ @expose()
+ @require_post()
+ @validate(F.invite_user_form, error_handler=index)
+ def invite_user(self, **kw):
+ require_access(c.project, 'admin')
+ username = kw['username']
+ user = M.User.query.get(username=kw['username'])
+ if not user:
+ flash(
+ '''The username "%s" doesn't belong to any user on the forge'''\
+ % username, "error")
+ redirect(c.project.organization_project_of.url() + 'admin/organizationprofile')
+
+ invitation = Membership.insert(kw['role'], 'invitation',
+ c.project.organization_project_of._id, user._id)
+ if invitation:
+ flash(
+ 'The user '+ username +' has been successfully invited to '+ \
+ 'become a member of the organization.')
+ else:
+ flash(
+ username+' is already a member of the organization.', 'error')
+
+ redirect(c.project.organization_project_of.url()+'admin/organizationprofile')
+
+ @expose()
+ @require_post()
+ @validate(V.NullValidator(), error_handler=index)
+ def change_membership(self, **kw):
+ membershipid = kw['membershipid']
+ memb = Membership.getById(membershipid)
+ status = kw['status']
+
+ return_url = memb.organization.url() + 'admin/organizationprofile'
+
+ if status == 'remove':
+ old_status = memb.status
+ if memb.status in ['invitation', 'request']:
+ Membership.delete(memb)
+ flash('The pending %s has been removed.' % old_status)
+ redirect(return_url)
+ return
+ else:
+ flash(
+ "You don't have the permission to perform this action.",
+ "error")
+ redirect(return_url)
+ return
+
+ allowed=True
+ if memb.status=='closed' and status!='closed':
+ allowed=False
+ if memb.status=='invitation' and status=='active':
+ allowed=False
+
+ if allowed:
+ memb.setStatus(status)
+ memb.role = kw.get('role')
+ flash('The membership has been successfully updated.')
+ else:
+ flash("You are not allowed to perform this action.")
+ redirect(return_url)
+
+ @expose()
+ @require_post()
+ @validate(F.request_collaboration_form, error_handler=index)
+ def send_collaboration_request(self, project_url_name, collaboration_type, **kw):
+ require_access(c.project, 'admin')
+ project=M.Project.query.get(shortname=project_url_name)
+ if not project:
+ flash(
+ "Invalid URL name. Please, insert the URL name of an existing "+\
+ "project.", "error")
+ else:
+ ProjectInvolvement.insert('request', collaboration_type,
+ c.project.organization_project_of._id, project._id)
+ flash("Collaboration request successfully sent.")
+ redirect('%sadmin/organizationprofile' % c.project.organization_project_of.url())
+
+ @expose()
+ @require_post()
+ @validate(V.NullValidator(), error_handler=index)
+ def update_collaboration_status(self, collaborationid, collaborationtype, status, **kw):
+ require_access(c.project, 'admin')
+
+ coll = ProjectInvolvement.getById(collaborationid)
+
+ allowed = True
+ if coll.status != status:
+ if coll.status=='invitation' and status not in ['active','remove']:
+ allowed=False
+ elif coll.status=='closed':
+ allowed=False
+ elif coll.status=='active' and status!='closed':
+ allowed=False
+ elif coll.status=='request' and status !='remove':
+ allowed=False
+
+ if allowed:
+ if status=='closed':
+ collaborationtype=coll.collaborationtype
+
+ if status=='remove':
+ ProjectInvolvement.delete(coll._id)
+ else:
+ coll.collaborationtype=collaborationtype
+ coll.setStatus(status)
+ flash('The information about this collaboration has been updated')
+ else:
+ flash("You are not allowed to perform this action", "error")
+ redirect('%sadmin/organizationprofile' % coll.organization.url())
+
+ @expose()
+ @require_post()
+ @validate(F.update_profile, error_handler=index)
+ def change_data(self, **kw):
+ require_access(c.project, 'admin')
+
+ c.project.organization_project_of.organization_type = kw['organization_type']
+ c.project.organization_project_of.fullname = kw['fullname']
+ c.project.organization_project_of.description = kw['description']
+ c.project.organization_project_of.headquarters = kw['headquarters']
+ c.project.organization_project_of.dimension = kw['dimension']
+ c.project.organization_project_of.website = kw['website']
+
+ flash('The organization profile has been successfully updated.')
+ redirect(c.project.organization_project_of.url() + 'admin/organizationprofile')
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/c4e7da6e/ForgeOrganization/forgeorganization/organization_profile/templates/__init__.py
----------------------------------------------------------------------
diff --git a/ForgeOrganization/forgeorganization/organization_profile/templates/__init__.py b/ForgeOrganization/forgeorganization/organization_profile/templates/__init__.py
new file mode 100644
index 0000000..e69de29
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/c4e7da6e/ForgeOrganization/forgeorganization/organization_profile/templates/edit_profile.html
----------------------------------------------------------------------
diff --git a/ForgeOrganization/forgeorganization/organization_profile/templates/edit_profile.html b/ForgeOrganization/forgeorganization/organization_profile/templates/edit_profile.html
new file mode 100644
index 0000000..aca2a57
--- /dev/null
+++ b/ForgeOrganization/forgeorganization/organization_profile/templates/edit_profile.html
@@ -0,0 +1,111 @@
+{% set hide_left_bar = True %}
+{% extends g.theme.master %}
+
+{% block title %}Change organization data{% endblock %}
+
+{% block header %}Change organization data{% endblock %}
+
+{% block content %}
+
+ <ul><li><a href="{{organization.url()}}organizationprofile">Click here</a> to check your public profile.</ul>
+ <div class="grid-20">
+ <h2>Data to be included in {{organization.fullname}}'s profile</h2>
+ {{forms.update_profile.display(organization=organization, action=organization.url()+'admin/organizationprofile/change_data') }}
+ </div>
+
+ <div class="grid-20" style="clear:both;">
+ <h2>Work Fields</h2>
+ {% if organization.workfields %}
+ <table>
+ <tr>
+ <thead>
+ <th>Work field</th>
+ <th>Description</th>
+ <th>Actions</th>
+ </thead>
+ </tr>
+ {% for wf in organization.getWorkfields() %}
+ {{forms.remove_work_field.display(workfield=wf, action=organization.url()+'admin/organizationprofile/remove_work_field')}}
+ {%endfor%}
+ </table>
+ {% else %}
+ <p>At the moment, there are no working fields set for this organization.</p>
+ {% endif %}
+ <h3>Add a new work field</h3>
+ <div class="grid-20" style="margin:0;">
+ {{forms.add_work_field.display(organization=organization, action=organization.url()+'admin/organizationprofile/add_work_field') }}
+ </div>
+ </div>
+
+ <div class="grid-20">
+ <h2>Members</h2>
+
+ {% if members %}
+ <table>
+ <thead>
+ <tr>
+ <th>Name</th>
+ <th>Role</th>
+ <th>Status</th>
+ <th>Actions</th>
+ </tr>
+ </thead>
+ <tbody>
+ {% for membership in members %}
+ <tr>
+ {{forms.new_change_membership_from_organization().display(
+ membership=membership,
+ action=membership.organization.url()+'admin/organizationprofile/change_membership')}}
+ </tr>
+ {% endfor %}
+ </tbody>
+ </table>
+ {% else %}
+ <p>This organization doesn't have any enrolled member.</p>
+ {% endif %}
+ <h3>Add a new user</h3>
+ <p>
+ You can add a member of your organization to the above list by filling the following form with his or her
+ username and the user's role within the organization.
+ </p>
+ {{forms.invite_user_form.display(action=organization.url()+'admin/organizationprofile/invite_user')}}
+
+ </div>
+
+ <div class="grid-20">
+ <h2>Collaborations</h2>
+
+ {% if collaborations %}
+ <h3>Edit existing collaborations</h3>
+ <table>
+ <thead>
+ <tr>
+ <th>Project</th>
+ <th>Collaboration type</th>
+ <th>Status</th>
+ <th>Actions</th>
+ </tr>
+ </thead>
+ <tbody>
+ {% for org in collaborations %}
+ <tr>
+ {{forms.new_change_collaboration_status().display(
+ collaboration=org,
+ action=organization.url()+'admin/organizationprofile/update_collaboration_status')}}
+ </tr>
+ {% endfor %}
+ </tbody>
+ </table>
+ {% else %}
+ <p>At the moment, this organization doesn't collaborate in any project.</p>
+ {% endif %}
+
+ <h3>Add a new collaboration</h3>
+ <p>
+ If you want to include a new collaboration in your profile, you can look for the project and send an admission request.
+ Otherwise, you can add it using the following form.
+ </p>
+ {{forms.request_collaboration_form.display(action=organization.url()+'admin/organizationprofile/send_collaboration_request')}}
+ </div>
+
+{% endblock %}
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/c4e7da6e/ForgeOrganization/forgeorganization/organization_profile/templates/organization_index.html
----------------------------------------------------------------------
diff --git a/ForgeOrganization/forgeorganization/organization_profile/templates/organization_index.html b/ForgeOrganization/forgeorganization/organization_profile/templates/organization_index.html
new file mode 100644
index 0000000..3c04a96
--- /dev/null
+++ b/ForgeOrganization/forgeorganization/organization_profile/templates/organization_index.html
@@ -0,0 +1,206 @@
+{% set hide_left_bar = True %}
+{% extends g.theme.master %}
+
+{% block title %}{{organization.fullname}} / Profile{% endblock %}
+
+{% block header %}{{ organization.fullname }}'s profile{% endblock %}
+
+{% block extra_css %}
+ <link rel="stylesheet" type="text/css"
+ href="{{g.app_static('css/user_profile.css')}}"/>
+{% endblock %}
+
+{% block head %}
+ <link rel="alternate" type="application/rss+xml" title="RSS" href="feed.rss">
+ <link rel="alternate" type="application/atom+xml" title="Atom" href="feed.atom">
+{% endblock %}
+
+{% block actions %}
+ <a href="{{c.app.url}}feed.rss" title="Follow"><b data-icon="{{g.icons['feed'].char}}" class="ico {{g.icons['feed'].css}}"></b></a>
+{% endblock %}
+
+{% block content %}
+
+ {% if not organization %}
+
+ <div class="grid-20">
+ This organization doesn't exists.
+ </div>
+
+ {% else %}
+
+ <h2>{{organization.fullname}} – General data</h2>
+ <div class="grid-20" style="margin-top:5px;margin-left:5px;">
+ <label class="grid-4">Organization Type:</label>
+ <label class="grid-14">{{organization.organization_type}}</label>
+ </div>
+
+ {% if organization.description %}
+ <div class="grid-20" style="margin-top:5px;margin-left:5px;">
+ <label class="grid-4">Description:</label>
+ <label class="grid-14">{{organization.description}}</label>
+ </div>
+ {% endif %}
+
+ {% if organization.dimension and organization.dimension != 'Unknown' %}
+ <div class="grid-20" style="margin-top:5px;margin-left:5px;">
+ <label class="grid-4">Dimension:</label>
+ <label class="grid-14">
+ {% if organization.dimension == 'Small' %}Small – No more than 50 members{% endif %}
+ {% if organization.dimension == 'Medium' %}Medium – Between 51 and 250 members{% endif %}
+ {% if organization.dimension == 'Large' %}Big – More than 250 members{% endif %}
+ </label>
+ </div>
+ {% endif %}
+
+ {% if organization.headquarters %}
+ <div class="grid-20" style="margin-top:5px;margin-left:5px;">
+ <label class="grid-4">Headquarters:</label>
+ <label class="grid-14">{{organization.headquarters}}</label>
+ </div>
+ {% endif %}
+
+ {% if organization.website %}
+ <div class="grid-20" style="margin-top:5px;margin-left:5px;">
+ <label class="grid-4">Website:</label>
+ <label class="grid-14"><a href="{{organization.website}}">{{organization.website}}</a></label>
+ </div>
+ {% endif %}
+
+ {% if organization.getWorkfields() %}
+ <div class="grid-20" style="margin-top:5px;margin-left:5px;">
+ <label class="grid-4">Workfields:</label>
+ <div class="grid-14">
+ <ul>
+ {% for wf in organization.getWorkfields() %}
+ <li>{{wf.name}} – {{wf.description}}</li>
+ {% endfor %}
+ </ul>
+ </div>
+ </div>
+ {% endif %}
+
+ <h2 style="clear:both;">Members</h2>
+ <div class="grid-20">
+ {% if members %}
+ <h3>Currently enrolled members</h3>
+ <table>
+ <thead>
+ <tr>
+ <th>Name</th>
+ <th>Role</th>
+ <th>Admission date on the forge</th>
+ </tr>
+ </thead>
+ <tbody>
+ {% for member in members -%}
+ <tr>
+ <td><a href="{{member.member.url()}}">{{member.member.display_name}}</a></td>
+ <td>{{member.role}}</td>
+ <td>{{member.startdate.strftime("%d %B %Y")}}</td>
+ </tr>
+ {% endfor %}
+ </tbody>
+ </table>
+ {% else %}
+ <p>Currently, this organization doesn't have any member.</p>
+ {% endif %}
+ </div>
+
+ <div class="grid-20">
+ {% if past_members %}
+ <h3>Past members of the organization</h3>
+ <table>
+ <thead>
+ <tr>
+ <th>Name</th>
+ <th>Role</th>
+ <th>Admission date on the forge</th>
+ <th>Closing membership date</th>
+ </tr>
+ </thead>
+ <tbody>
+ {% for member in past_members -%}
+ <tr>
+ <td><a href="{{member.member.url()}}">{{member.member.display_name}}</a></td>
+ <td>{{member.role}}</td>
+ <td>{{member.startdate.strftime("%d %B %Y")}}</td>
+ <td>{{member.closeddate.strftime("%d %B %Y")}}</td>
+ </tr>
+ {% endfor %}
+ </tbody>
+ </table>
+ {% endif %}
+ </div>
+
+ {% if ask_admission %}
+ <div class="grid-20" style="clear:both;">
+ <h3>Are you a member of this organization?</h3>
+ <p>
+ If you are a member of this organization, you can send a request to appear in the list above. Before being admitted
+ to the organization, an administrator of the organization profile has to confirm your enrollment.
+ </p>
+ <div class="grid-20" style="margin:0;clear:both;">
+ {{forms.admission_request_form.display(action=organization.url()+'organizationprofile/admission_request')}}
+ </div>
+ </div>
+ {% endif %}
+
+ <h2 style="clear:both;">Projects and collaborations</h2>
+ {%if active_collaborations %}
+ <div class="grid-20">
+ <h3>Active collaborations</h3>
+ </div>
+ <div class="grid-20">
+ <table>
+ <thead>
+ <th>Project</th>
+ <th>Collaboration type</th>
+ <th>Start date</th>
+ </thead>
+ <tbody>
+ {% for collaboration in active_collaborations %}
+ <tr>
+ <td><a href="{{collaboration.project.url()}}">{{collaboration.project.name}}</a></td>
+ <td>{{collaboration.collaborationtype.capitalize()}}</td>
+ <td>{{collaboration.startdate.strftime("%d %B %Y")}}</td>
+ </tr>
+ {% endfor %}
+ </tbody>
+ </table>
+ </div>
+ {% endif %}
+
+ {%if closed_collaborations %}
+ <div class="grid-20">
+ <h3>Past projects collaborations</h3>
+ </div>
+ <div class="grid-20">
+ <table>
+ <thead>
+ <th>Project</th>
+ <th>Collaboration type</th>
+ <th>Start date</th>
+ <th>End date</th>
+ </thead>
+ <tbody>
+ {% for collaboration in closed_collaborations %}
+ <tr>
+ <td><a href="{{collaboration.project.url()}}">{{collaboration.project.name}}</a></td>
+ <td>{{collaboration.collaborationtype.capitalize()}}</td>
+ <td>{{collaboration.startdate.strftime("%d %B %Y")}}</td>
+ <td>{{collaboration.closeddate.strftime("%d %B %Y")}}</td>
+ </tr>
+ {% endfor %}
+ </tbody>
+ </table>
+ </div>
+ {% endif %}
+
+ {% if not (closed_collaborations or active_collaborations) %}
+ <div class="grid-18"><p>This organization has never collaborated to any project.</p></div>
+ {% endif %}
+
+ {% endif %}
+
+{% endblock %}
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/c4e7da6e/ForgeOrganization/forgeorganization/tests/test_organizations.py
----------------------------------------------------------------------
diff --git a/ForgeOrganization/forgeorganization/tests/test_organizations.py b/ForgeOrganization/forgeorganization/tests/test_organizations.py
index 96d7d02..32bc944 100644
--- a/ForgeOrganization/forgeorganization/tests/test_organizations.py
+++ b/ForgeOrganization/forgeorganization/tests/test_organizations.py
@@ -40,7 +40,7 @@ class TestOrganization(TestController):
#Add a new user
self.user2 = User.by_username('test-user-2')
- r = self.app.post('/organization/%s/invite_user' % self.name,
+ r = self.app.post((self.org.url()+'admin/organizationprofile/invite_user'),
params=dict(
username = self.user2.username,
role = 'Software Engineer'),
@@ -48,10 +48,9 @@ class TestOrganization(TestController):
m = OM.Membership.query.get(
member_id=self.user2._id,
organization_id=self.org._id)
- r = self.app.post('/organization/%s/change_membership' % self.name,
+ r = self.app.post('/organization/change_membership',
params=dict(
status = 'active',
- permission = 'member',
membershipid = str(m._id),
requestfrom = 'user',
role = 'Software Engineer'),
@@ -73,7 +72,7 @@ class TestOrganizationGeneral(TestOrganization):
m = OM.Membership.query.get(
member_id=c.user._id,
organization_id=self.org._id)
- assert self.org.userPermissions(c.user) == 'admin'
+ assert c.user.username in org.project().admins()
assert m.status == 'active'
assert m.role == self.role
@@ -88,7 +87,7 @@ class TestOrganizationGeneral(TestOrganization):
website = 'http://www.example.com'
#Update the profile of the organization
- r = self.app.post('/organization/%s/change_data' % self.name,
+ r = self.app.post('%sadmin/organizationprofile/change_data' % self.org.url(),
params = dict(
fullname = fullname,
organization_type = organization_type,
@@ -100,7 +99,7 @@ class TestOrganizationGeneral(TestOrganization):
ThreadLocalORMSession.flush_all()
- r = self.app.get('/organization/%s/' % self.name)
+ r = self.app.get('%sorganizationprofile' % self.org.url())
assert organization_type in r
assert description in r
assert headquarters in r
@@ -117,7 +116,7 @@ class TestOrganizationGeneral(TestOrganization):
assert self.org.fullname == fullname
#Try to provide invalid parameters
- r = self.app.post('/organization/%s/change_data' % self.name,
+ r = self.app.post('%sadmin/organizationprofile/change_data' % self.org.url(),
params = dict(
fullname = 'a',
organization_type = 'Invalid type',
@@ -129,7 +128,7 @@ class TestOrganizationGeneral(TestOrganization):
ThreadLocalORMSession.flush_all()
- r = self.app.get('/organization/%s/' % self.name,
+ r = self.app.get('%sorganizationprofile' % self.org.url(),
extra_environ=dict(username='test-user-1'))
self.org = OM.Organization.query.get(_id=self.org._id)
@@ -140,21 +139,20 @@ class TestOrganizationGeneral(TestOrganization):
assert self.org.website == website
assert self.org.fullname == fullname
+
@td.with_user_project('test-user-1')
def test_workfield(self):
wf = OM.WorkFields.query.get(name='Mobile apps')
c.user = User.by_username('test-user-1')
- r = self.app.get('/organization/%s/edit_profile' % self.name)
-
#Add a workfield
- r = self.app.post('/organization/%s/add_work_field' % self.name,
+ r = self.app.post('%sadmin/organizationprofile/add_work_field' % self.org.url(),
params = dict(
workfield = str(wf._id)),
extra_environ=dict(username='test-user-1'))
- r = self.app.get('/organization/%s/' % self.name)
+ r = self.app.get('%sorganizationprofile' % self.org.url())
self.org = OM.Organization.query.get(_id=self.org._id)
assert len(self.org.getWorkfields()) == 1
@@ -165,12 +163,12 @@ class TestOrganizationGeneral(TestOrganization):
#Add a second workfield
wf2 = OM.WorkFields.query.get(name='Web applications')
- r = self.app.post('/organization/%s/add_work_field' % self.name,
+ r = self.app.post('%sadmin/organizationprofile/add_work_field' % self.org.url(),
params = dict(
workfield = str(wf2._id)),
extra_environ=dict(username='test-user-1'))
- r = self.app.get('/organization/%s/' % self.name)
+ r = self.app.get('%sorganizationprofile' % self.org.url())
self.org = OM.Organization.query.get(_id=self.org._id)
assert len(self.org.getWorkfields()) == 2
@@ -178,11 +176,11 @@ class TestOrganizationGeneral(TestOrganization):
assert wf2.description in r
#Remove a workfield
- r = self.app.post('/organization/%s/remove_work_field' % self.name,
+ r = self.app.post('%sadmin/organizationprofile/remove_work_field' % self.org.url(),
params = {'workfieldid' : str(wf._id)},
extra_environ=dict(username='test-user-1'))
- r = self.app.get('/organization/%s/' % self.name)
+ r = self.app.get('%sorganizationprofile' % self.org.url())
assert len(self.org.getWorkfields()) == 1
assert self.org.getWorkfields()[0]._id == wf2._id
assert wf.name not in r
@@ -191,42 +189,18 @@ class TestOrganizationGeneral(TestOrganization):
class TestOrganizationMembership(TestOrganization):
@td.with_user_project('test-user-1')
- def test_invalid_close_membership(self):
- m = OM.Membership.query.get(
- member_id=c.user._id,
- organization_id=self.org._id)
-
- #Try to close the created membership
- r = self.app.post('/organization/%s/change_membership' % self.name,
- params=dict(
- status = 'closed',
- permission = 'admin',
- membershipid = str(m._id),
- requestfrom = 'user',
- role = self.role),
- extra_environ=dict(username='test-user-1'))
-
- #Since there aren't other admins, check that nothing changed
- m = OM.Membership.query.get(
- member_id=c.user._id,
- organization_id=self.org._id)
- assert self.org.userPermissions(c.user) == 'admin'
- assert m.status == 'active'
- assert m.role == self.role
-
- @td.with_user_project('test-user-1')
def test_invite_user(self):
#Try to invite a new user
user3 = User.by_username('test-admin')
testrole = 'Software Engineer'
- r = self.app.post('/organization/%s/invite_user' % self.name,
+ r = self.app.post('%sadmin/organizationprofile/invite_user' % self.org.url(),
params=dict(
username = user3.username,
role = testrole),
extra_environ=dict(username='test-user-1'))
- r = self.app.get('/organization/%s/edit_profile' % self.name,
+ r = self.app.get('%sadmin/organizationprofile' % self.org.url(),
extra_environ=dict(username='test-user-1'))
assert user3.display_name in r
@@ -237,12 +211,10 @@ class TestOrganizationMembership(TestOrganization):
assert m.role == testrole
#Accept invitation
- r = self.app.post('/organization/%s/change_membership' % self.name,
+ r = self.app.post('/organization/change_membership',
params=dict(
status = 'active',
- permission = 'member',
membershipid = str(m._id),
- requestfrom = 'user',
role = testrole),
extra_environ=dict(username='test-admin'))
@@ -251,41 +223,22 @@ class TestOrganizationMembership(TestOrganization):
organization_id=self.org._id)
assert m.status == 'active'
assert m.role == testrole
- assert m.membertype == 'member'
-
+
@td.with_user_project('test-user-1')
def test_change_permissions(self):
m = OM.Membership.query.get(
member_id=self.user2._id,
organization_id=self.org._id)
- #Change permissions of the non-admin user
- r = self.app.post('/organization/%s/change_membership' % self.name,
- params=dict(
- status = 'active',
- permission = 'admin',
- membershipid = str(m._id),
- requestfrom = 'organization',
- role = 'Software Engineer'),
- extra_environ=dict(username='test-user-1'))
-
- m = OM.Membership.query.get(
- member_id=self.user2._id,
- organization_id=self.org._id)
- assert m.status == 'active'
- assert m.role == 'Software Engineer'
- assert m.membertype == 'admin'
-
#Close the involvement of test-user-1
testuser1 = User.by_username('test-user-1')
m = OM.Membership.query.get(
member_id=testuser1._id,
organization_id=self.org._id)
- r = self.app.post('/organization/%s/change_membership' % self.name,
+ r = self.app.post('%sadmin/organizationprofile/change_membership' % self.org.url(),
params=dict(
status = 'closed',
- permission = 'admin',
membershipid = str(m._id),
requestfrom = 'user',
role = self.role),
@@ -294,23 +247,23 @@ class TestOrganizationMembership(TestOrganization):
m = OM.Membership.query.get(
member_id=c.user._id,
organization_id=self.org._id)
- assert self.org.userPermissions(c.user) == 'admin'
assert m.status == 'closed'
assert m.role == self.role
@td.with_user_project('test-admin')
def test_send_request(self):
- #Send an admission new user
+ #Send an admission request from a new user
user3 = User.by_username('test-admin')
testrole = 'Software Engineer'
- r = self.app.post('/organization/%s/admission_request' % self.name,
+ r = self.app.post('%sorganizationprofile/admission_request' % self.org.url(),
params=dict(
role = testrole),
extra_environ=dict(username='test-admin'))
-
- r = self.app.get('/organization/%s/edit_profile' % self.name,
+
+ c.user = M.User.by_username('test-user-1')
+ r = self.app.get('%sadmin/organizationprofile' % self.org.url(),
extra_environ=dict(username='test-user-1'))
assert user3.display_name in r
@@ -320,26 +273,10 @@ class TestOrganizationMembership(TestOrganization):
assert m.status == 'request'
assert m.role == testrole
- #Check that the user is not allowed to accept his own request
- r = self.app.post('/organization/%s/change_membership' % self.name,
- params=dict(
- status = 'active',
- permission = 'member',
- membershipid = str(m._id),
- requestfrom = 'user',
- role = testrole),
- extra_environ=dict(username='test-admin'))
-
- m = OM.Membership.query.get(
- member_id=user3._id,
- organization_id=self.org._id)
- assert m.status == 'request'
-
#Accept request
- r = self.app.post('/organization/%s/change_membership' % self.name,
+ r = self.app.post('%sadmin/organizationprofile/change_membership' % self.org.url(),
params=dict(
status = 'active',
- permission = 'member',
membershipid = str(m._id),
requestfrom = 'user',
role = testrole),
@@ -350,10 +287,8 @@ class TestOrganizationMembership(TestOrganization):
organization_id=self.org._id)
assert m.status == 'active'
assert m.role == testrole
- assert m.membertype == 'member'
-
-#check progetti
+#check projects
class TestOrganizationProjects(TestOrganization):
@td.with_user_project('test-admin')
@@ -425,7 +360,7 @@ class TestOrganizationProjects(TestOrganization):
assert p.collaborationtype == 'cooperation'
#As the admin of the organization, reject pending invitation
- r = self.app.post('/organization/%s/update_collaboration_status' % self.name,
+ r = self.app.post('%sadmin/organizationprofile/update_collaboration_status' % self.org.url(),
params=dict(
collaborationid = str(p._id),
collaborationtype = 'cooperation',
@@ -438,7 +373,7 @@ class TestOrganizationProjects(TestOrganization):
p = send_invitation(self)
#As the admin of the organization, accept pending invitation
- r = self.app.post('/organization/%s/update_collaboration_status' % self.name,
+ r = self.app.post('%sadmin/organizationprofile/update_collaboration_status' % self.org.url(),
params=dict(
collaborationid = str(p._id),
collaborationtype = 'cooperation',
@@ -449,7 +384,7 @@ class TestOrganizationProjects(TestOrganization):
assert p.collaborationtype == 'cooperation'
#As the admin of the organization, close the collaboration
- r = self.app.post('/organization/%s/update_collaboration_status' % self.name,
+ r = self.app.post('%sadmin/organizationprofile/update_collaboration_status' % self.org.url(),
params=dict(
collaborationid = str(p._id),
collaborationtype = 'cooperation',
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/c4e7da6e/ForgeOrganization/forgeorganization/tool/controller/organizationtool.py
----------------------------------------------------------------------
diff --git a/ForgeOrganization/forgeorganization/tool/controller/organizationtool.py b/ForgeOrganization/forgeorganization/tool/controller/organizationtool.py
index 078ed72..af64a81 100644
--- a/ForgeOrganization/forgeorganization/tool/controller/organizationtool.py
+++ b/ForgeOrganization/forgeorganization/tool/controller/organizationtool.py
@@ -33,8 +33,8 @@ class OrganizationToolController(BaseController):
if el.collaborationtype=='cooperation']
participations=[el for el in c.project.organizations
if el.collaborationtype=='participation']
- user_organizations=[o.organization for o in c.user.memberships
- if o.membertype == 'admin']
+ user_organizations=[o for o in Organization.query.find()
+ if c.user.username in o.project().admins()]
return dict(
user_organizations=user_organizations,
cooperations=cooperations,
@@ -125,7 +125,7 @@ class OrganizationToolController(BaseController):
def send_request(self, organization, coll_type, **kw):
organization = Organization.getById(organization)
- if not organization or organization.userPermissions(c.user)!='admin':
+ if (not organization) or not (c.user.username in organization.project().admins()):
flash("You are not allowed to perform this action.", "error")
redirect(".")
return
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/c4e7da6e/ForgeOrganization/setup.py
----------------------------------------------------------------------
diff --git a/ForgeOrganization/setup.py b/ForgeOrganization/setup.py
index 5330aa1..e7389d6 100644
--- a/ForgeOrganization/setup.py
+++ b/ForgeOrganization/setup.py
@@ -27,7 +27,7 @@ setup(name='ForgeOrganization',
organization=forgeorganization.organization.main:ForgeOrganizationApp
[allura]
+ organizationprofile = forgeorganization.organization_profile.organization_main:OrganizationProfileApp
organizationstool=forgeorganization.tool.main:ForgeOrganizationToolApp
-
""",
)