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/04/04 00:22:26 UTC

[4/5] git commit: organization and organization stats

organization and organization stats

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/787ef235
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/787ef235
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/787ef235

Branch: refs/heads/si/5566
Commit: 787ef235fe79b8bf0f51feb83ea92cfaf0a45b30
Parents: 2b3bad7
Author: Simone Gatti <si...@gmail.com>
Authored: Wed Apr 3 23:47:41 2013 +0200
Committer: Stefano Invernizzi <st...@apache.org>
Committed: Wed Apr 3 23:49:53 2013 +0200

----------------------------------------------------------------------
 Allura/allura/controllers/basetest_project_root.py |    4 +-
 Allura/allura/controllers/root.py                  |    4 +
 .../ext/user_profile/templates/user_index.html     |   22 +
 Allura/allura/lib/app_globals.py                   |    2 +
 Allura/allura/model/auth.py                        |   12 +
 Allura/allura/model/project.py                     |   19 +
 Allura/allura/nf/allura/css/allura.css             |   31 +-
 .../templates/jinja_master/theme_macros.html       |    5 +-
 Allura/allura/websetup/bootstrap.py                |   16 +
 Allura/development.ini                             |    1 +
 ForgeOrganization/forgeorganization/__init__.py    |    4 +
 .../organization/controller/organization.py        |  117 ++++
 .../forgeorganization/organization/main.py         |   44 ++
 .../organization/model/__init__.py                 |    1 +
 .../organization/model/organization.py             |  268 ++++++++
 .../organization/templates/register.html           |   25 +
 .../organization/templates/search_results.html     |   34 +
 .../organization/templates/user_memberships.html   |   51 ++
 .../organization/widgets/forms.py                  |  403 +++++++++++
 .../organization_profile/__init__.py               |    1 +
 .../organization_profile/organization_main.py      |  293 ++++++++
 .../templates/edit_profile.html                    |  111 +++
 .../templates/organization_index.html              |  206 ++++++
 .../forgeorganization/tests/test_organizations.py  |  396 +++++++++++
 .../forgeorganization/tool/controller/__init__.py  |    1 +
 .../tool/controller/organizationtool.py            |  144 ++++
 ForgeOrganization/forgeorganization/tool/main.py   |   91 +++
 .../forgeorganization/tool/templates/index.html    |  114 +++
 .../tool/templates/search_results.html             |   37 +
 .../forgeorganization/tool/widgets/forms.py        |  152 ++++
 ForgeOrganization/forgeorganization/version.py     |    2 +
 ForgeOrganization/setup.py                         |   33 +
 ForgeOrganization/test.ini                         |   57 ++
 ForgeOrganizationStats/.svn/all-wcprops            |   11 +
 ForgeOrganizationStats/.svn/entries                |   65 ++
 .../.svn/text-base/setup.py.svn-base               |   29 +
 .../forgeorganizationstats/.svn/all-wcprops        |   23 +
 .../forgeorganizationstats/.svn/entries            |  136 ++++
 .../.svn/text-base/__init__.py.svn-base            |    1 +
 .../.svn/text-base/main.py.svn-base                |  227 ++++++
 .../.svn/text-base/version.py.svn-base             |    2 +
 .../controllers/organizationstats.py               |  311 +++++++++
 .../forgeorganizationstats/main.py                 |  145 ++++
 .../forgeorganizationstats/model/.svn/all-wcprops  |   17 +
 .../forgeorganizationstats/model/.svn/entries      |   96 +++
 .../model/.svn/text-base/stats.py.svn-base         |  534 +++++++++++++++
 .../forgeorganizationstats/model/__init__.py       |    1 +
 .../forgeorganizationstats/model/orgstats.py       |   62 ++
 .../templates/.svn/all-wcprops                     |   29 +
 .../forgeorganizationstats/templates/.svn/entries  |  164 +++++
 .../.svn/text-base/artifacts.html.svn-base         |   48 ++
 .../templates/.svn/text-base/commits.html.svn-base |   37 +
 .../templates/.svn/text-base/index.html.svn-base   |  341 +++++++++
 .../templates/.svn/text-base/tickets.html.svn-base |   47 ++
 .../templates/artifacts.html                       |   67 ++
 .../forgeorganizationstats/templates/commits.html  |   56 ++
 .../forgeorganizationstats/templates/index.html    |  509 ++++++++++++++
 .../forgeorganizationstats/templates/settings.html |   19 +
 .../forgeorganizationstats/templates/tickets.html  |   66 ++
 .../forgeorganizationstats/tests/test_model.py     |  396 +++++++++++
 .../forgeorganizationstats/tests/test_stats.py     |  283 ++++++++
 .../forgeorganizationstats/version.py              |    2 +
 .../forgeorganizationstats/widgets/forms.py        |   22 +
 ForgeOrganizationStats/setup.py                    |   32 +
 ForgeOrganizationStats/test.ini                    |   56 ++
 65 files changed, 6530 insertions(+), 5 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/787ef235/Allura/allura/controllers/basetest_project_root.py
----------------------------------------------------------------------
diff --git a/Allura/allura/controllers/basetest_project_root.py b/Allura/allura/controllers/basetest_project_root.py
index 5626019..4a70465 100644
--- a/Allura/allura/controllers/basetest_project_root.py
+++ b/Allura/allura/controllers/basetest_project_root.py
@@ -6,7 +6,7 @@ from urllib import unquote
 
 import pkg_resources
 from pylons import tmpl_context as c
-from pylons import request, response
+from pylons import request, response, g
 from webob import exc
 from tg import expose
 from tg.decorators import without_trailing_slash
@@ -59,6 +59,8 @@ class BasetestProjectRootController(WsgiDispatchController, ProjectController):
         self.security = SecurityTests()
         for attr in ('index', 'browse', 'auth', 'nf', 'error'):
             setattr(self, attr, getattr(proxy_root, attr))
+        if g.show_organizations:
+            self.organization = proxy_root.organization
         self.gsearch = proxy_root.search
         self.rest = RestController()
         super(BasetestProjectRootController, self).__init__()

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/787ef235/Allura/allura/controllers/root.py
----------------------------------------------------------------------
diff --git a/Allura/allura/controllers/root.py b/Allura/allura/controllers/root.py
index 83aa5e5..d286437 100644
--- a/Allura/allura/controllers/root.py
+++ b/Allura/allura/controllers/root.py
@@ -70,6 +70,10 @@ class RootController(WsgiDispatchController):
             n.bind_controller(self)
         self.browse = ProjectBrowseController()
 
+        if g.show_organizations:
+            ep = g.entry_points["organizations"].get('organization')
+            self.organization = ep().root
+
         super(RootController, self).__init__()
 
     def _setup_request(self):

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/787ef235/Allura/allura/ext/user_profile/templates/user_index.html
----------------------------------------------------------------------
diff --git a/Allura/allura/ext/user_profile/templates/user_index.html b/Allura/allura/ext/user_profile/templates/user_index.html
index 2614953..4e0856e 100644
--- a/Allura/allura/ext/user_profile/templates/user_index.html
+++ b/Allura/allura/ext/user_profile/templates/user_index.html
@@ -236,6 +236,28 @@
     </div>
   </div>
 
+  {% if user.get_current_organizations() or user.get_past_organizations() %}
+    <div class="grid-24">
+      <div class="grid-24" style="margin:0;"><b>Organizations</b></div>
+      <div class="grid-24" style="margin-top:5px;margin-bottom:5px;">
+        <ul>
+          {% for org in user.get_current_organizations() %}
+            <li>
+              {{org.role.capitalize()}} at <a href="{{org.organization.url()}}">{{org.organization.fullname}}</a> 
+              from {{org.startdate.strftime('%d %B %Y')}}
+            </li>
+          {% endfor %}
+          {% for org in user.get_past_organizations() %}
+            <li>
+              {{org.role.capitalize()}} at <a href="{{org.organization.url()}}">{{org.organization.fullname}}</a> 
+              from {{org.startdate.strftime('%d %B %Y')}} to {{org.closeddate.strftime('%d %B %Y')}}
+            </li>
+          {% endfor %}
+        <ul>
+      </div>
+    </div>
+  {%endif%}
+
   {% if c.user.username == user.username %}
       <div class="address-list grid-18">
         <b>Email Addresses</b>

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/787ef235/Allura/allura/lib/app_globals.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/app_globals.py b/Allura/allura/lib/app_globals.py
index 1ab9b31..4faebcc 100644
--- a/Allura/allura/lib/app_globals.py
+++ b/Allura/allura/lib/app_globals.py
@@ -166,12 +166,14 @@ class Globals(object):
             theme=_cache_eps('allura.theme'),
             user_prefs=_cache_eps('allura.user_prefs'),
             spam=_cache_eps('allura.spam'),
+            organizations=_cache_eps('allura.organization'),
             stats=_cache_eps('allura.stats'),
             )
 
         # Zarkov logger
         self._zarkov = None
 
+        self.show_organizations = 'organization' in self.entry_points['organizations']
         # Set listeners to update stats
         statslisteners = []
         for name, ep in self.entry_points['stats'].iteritems():

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/787ef235/Allura/allura/model/auth.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/auth.py b/Allura/allura/model/auth.py
index a0bdb20..0769fd7 100644
--- a/Allura/allura/model/auth.py
+++ b/Allura/allura/model/auth.py
@@ -332,6 +332,8 @@ class User(MappedClass, ActivityNode, ActivityObject):
         level = S.OneOf('low', 'high', 'medium'),
         comment=str)])
 
+    #Organizations
+    memberships = RelationProperty('Membership')
     #Statistics
     stats_id = FieldProperty(S.ObjectId, if_missing=None)
 
@@ -355,6 +357,16 @@ class User(MappedClass, ActivityNode, ActivityObject):
     def set_pref(self, pref_name, pref_value):
         return plugin.UserPreferencesProvider.get().set_pref(self, pref_name, pref_value)
 
+    def get_current_organizations(self):
+        if hasattr(self, 'memberships'):
+            return [m for m in self.memberships if m.status=='active']
+        return []
+
+    def get_past_organizations(self):
+        if hasattr(self, 'memberships'):
+            return [m for m in self.memberships if m.status=='closed']
+        return []
+
     def add_socialnetwork(self, socialnetwork, accounturl):
         if socialnetwork == 'Twitter' and not accounturl.startswith('http'):
             accounturl = 'http://twitter.com/%s' % accounturl.replace('@', '')

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/787ef235/Allura/allura/model/project.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/project.py b/Allura/allura/model/project.py
index 4272345..4ad89ff 100644
--- a/Allura/allura/model/project.py
+++ b/Allura/allura/model/project.py
@@ -166,6 +166,8 @@ class Project(MappedClass, ActivityNode, ActivityObject):
     tracking_id = FieldProperty(str, if_missing='')
     is_nbhd_project=FieldProperty(bool, if_missing=False)
 
+    organizations=RelationProperty('ProjectInvolvement')
+
     # transient properties
     notifications_disabled = False
 
@@ -288,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):
         '''
@@ -299,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
@@ -705,6 +722,8 @@ class Project(MappedClass, ActivityNode, ActivityObject):
                 apps = [('admin', 'admin', 'Admin'),
                         ('search', 'search', 'Search'),
                         ('activity', 'activity', 'Activity')]
+        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/787ef235/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 613caa7..fd0f0da 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;
@@ -60,7 +65,7 @@ b.ico.ico-vote-down { background-image: url('../images/vote_down.png'); }
   background-repeat: no-repeat;
 }
 
-.ui-icon-tool-userstats {
+.ui-icon-tool-userstats, .ui-icon-tool-organizationstats{
   background-image: url("../images/stats_24.png");
   background-repeat: no-repeat;
 }
@@ -96,6 +101,11 @@ b.ico.ico-vote-down { background-image: url('../images/vote_down.png'); }
   background-repeat: no-repeat;
 }
 
+.ui-icon-tool-organizationstool {
+  background-image: url("../images/org_24.png");
+  background-repeat: no-repeat;
+}
+
 .ui-icon-tool-chat {
   background-image: url("../images/chat_24.png");
   background-repeat: no-repeat;
@@ -120,10 +130,15 @@ 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");
 }
-#top_nav .ui-icon-tool-userstats {
+#top_nav .ui-icon-tool-userstats, .ui-icon-tool-organizationstats {
   background-image: url("../images/stats_32.png");
 }
 
@@ -149,9 +164,19 @@ b.ico.ico-vote-down { background-image: url('../images/vote_down.png'); }
   background-image: url("../images/chat_32.png");
 }
 
+#top_nav .ui-icon-tool-organizationstool {
+  background-image: url("../images/org_32.png");
+}
+.big_icon.ui-icon-tool-organizationstool {
+  background-image: url("../images/org_48.png");
+}
+
 .big_icon.ui-icon-tool-home, .big_icon.ui-icon-tool-profile {
   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");
 }
@@ -161,7 +186,7 @@ b.ico.ico-vote-down { background-image: url('../images/vote_down.png'); }
 .big_icon.ui-icon-tool-stats {
   background-image: url("../images/stats_48.png");
 }
-.big_icon.ui-icon-tool-userstats {
+.big_icon.ui-icon-tool-userstats, .big_icon.ui-icon-tool-organizationstats {
   background-image: url("../images/stats_48.png");
 }
 

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/787ef235/Allura/allura/templates/jinja_master/theme_macros.html
----------------------------------------------------------------------
diff --git a/Allura/allura/templates/jinja_master/theme_macros.html b/Allura/allura/templates/jinja_master/theme_macros.html
index d0d28ed..0a27187 100644
--- a/Allura/allura/templates/jinja_master/theme_macros.html
+++ b/Allura/allura/templates/jinja_master/theme_macros.html
@@ -3,7 +3,10 @@
     <div class="wrapper">
         <nav>
           {% if c.user._id %}
-            <a href="/auth/preferences/">Account</a>
+            {%if g.show_organizations %}
+              <a href="/organization/">Organizations</a>
+            {% endif %}
+            <a href="/auth/prefs/">Account</a>
             <a href="{{c.user.url()}}">{{name}}</a>
             <a href="{{logout_url}}">Log Out</a>
           {% else %}

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/787ef235/Allura/allura/websetup/bootstrap.py
----------------------------------------------------------------------
diff --git a/Allura/allura/websetup/bootstrap.py b/Allura/allura/websetup/bootstrap.py
index 90c8e62..1809fef 100644
--- a/Allura/allura/websetup/bootstrap.py
+++ b/Allura/allura/websetup/bootstrap.py
@@ -6,6 +6,7 @@ import logging
 import shutil
 from collections import defaultdict
 from datetime import datetime
+import pkg_resources
 
 import tg
 from pylons import tmpl_context as c, app_globals as g
@@ -82,6 +83,7 @@ def bootstrap(command, conf, vars):
     root = create_user('Root', make_project=False)
 
     n_projects = M.Neighborhood(name='Projects', url_prefix='/p/',
+                             anchored_tools='admin:Admin,organizationstool:Organizations',
                                 features=dict(private_projects = True,
                                               max_projects = None,
                                               css = 'none',
@@ -93,7 +95,15 @@ 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/',
+                             anchored_tools='organizationprofile:Profile,organizationstats:Statistics',
+                             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/',
+                             anchored_tools='admin:Admin,organizationstool:Organizations',
                              features=dict(private_projects = True,
                                            max_projects = None,
                                            css = 'custom',
@@ -103,6 +113,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()
 
@@ -176,6 +187,11 @@ def bootstrap(command, conf, vars):
     ThreadLocalORMSession.flush_all()
     ThreadLocalORMSession.close_all()
 
+    ep = pkg_resources.get_entry_info(
+        'forgeorganization', 'allura.organization', 'organization')
+    if ep is not None: 
+        ep.load().bootstrap()
+
 def wipe_database():
     conn = M.main_doc_session.bind.conn
     create_trove_categories = CreateTroveCategoriesCommand('create_trove_categories')

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/787ef235/Allura/development.ini
----------------------------------------------------------------------
diff --git a/Allura/development.ini b/Allura/development.ini
index 3dc76d0..b5dbbda 100644
--- a/Allura/development.ini
+++ b/Allura/development.ini
@@ -125,6 +125,7 @@ scm.repos.tarball.root = /usr/share/nginx/www/
 scm.repos.tarball.url_prefix = http://localhost/
 
 trovecategories.enableediting = true
+organizations.enable = true
 
 # ActivityStream
 activitystream.master = mongodb://127.0.0.1:27017

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/787ef235/ForgeOrganization/forgeorganization/__init__.py
----------------------------------------------------------------------
diff --git a/ForgeOrganization/forgeorganization/__init__.py b/ForgeOrganization/forgeorganization/__init__.py
new file mode 100644
index 0000000..c10c2db
--- /dev/null
+++ b/ForgeOrganization/forgeorganization/__init__.py
@@ -0,0 +1,4 @@
+import organization
+import tool
+from organization.main import ForgeOrganizationApp
+from tool.main import ForgeOrganizationToolApp

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/787ef235/ForgeOrganization/forgeorganization/organization/__init__.py
----------------------------------------------------------------------
diff --git a/ForgeOrganization/forgeorganization/organization/__init__.py b/ForgeOrganization/forgeorganization/organization/__init__.py
new file mode 100644
index 0000000..e69de29

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/787ef235/ForgeOrganization/forgeorganization/organization/controller/__init__.py
----------------------------------------------------------------------
diff --git a/ForgeOrganization/forgeorganization/organization/controller/__init__.py b/ForgeOrganization/forgeorganization/organization/controller/__init__.py
new file mode 100644
index 0000000..e69de29

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/787ef235/ForgeOrganization/forgeorganization/organization/controller/organization.py
----------------------------------------------------------------------
diff --git a/ForgeOrganization/forgeorganization/organization/controller/organization.py b/ForgeOrganization/forgeorganization/organization/controller/organization.py
new file mode 100644
index 0000000..1e2bada
--- /dev/null
+++ b/ForgeOrganization/forgeorganization/organization/controller/organization.py
@@ -0,0 +1,117 @@
+from tg import expose, flash, redirect, validate
+from tg.decorators import with_trailing_slash
+import pylons
+c = pylons.c = pylons.tmpl_context
+g = pylons.g = pylons.app_globals
+import re
+
+from allura.lib import validators as V
+from allura.lib.security import require_authenticated
+from allura.lib.decorators import require_post
+import allura.model as M
+from allura.controllers import BaseController
+import forgeorganization.organization.widgets.forms as forms
+from forgeorganization.organization.model import Organization, Membership
+from forgeorganization.organization.model import WorkFields, ProjectInvolvement
+from datetime import datetime
+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')
+    admission_request_form = forms.RequestAdmissionForm()
+    request_collaboration_form = forms.RequestCollaborationForm()
+
+    def new_change_collaboration_status(self):
+        return forms.ChangeCollaborationStatusForm()
+
+    def new_change_membership_from_user_form(self):
+        return forms.ChangeMembershipFromUser()
+    
+    def new_change_membership_from_organization(self):
+        return forms.ChangeMembershipFromOrganization()
+
+F = Forms()
+
+class OrganizationController(object):
+    @expose('jinja:forgeorganization:organization/templates/user_memberships.html')
+    def index(self, **kw):
+        require_authenticated()
+
+        return dict(
+            memberships=[m for m in c.user.memberships if m.status!='closed'],
+            forms=F)
+
+    @expose('jinja:forgeorganization:organization/templates/search_results.html')
+    @require_post()
+    @validate(F.search_form, error_handler=index)
+    def search(self, orgname, **kw):
+        regx = re.compile(orgname, re.IGNORECASE)
+        orgs = Organization.query.find(dict(fullname=regx))
+        return dict(
+            orglist = orgs, 
+            forms = F, 
+            search_string = orgname)
+
+    @expose('jinja:forgeorganization:organization/templates/register.html')
+    def register(self, **kw):
+        require_authenticated()
+        return dict(forms=F)
+
+    @expose()
+    @require_post()
+    @validate(F.registration_form, error_handler=register)
+    def save_new(self, fullname, shortname, orgtype, role, **kw):
+        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(role, 'active', o._id, c.user._id)
+        flash('Organization "%s" correctly created!' % fullname)
+        redirect('%sadmin/organizationprofile' % o.url())
+
+    @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 = '/organization'
+
+        if c.user != memb.member:
+            flash(
+                "You don't have the permission to perform this action.", 
+                "error")
+            redirect(return_url)
+
+        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=='request' 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)

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/787ef235/ForgeOrganization/forgeorganization/organization/main.py
----------------------------------------------------------------------
diff --git a/ForgeOrganization/forgeorganization/organization/main.py b/ForgeOrganization/forgeorganization/organization/main.py
new file mode 100644
index 0000000..94c561e
--- /dev/null
+++ b/ForgeOrganization/forgeorganization/organization/main.py
@@ -0,0 +1,44 @@
+import logging
+import allura.model as M
+
+from model.organization import WorkFields
+from controller.organization import OrganizationController
+
+log = logging.getLogger(__name__)
+
+class ForgeOrganizationApp:
+    root = OrganizationController()
+    
+    @classmethod
+    def bootstrap(self) :
+        conn = M.main_doc_session.bind.conn
+        if 'allura' in conn.database_names():
+            db = conn['allura']
+            if 'work_fields' in db.collection_names():
+                log.info('Dropping collection allura.work_fields')
+                db.drop_collection('work_fields')
+
+        l = [
+            ('Home & Entertainment',
+             'Applications designed primarily for use in or for the home, '+\
+             'or for entertainment.'),
+            ('Content & Communication',
+             'Office productivity suites, multimedia players, file viewers, '+\
+             'Web browsers, collaboration tools, ...'),
+            ('Education & Reference',
+             'Educational software, learning support tools, ...'),
+            ('Operations & Professionals',
+             'ERPs, CRMs, SCMs, applications for specific business uses, ...'),
+            ('Product manufacturing and service delivery',
+             'Software to support specific product manufacturing and '+\
+             'service delivery'),
+            ('Platform & Management',
+             'Operating systems, security, infrastructure services, ' + \
+             'hardware components controllers, ...'),
+            ('Mobile apps',
+             'Applications for mobile devices, such as telephones, PDAs, ...'),
+            ('Web applications','Applications available on the web')]
+
+        for (n, d) in l: 
+            log.info('Added work field %s.' % n)
+            WorkFields.insert(n,d)

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/787ef235/ForgeOrganization/forgeorganization/organization/model/__init__.py
----------------------------------------------------------------------
diff --git a/ForgeOrganization/forgeorganization/organization/model/__init__.py b/ForgeOrganization/forgeorganization/organization/model/__init__.py
new file mode 100644
index 0000000..6cf34c0
--- /dev/null
+++ b/ForgeOrganization/forgeorganization/organization/model/__init__.py
@@ -0,0 +1 @@
+from organization import Organization, Membership, WorkFields, ProjectInvolvement

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/787ef235/ForgeOrganization/forgeorganization/organization/model/organization.py
----------------------------------------------------------------------
diff --git a/ForgeOrganization/forgeorganization/organization/model/organization.py b/ForgeOrganization/forgeorganization/organization/model/organization.py
new file mode 100644
index 0000000..32e6316
--- /dev/null
+++ b/ForgeOrganization/forgeorganization/organization/model/organization.py
@@ -0,0 +1,268 @@
+from datetime import datetime
+
+import iso8601
+import pymongo
+import pylons
+pylons.c = pylons.tmpl_context
+pylons.g = pylons.app_globals
+from pylons import c, g
+
+import bson
+from ming import schema as S
+from ming import Field, Index, collection
+from ming.orm import session, state, Mapper
+from ming.orm import FieldProperty, RelationProperty, ForeignIdProperty
+from ming.orm.declarative import MappedClass
+
+import allura.tasks.mail_tasks
+from allura.lib import helpers as h
+from allura.lib import plugin
+
+from allura.model.session import main_orm_session
+
+from allura.model import User
+import allura.model as M
+
+class Organization(MappedClass):
+    class __mongometa__:
+        name='organization'
+        session = main_orm_session
+        unique_indexes = [ 'shortname' ]
+
+    _id=FieldProperty(S.ObjectId)
+    shortname=FieldProperty(str)
+    fullname=FieldProperty(str)
+    organization_type=FieldProperty(S.OneOf(
+        'For-profit business',
+        'Foundation or other non-profit organization',
+        'Research and/or education institution'))
+    description=FieldProperty(str)
+    headquarters=FieldProperty(str)
+    dimension=FieldProperty(
+        S.OneOf('Small', 'Medium', 'Large', 'Unknown'),
+        if_missing = 'Unknown')
+    website=FieldProperty(str)
+    workfields=FieldProperty([S.ObjectId])
+    created=FieldProperty(S.DateTime, if_missing=datetime.utcnow())
+    
+    memberships=RelationProperty('Membership')
+    project_involvements=RelationProperty('ProjectInvolvement')
+
+    stats_id = FieldProperty(S.ObjectId, if_missing=None)
+
+    @property
+    def stats(self):
+        if 'organizationstats' in g.entry_points['stats']:
+            from forgeorganizationstats.model import OrganizationStats
+            return OrganizationStats.query.get(_id=self.stats_id)
+        else:
+            return None
+
+    def url(self):
+        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, user):
+        o=cls.query.get(shortname=shortname)
+        if o is not None: return None
+        try:
+            o = cls(
+                shortname=shortname, 
+                fullname=fullname,
+                organization_type=orgtype)
+            session(o).flush(o)
+        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)
+            g.statsUpdater.newOrganization(o)
+        return o
+
+    @classmethod
+    def delete(cls, o):
+        try:
+            session(o).expunge(o)
+        except:
+            return False
+        return True
+
+    @classmethod
+    def getById(cls, org_id):
+        org_id = str(org_id)
+        org_id = bson.ObjectId(org_id)
+        return cls.query.get(_id=org_id)
+
+    def getWorkfields(self):
+        l = []
+        for wf in self.workfields:
+            l.append(WorkFields.query.get(_id = wf))
+        return l
+
+    def addWorkField(self, workfield):
+        wfid = workfield._id
+        if not wfid in self.workfields:
+            self.workfields.append(wfid)
+           
+    def removeWorkField(self, workfield):
+        wfid = workfield._id
+        if wfid in self.workfields:
+            del self.workfields[self.workfields.index(wfid)]
+
+    def getActiveCooperations(self):
+        return [c for c in self.project_involvements if c.status=='active' and
+            c.collaborationtype == 'cooperation']
+
+    def getPastCooperations(self):
+        return [c for c in self.project_involvements if c.status=='closed' and 
+            c.collaborationtype == 'cooperation']
+
+    def getActiveParticipations(self):
+        return [c for c in self.project_involvements if c.status=='active' and 
+            c.collaborationtype == 'participation']
+
+    def getPastParticipations(self):
+        return [c for c in self.project_involvements if c.status=='closed' and 
+            c.collaborationtype == 'participation']
+
+    def getEnrolledUsers(self):
+        return [m for m in self.memberships if m.status=='active']
+
+class WorkFields(MappedClass):
+    class __mongometa__:
+        session = main_orm_session
+        name='work_fields'
+
+    _id=FieldProperty(S.ObjectId)
+    name=FieldProperty(str)
+    description=FieldProperty(str, if_missing='')
+
+    @classmethod
+    def insert(cls, name, description):
+        wf=cls.query.get(name=name)
+        if wf is not None: 
+            return None
+        try:
+            wf = cls(name=name, description=description)
+            session(wf).flush(wf)
+        except pymongo.errors.DuplicateKeyError:
+            session(wf).expunge(wf)
+            return None
+        return wf
+
+    @classmethod
+    def getById(cls, workfieldid):
+        workfieldid = str(workfieldid)
+        workfieldid = bson.ObjectId(workfieldid)
+        return cls.query.get(_id=workfieldid)
+
+class Membership(MappedClass):
+    class __mongometa__:
+        session = main_orm_session
+        name='organization_membership'
+
+    _id=FieldProperty(S.ObjectId)
+    status=FieldProperty(S.OneOf('active', 'closed', 'invitation', 'request'))
+    role=FieldProperty(str)
+    organization_id=ForeignIdProperty('Organization')
+    member_id=ForeignIdProperty('User')
+    startdate = FieldProperty(S.DateTime, if_missing=None)
+    closeddate = FieldProperty(S.DateTime, if_missing=None)
+
+    organization = RelationProperty('Organization')
+    member = RelationProperty('User')
+
+    @classmethod
+    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':
+                return None
+        try:
+            m = cls(
+                organization_id=organization_id, 
+                member_id=member_id,
+                role=role,
+                startdate=None,
+                status=status)
+            session(m).flush(m)
+        except pymongo.errors.DuplicateKeyError:
+            session(m).expunge(m)
+            m = cls.query.get(organization_id=organization_id, member_id=member_id)
+        if status == 'active':
+            m.startdate = datetime.utcnow()
+
+        return m
+    
+    @classmethod
+    def delete(cls, membership):
+        cls.query.remove(dict(_id=membership._id))
+        
+    @classmethod
+    def getById(cls, membershipid):
+        membershipid = str(membershipid)
+        membershipid = bson.ObjectId(membershipid)
+        return cls.query.get(_id=membershipid)
+
+    def setStatus(self, status):
+        if status=='active' and self.status!='active':
+            self.startdate = datetime.utcnow()
+        elif status=='closed':
+            self.closeddate = datetime.utcnow()
+        self.status = status
+
+class ProjectInvolvement(MappedClass):
+    class __mongometa__:
+        session = main_orm_session
+        name='project_involvement'
+
+    _id=FieldProperty(S.ObjectId)
+    status=FieldProperty(S.OneOf('active', 'closed', 'invitation', 'request'))
+    collaborationtype=FieldProperty(S.OneOf('cooperation', 'participation'))
+    organization_id=ForeignIdProperty('Organization')
+    project_id=ForeignIdProperty('Project')
+    startdate = FieldProperty(S.DateTime, if_missing=None)
+    closeddate = FieldProperty(S.DateTime, if_missing=None)
+
+    organization = RelationProperty('Organization')
+    project = RelationProperty('Project')
+    
+    @classmethod
+    def insert(cls, status, collaborationtype, organization_id, project_id):
+        p = cls.query.find(dict(
+            organization_id=organization_id, 
+            project_id=project_id))
+        for el in p:
+            if p.status != 'closed':
+                return None
+        try:
+            m = cls(organization_id=organization_id, project_id=project_id, status=status, collaborationtype=collaborationtype)
+            session(m).flush(m)
+        except pymongo.errors.DuplicateKeyError:
+            session(m).expunge(m)
+            m = cls.query.get(organization_id=organization_id, project_id=project_id)
+        return m
+    
+    @classmethod
+    def delete(cls, coll_id):
+        cls.query.remove(dict(_id=coll_id))
+
+    @classmethod
+    def getById(cls, p_id):
+        p_id = str(p_id)
+        p_id = bson.ObjectId(p_id)
+        return cls.query.get(_id=p_id)
+
+    def setStatus(self, status):
+        if status=='active' and self.status!='active':
+            self.startdate = datetime.utcnow()
+        elif status=='closed':
+            self.closeddate = datetime.utcnow()
+        self.status = status
+
+Mapper.compile_all()

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/787ef235/ForgeOrganization/forgeorganization/organization/templates/register.html
----------------------------------------------------------------------
diff --git a/ForgeOrganization/forgeorganization/organization/templates/register.html b/ForgeOrganization/forgeorganization/organization/templates/register.html
new file mode 100644
index 0000000..461f962
--- /dev/null
+++ b/ForgeOrganization/forgeorganization/organization/templates/register.html
@@ -0,0 +1,25 @@
+{% set hide_left_bar = True %}
+{% extends g.theme.master %}
+
+{% block title %}Create organization{% endblock %}
+
+{% block header %}Create organization{% endblock %}
+
+{% block content %}
+
+<div class="grid-20">
+  <h2>Create a new organization profile</h2>
+  <p>
+    If you want to create a new organization, fill the form below, then press "Save".
+    Please, note that "Your Role" should be filled with the role you have in the real life within the organization 
+    (for example, C.E.O., developer, ...).
+    In the field "Desired Short Name", put a valid unique name which will be used to distinguish your organization from
+    the other ones.
+  </p>
+
+  {{ forms.registration_form.display() }}
+</div>
+
+{% endblock %}
+
+

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/787ef235/ForgeOrganization/forgeorganization/organization/templates/search_results.html
----------------------------------------------------------------------
diff --git a/ForgeOrganization/forgeorganization/organization/templates/search_results.html b/ForgeOrganization/forgeorganization/organization/templates/search_results.html
new file mode 100644
index 0000000..59f33ce
--- /dev/null
+++ b/ForgeOrganization/forgeorganization/organization/templates/search_results.html
@@ -0,0 +1,34 @@
+{% set hide_left_bar = True %}
+{% extends g.theme.master %}
+
+{% block title %}Search results{% endblock %}
+
+{% block header %}Search results{% endblock %}
+
+{% block content %}
+
+  <div class="grid-20">
+    <h2>Results</h2>
+    {% if not orglist %}
+      <p>
+        Your entered the search string "{{search_string}}", but there isn't any organization
+        matching your query. Try to change your search string.
+      </p>
+    {% else %}
+      <p>
+        Your search for "{{search_string}}" produced {{orglist|length}} result{% if orglist|length != 1 %}s{% endif %}: 
+      </p>
+      <ul>
+        {% for o in orglist %}
+          <li><a href="{{o.url()}}">{{o.fullname}}</a></li>
+        {% endfor %}
+      </ul>
+    {% endif %}
+  </div>
+  <div class="grid-20">
+    <h2>Perform a new search</h2>
+    {{forms.search_form.display()}}
+  </div>
+
+{% endblock %}
+

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/787ef235/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
new file mode 100644
index 0000000..f130cea
--- /dev/null
+++ b/ForgeOrganization/forgeorganization/organization/templates/user_memberships.html
@@ -0,0 +1,51 @@
+{% set hide_left_bar = True %}
+{% extends g.theme.master %}
+{% block title %}{{c.user.display_name}}'s organizations{% endblock %}
+{% block header %}{{c.user.display_name}}'s organizations{% endblock %}
+{% block content %}
+ 
+  <div class="grid-20">
+    <h2>Your organizations</h2>
+    {% if memberships %}
+      <table>
+        <thead>
+          <tr>
+            <th>Name</th>
+            <th>Organization type</th>
+            <th>Role</th>
+            <th>Status</th>
+            <th>Actions</th>
+          </tr>
+        </thead>
+        <tbody>
+          {% for membership in memberships %}
+            {{forms.new_change_membership_from_user_form().display(
+                  membership=membership,
+                  action='organization/change_membership')}}
+          {% endfor %}
+        </tbody>
+      </table>
+    {% else %}
+      <p>
+        At the moment, it looks like you are not involved in any organization. 
+      </p>
+    {% endif %}
+  </div>
+
+  <div class="grid-20">
+    <h2>Add your enrollment with an organization</h2>
+    <p>If you are a member of an organization which is not included in your list, you can simply add it.</p>
+    <h3>Already existing organizations</h3>
+    <p>
+      If your organization already has a profile on the forge, you can search for it typing the organization's name 
+      in the form below. Then, you simply have to ask to be added to the member of the organization. 
+      {{forms.search_form.display()}}
+    </p>
+    <h3>New organizations</h3>
+    <p>
+      If your organization doesn't exist on the forge, you can create its profile. <a href="/organization/register">Click here</a> 
+      to do it.
+    </p>
+  </div>
+
+{% endblock %}

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/787ef235/ForgeOrganization/forgeorganization/organization/widgets/__init__.py
----------------------------------------------------------------------
diff --git a/ForgeOrganization/forgeorganization/organization/widgets/__init__.py b/ForgeOrganization/forgeorganization/organization/widgets/__init__.py
new file mode 100644
index 0000000..e69de29

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/787ef235/ForgeOrganization/forgeorganization/organization/widgets/forms.py
----------------------------------------------------------------------
diff --git a/ForgeOrganization/forgeorganization/organization/widgets/forms.py b/ForgeOrganization/forgeorganization/organization/widgets/forms.py
new file mode 100644
index 0000000..f0403e1
--- /dev/null
+++ b/ForgeOrganization/forgeorganization/organization/widgets/forms.py
@@ -0,0 +1,403 @@
+import logging
+import warnings
+from pylons import g, c
+from allura.lib import validators as V
+from allura.lib import helpers as h
+from allura.lib import plugin
+from allura.lib.widgets import form_fields as ffw
+from allura.lib.widgets.forms import ForgeForm
+from allura import model as M
+
+from formencode import validators as fev
+import formencode
+from forgeorganization.organization.model import WorkFields
+import ew as ew_core
+import ew.jinja2_ew as ew
+
+from pytz import common_timezones, country_timezones, country_names
+
+log = logging.getLogger(__name__)
+
+class RegistrationForm(ForgeForm):
+    class fields(ew_core.NameList):
+        fullname = ew.TextField(
+            label='Organization Full Name',
+            validator=fev.UnicodeString(not_empty=True))
+        shortname = ew.TextField(
+            label='Desired Short Name',
+            validator=formencode.All(
+                fev.Regex(h.re_path_portion),
+                fev.UnicodeString(not_empty=True)))
+        orgtype = ew.SingleSelectField(
+            label='Organization Type',
+            options = [
+                ew.Option(
+                    py_value='For-profit business', 
+                    label='For-profit business'),
+                ew.Option(
+                    py_value='Foundation or other non-profit organization',
+                    label='Foundation or other non-profit organization'),
+                ew.Option(
+                    py_value='Research and/or education institution',
+                    label='Research and/or education institution')],
+            validator=fev.UnicodeString(not_empty=True))
+        role = ew.TextField(
+            label='Your Role',
+            validator=fev.UnicodeString(not_empty=True))
+
+class SearchForm(ForgeForm):
+    defaults=dict(ForgeForm.defaults, submit_text='Search')
+
+    class fields(ew_core.NameList):
+        orgname = ew.TextField(
+            label='Organization name', 
+            validator=fev.UnicodeString(not_empty=True))
+
+class RequestAdmissionForm(ForgeForm):
+    defaults=dict(ForgeForm.defaults)
+
+    class fields(ew_core.NameList):
+        role = ew.TextField(
+            label='Your role', 
+            validator=fev.UnicodeString(not_empty=True))
+
+class RequestCollaborationForm(ForgeForm):
+    defaults=dict(ForgeForm.defaults)
+
+    class fields(ew_core.NameList):
+        project_url_name = ew.TextField(
+            label='Project URL Name', 
+            validator=fev.UnicodeString(not_empty=True))
+        collaboration_type=ew.SingleSelectField(
+            label='Collaboration Type', 
+            options = [
+                ew.Option(py_value='cooperation', label='Cooperation'),
+                ew.Option(py_value='participation', label='Participation')])
+
+class UpdateProfile(ForgeForm):
+    defaults=dict(ForgeForm.defaults)
+
+    class fields(ew_core.NameList):
+        fullname=ew.TextField(
+            label='Organization Full Name',
+            validator=fev.UnicodeString(not_empty=True))
+        organization_type=ew.SingleSelectField(
+            label='Organization Type', 
+            options = [
+                ew.Option(
+                    py_value='For-profit business', 
+                    label='For-profit business'),
+                ew.Option(
+                    py_value='Foundation or other non-profit organization',
+                    label='Foundation or other non-profit organization'),
+                ew.Option(
+                    py_value='Research and/or education institution',
+                    label='Research and/or education institution')],
+             validator=fev.UnicodeString(not_empty=True))
+        description=ew.TextField(
+            label='Description')
+        dimension=ew.SingleSelectField(
+            label='Dimension', 
+            options = [
+                ew.Option(
+                    py_value='Small', 
+                    label='Small organization (up to 50 members)'),
+                ew.Option(
+                    py_value='Medium',
+                    label='Medium-size organization (51-250 members)'),
+                ew.Option(
+                    py_value='Large',
+                    label='Big organization (at least 251 members)'),
+                ew.Option(
+                    py_value='Unknown',
+                    label='Unknown')],
+            validator=fev.UnicodeString(not_empty=True))
+        headquarters=ew.TextField(
+            label='Headquarters')
+        website=ew.TextField(
+            label='Website')
+
+    def display(self, **kw):
+        organization = kw.get('organization')
+        self.fields['fullname'].attrs = dict(value=organization.fullname)
+        self.fields['description'].attrs = dict(value=organization.description)
+        for opt in self.fields['organization_type'].options:
+            if opt.py_value == organization.organization_type:
+                opt.selected = True
+            else:
+                opt.selected = False
+        for opt in self.fields['dimension'].options:
+            if opt.py_value == organization.dimension:
+                opt.selected = True
+            else:
+                opt.selected = False
+        self.fields['website'].attrs = dict(value=organization.website)
+        self.fields['headquarters'].attrs = \
+            dict(value=organization.headquarters)
+
+        return super(UpdateProfile, self).display(**kw)
+
+class InviteUser(ForgeForm):
+    defaults=dict(ForgeForm.defaults)
+
+    class fields(ew_core.NameList):
+        username=ew.TextField(
+            label='Username',
+            validator=fev.UnicodeString(not_empty=True))
+        role=ew.TextField(
+            label='Role',
+            validator=fev.UnicodeString(not_empty=True))
+        
+class AddWorkField(ForgeForm):
+    defaults=dict(ForgeForm.defaults)
+
+    def display(self, **kw):
+        self.fields = [
+            ew.SingleSelectField(
+                name='workfield',
+                label='Work Field',
+                options = [ew.Option(py_value=wf._id, label=wf.name)
+                           for wf in WorkFields.query.find()],
+                validator=fev.UnicodeString(not_empty=True))]
+        return super(AddWorkField, self).display(**kw)
+
+
+    def to_python(self, value, state):
+        d = super(AddWorkField, self).to_python(value, state)
+        return d
+
+class RemoveWorkField(ForgeForm):
+    defaults=dict(ForgeForm.defaults, submit_text=None, show_errors=False)
+
+    def display(self, **kw):
+        wf = kw.get('workfield')
+
+        self.fields = [
+            ew.RowField(
+                show_errors=False,
+                hidden_fields=[
+                    ew.HiddenField(
+                        name="workfieldid",
+                        attrs={'value':str(wf._id)},
+                        show_errors=False)
+                ],
+                fields=[
+                    ew.HTMLField(
+                        text=wf.name,
+                        show_errors=False),
+                    ew.HTMLField(
+                        text=wf.description,
+                        show_errors=False),
+                    ew.SubmitButton(
+                        show_label=False,
+                        attrs={'value':'Remove'},
+                        show_errors=False)])]
+        return super(RemoveWorkField, self).display(**kw)
+
+    def to_python(self, value, state):
+        d = {}
+        d['workfield'] = WorkFields.getById(value['workfieldid'])
+        return d
+
+class ChangeMembershipFromUser(ForgeForm):
+    defaults=dict(ForgeForm.defaults, submit_text=None, show_errors=False)
+
+    def display(self, **kw):
+        m = kw.get('membership')
+        org = m.organization
+
+        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),
+                ew.Option(py_value='closed',label='Closed',selected=False)]
+        elif m.status == 'closed':
+            statusoptions = [
+                ew.Option(py_value='closed',label='Closed',selected=True)]
+        elif m.status == 'invitation':
+            statusoptions = [
+                ew.Option(
+                    py_value='invitation',
+                    label='Pending invitation',
+                    selected=True),
+                ew.Option(py_value='active',label='Accept',selected=False),
+                ew.Option(py_value='remove',label='Decline',selected=False)]
+        elif m.status == 'request':
+            statusoptions = [
+                ew.Option(
+                    py_value='request',label='Pending request',selected=True),
+                ew.Option(
+                    py_value='remove',label='Remove request',selected=False)]
+ 
+        self.fields = [
+            ew.RowField(
+                show_errors=False,
+                hidden_fields=[
+                    ew.HiddenField(
+                        name="membershipid",
+                        attrs={'value':str(m._id)},
+                        show_errors=False),
+                    ew.HiddenField(
+                        name="requestfrom",
+                        attrs={'value':'user'},
+                        show_errors=False)
+                ],
+                fields=[
+                    ew.HTMLField(
+                        text=orgnamefield,
+                        show_errors=False),
+                    ew.HTMLField(
+                        text=org.organization_type,
+                        show_errors=False),
+                    ew.TextField(
+                        name='role',
+                        attrs=dict(value=m.role),
+                        show_errors=False),
+                    ew.SingleSelectField(
+                        name='status',
+                        show_errors=False,
+                        options = statusoptions),
+                    ew.SubmitButton(
+                        show_label=False,
+                        attrs={'value':'Save'},
+                        show_errors=False)])]
+        return super(ChangeMembershipFromUser, self).display(**kw)
+
+class ChangeMembershipFromOrganization(ForgeForm):
+    defaults=dict(ForgeForm.defaults, submit_text=None, show_errors=False)
+
+    def display(self, **kw):
+        m = kw.get('membership')
+        user = m.member
+
+        if m.status == 'active':
+            statusoptions = [
+                ew.Option(py_value='active',label='Active',selected=True),
+                ew.Option(py_value='closed',label='Closed',selected=False)]
+        elif m.status == 'closed':
+            statusoptions = [
+                ew.Option(py_value='closed',label='Closed',selected=True)]
+        elif m.status == 'invitation':
+            statusoptions = [
+                ew.Option(
+                    py_value='invitation',
+                    label='Pending invitation',
+                    selected=True),
+                ew.Option(
+                    py_value='remove',
+                    label='Remove invitation',
+                    selected=False)]
+        elif m.status == 'request':
+            statusoptions = [
+                ew.Option(
+                    py_value='request',label='Pending request',selected=True),
+                ew.Option(py_value='active',label='Accept',selected=False),
+                ew.Option(py_value='remove',label='Decline',selected=False)]
+ 
+        self.fields = [
+            ew.RowField(
+                show_errors=False,
+                hidden_fields=[
+                    ew.HiddenField(
+                        name="membershipid",
+                        attrs={'value':str(m._id)},
+                        show_errors=False),
+                    ew.HiddenField(
+                        name="requestfrom",
+                        attrs={'value':'organization'},
+                        show_errors=False)
+                ],
+                fields=[
+                    ew.HTMLField(
+                        text='<a href="%s">%s</a>' % (
+                            user.url(), user.display_name),
+                        show_errors=False),
+                    ew.TextField(
+                        name='role',
+                        attrs=dict(value=m.role),
+                        show_errors=False),
+                    ew.SingleSelectField(
+                        name='status',
+                        show_errors=False,
+                        options = statusoptions),
+                    ew.SubmitButton(
+                        show_label=False,
+                        attrs={'value':'Save'},
+                        show_errors=False)])]
+        return super(ChangeMembershipFromOrganization, self).display(**kw)
+
+class ChangeCollaborationStatusForm(ForgeForm):
+    defaults=dict(ForgeForm.defaults, submit_text=None, show_errors=False)
+
+    def display(self, **kw):
+        coll = kw.get('collaboration')
+        proj = coll.project
+        projfield = '<a href="%s">%s</a>' % (proj.url(), proj.name)
+        
+        select_cooperation = (coll.collaborationtype=='cooperation')
+        if coll.status=='closed':
+            options=[ew.Option(py_value='closed',label='Closed',selected=True)]
+        elif coll.status=='active':
+            options=[
+                ew.Option(py_value='closed',label='Closed',selected=False),
+                ew.Option(py_value='active',label='Active',selected=True)]
+        elif coll.status=='invitation':
+            options=[
+                ew.Option(
+                    py_value='invitation',
+                    label='Pending invitation',
+                    selected=True),
+                ew.Option(
+                    py_value='active',
+                    label='Accept invitation',
+                    selected=False),
+                ew.Option(
+                    py_value='remove',
+                    label='Remove invitation',
+                    selected=False)]
+        elif coll.status=='request':
+            options=[
+                ew.Option(
+                    py_value='request',
+                    label='Pending request',
+                    selected=True),
+                ew.Option(
+                    py_value='remove',
+                    label='Remove request',
+                    selected=False)]
+        self.fields = [
+            ew.RowField(
+                show_errors=False,
+                hidden_fields=[
+                    ew.HiddenField(
+                        name="collaborationid",
+                        attrs={'value':str(coll._id)},
+                        show_errors=False)
+                ],
+                fields=[
+                    ew.HTMLField(
+                        text=projfield,
+                        show_errors=False),
+                    ew.SingleSelectField(
+                        name='collaborationtype',
+                        options = [
+                            ew.Option(
+                                py_value='cooperation', 
+                                selected=select_cooperation,
+                                label='Cooperation'),
+                            ew.Option(
+                                py_value='participation',
+                                selected=not select_cooperation,
+                                label='Participation')],
+                        validator=fev.UnicodeString(not_empty=True)),
+                    ew.SingleSelectField(
+                        name='status',
+                        options = options,
+                        validator=fev.UnicodeString(not_empty=True)),
+                    ew.SubmitButton(
+                        show_label=False,
+                        attrs={'value':'Save'},
+                        show_errors=False)])]
+        return super(ChangeCollaborationStatusForm, self).display(**kw)

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/787ef235/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/787ef235/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..bb29f6d
--- /dev/null
+++ b/ForgeOrganization/forgeorganization/organization_profile/organization_main.py
@@ -0,0 +1,293 @@
+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 DefaultAdminController
+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
+    tool_label = 'Profile'
+    permissions = ['configure', 'read', 'write',
+                    'unmoderated_post', 'post', 'moderate', 'admin']
+    config_options = Application.config_options
+    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)
+        role_admin = M.ProjectRole.by_name('Admin')._id
+        role_anon = M.ProjectRole.by_name('*anonymous')._id
+        self.config.acl = [
+            M.ACE.allow(role_anon, 'read'),
+            M.ACE.allow(role_admin, 'admin')]
+        self.root = OrganizationProfileController()
+        self.admin = OrganizationProfileAdminController(self)
+
+    @property
+    @h.exceptionless([], log)
+    def sitemap(self):
+        return [SitemapEntry('Profile', '.')]
+
+    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
+
+    def main_menu(self):
+        return [SitemapEntry('Profile', '.')]
+
+    def is_visible_to(self, user):
+        return True
+
+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(DefaultAdminController):
+    @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/787ef235/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/787ef235/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/787ef235/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/787ef235/ForgeOrganization/forgeorganization/tests/__init__.py
----------------------------------------------------------------------
diff --git a/ForgeOrganization/forgeorganization/tests/__init__.py b/ForgeOrganization/forgeorganization/tests/__init__.py
new file mode 100644
index 0000000..e69de29