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:25 UTC

[3/5] organization and organization stats

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/787ef235/ForgeOrganization/forgeorganization/tests/test_organizations.py
----------------------------------------------------------------------
diff --git a/ForgeOrganization/forgeorganization/tests/test_organizations.py b/ForgeOrganization/forgeorganization/tests/test_organizations.py
new file mode 100644
index 0000000..32bc944
--- /dev/null
+++ b/ForgeOrganization/forgeorganization/tests/test_organizations.py
@@ -0,0 +1,396 @@
+import pkg_resources
+import unittest
+
+from pylons import app_globals as g
+from pylons import tmpl_context as c
+from ming.orm.ormsession import ThreadLocalORMSession, session
+
+from alluratest.controller import TestController, setup_basic_test, setup_global_objects
+from allura.tests import decorators as td
+from allura.lib import helpers as h
+from allura.model import User
+from allura import model as M
+
+from forgegit.tests import with_git
+from forgeorganization.organization import model as OM
+import tg
+
+class TestOrganization(TestController):
+
+    def setUp(self):
+        setup_basic_test(config='test.ini')
+        setup_global_objects()
+        super(TestOrganization, self).setUp()
+
+        c.user = User.by_username('test-user-1')
+        #Create a test organization
+        self.name = 'testorg'
+        self.fullname = 'Test Organization'
+        self.role = 'Developer'
+        self.orgtype = 'Foundation or other non-profit organization'
+        r = self.app.post('/organization/save_new/', 
+            params=dict(
+                fullname=self.fullname,
+                shortname=self.name,
+                orgtype=self.orgtype,
+                role=self.role),
+            extra_environ=dict(username='test-user-1'))
+
+        self.org = OM.Organization.query.get(shortname=self.name)
+
+        #Add a new user
+        self.user2 = User.by_username('test-user-2')
+        r = self.app.post((self.org.url()+'admin/organizationprofile/invite_user'),
+            params=dict(
+                username = self.user2.username,
+                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)
+        r = self.app.post('/organization/change_membership',
+            params=dict(
+                status = 'active',
+                membershipid = str(m._id),
+                requestfrom = 'user',
+                role = 'Software Engineer'),
+            extra_environ=dict(username='test-user-2'))
+
+class TestOrganizationGeneral(TestOrganization):
+
+    @td.with_user_project('test-user-1')
+    def test_registration(self):
+        #Test organization registration
+        r = self.app.get('/organization/',
+            extra_environ=dict(username='test-user-1'))
+        assert 'Test Organization' in r
+        org = OM.Organization.query.get(shortname=self.name)
+        assert self.org.fullname == self.fullname
+        assert self.org.organization_type == self.orgtype
+
+        #Check that the user is the administrator of the org. profile
+        m = OM.Membership.query.get(
+            member_id=c.user._id,
+            organization_id=self.org._id)
+        assert c.user.username in org.project().admins()
+        assert m.status == 'active'
+        assert m.role == self.role
+
+    @td.with_user_project('test-user-1')
+    def test_update_profile(self):
+
+        fullname = 'New Full Name'
+        organization_type = 'For-profit business'
+        description = 'New test description'
+        dimension = 'Medium'
+        headquarters = 'Milan'
+        website = 'http://www.example.com'
+
+        #Update the profile of the organization
+        r = self.app.post('%sadmin/organizationprofile/change_data' % self.org.url(),
+            params = dict(
+                fullname = fullname,
+                organization_type = organization_type,
+                description = description,
+                dimension = dimension,
+                headquarters = headquarters,
+                website = website),
+            extra_environ=dict(username='test-user-1'))
+
+        ThreadLocalORMSession.flush_all()
+
+        r = self.app.get('%sorganizationprofile' % self.org.url())
+        assert organization_type in r
+        assert description in r
+        assert headquarters in r
+        assert fullname in r
+        assert website in r
+
+        self.org = OM.Organization.query.get(_id=self.org._id)
+
+        assert self.org.organization_type == organization_type
+        assert self.org.description == description
+        assert self.org.dimension == dimension
+        assert self.org.headquarters == headquarters
+        assert self.org.website == website
+        assert self.org.fullname == fullname
+
+        #Try to provide invalid parameters
+        r = self.app.post('%sadmin/organizationprofile/change_data' % self.org.url(),
+            params = dict(
+                fullname = 'a',
+                organization_type = 'Invalid type',
+                description = 'b',
+                dimension = 'Invalid dimension',
+                headquarters = 'c',
+                website = 'd'),
+            extra_environ=dict(username='test-user-1'))
+
+        ThreadLocalORMSession.flush_all()
+
+        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)
+        assert self.org.organization_type == organization_type
+        assert self.org.description == description
+        assert self.org.dimension == dimension
+        assert self.org.headquarters == headquarters
+        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')
+
+        #Add a workfield
+        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('%sorganizationprofile' % self.org.url())
+        
+        self.org = OM.Organization.query.get(_id=self.org._id)
+        assert len(self.org.getWorkfields()) == 1
+        assert self.org.getWorkfields()[0]._id == wf._id
+        assert wf.name in r
+        assert wf.description in r
+        
+        #Add a second workfield
+        wf2 = OM.WorkFields.query.get(name='Web applications')
+
+        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('%sorganizationprofile' % self.org.url())
+        
+        self.org = OM.Organization.query.get(_id=self.org._id)
+        assert len(self.org.getWorkfields()) == 2
+        assert wf2.name in r
+        assert wf2.description in r
+
+        #Remove a workfield
+        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('%sorganizationprofile' % self.org.url())
+        assert len(self.org.getWorkfields()) == 1
+        assert self.org.getWorkfields()[0]._id == wf2._id
+        assert wf.name not in r
+        assert wf.description not in r
+
+class TestOrganizationMembership(TestOrganization):
+
+    @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('%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('%sadmin/organizationprofile' % self.org.url(),
+            extra_environ=dict(username='test-user-1'))
+        assert user3.display_name in r
+
+        m = OM.Membership.query.get(
+            member_id=user3._id, 
+            organization_id=self.org._id)
+        assert m.status == 'invitation'
+        assert m.role == testrole
+
+        #Accept invitation
+        r = self.app.post('/organization/change_membership',
+            params=dict(
+                status = 'active',
+                membershipid = str(m._id),
+                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 == 'active'
+        assert m.role == testrole
+        
+    @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)
+
+        #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('%sadmin/organizationprofile/change_membership' % self.org.url(),
+            params=dict(
+                status = 'closed',
+                membershipid = str(m._id),
+                requestfrom = 'user',
+                role = self.role),
+            extra_environ=dict(username='test-user-1'))
+
+        m = OM.Membership.query.get(
+            member_id=c.user._id, 
+            organization_id=self.org._id)
+        assert m.status == 'closed'
+        assert m.role == self.role
+
+
+    @td.with_user_project('test-admin')
+    def test_send_request(self):
+        #Send an admission request from a new user
+        user3 = User.by_username('test-admin')
+        testrole = 'Software Engineer'
+
+        r = self.app.post('%sorganizationprofile/admission_request' % self.org.url(),
+            params=dict(
+                role = testrole),
+            extra_environ=dict(username='test-admin'))
+
+        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
+
+        m = OM.Membership.query.get(
+            member_id=user3._id, 
+            organization_id=self.org._id)
+        assert m.status == 'request'
+        assert m.role == testrole
+
+        #Accept request
+        r = self.app.post('%sadmin/organizationprofile/change_membership' % self.org.url(),
+            params=dict(
+                status = 'active',
+                membershipid = str(m._id),
+                requestfrom = 'user',
+                role = testrole),
+            extra_environ=dict(username='test-user-1'))
+
+        m = OM.Membership.query.get(
+            member_id=user3._id, 
+            organization_id=self.org._id)
+        assert m.status == 'active'
+        assert m.role == testrole
+
+#check projects
+class TestOrganizationProjects(TestOrganization):
+
+    @td.with_user_project('test-admin')
+    def test_request_collaboration(self):
+        def send_request(self):
+            #Try to send a request to participate to a project
+            #The user sending the request is the admin of the org's profile
+            r = self.app.post('/organizationstool/send_request',
+                params=dict(
+                    organization = str(self.org._id),
+                    coll_type = 'cooperation'),
+                extra_environ=dict(username='test-user-1'))
+            return OM.ProjectInvolvement.query.get(organization_id=self.org._id)
+       
+        p = send_request(self)
+        assert p.status == 'request'
+        assert p.collaborationtype == 'cooperation'
+
+        #As the admin of the project, reject pending request
+        r = self.app.post('/organizationstool/update_collaboration_status',
+            params=dict(
+                collaborationid = str(p._id),
+                collaborationtype = 'cooperation',
+                status = 'remove'),
+            extra_environ=dict(username='test-admin'))
+
+        p = OM.ProjectInvolvement.query.get(organization_id=self.org._id)        
+        assert p is None
+
+        p = send_request(self)
+
+        #As the admin of the project, accept pending request
+        r = self.app.post('/organizationstool/update_collaboration_status',
+            params=dict(
+                collaborationid = str(p._id),
+                collaborationtype = 'cooperation',
+                status = 'active'),
+            extra_environ=dict(username='test-admin'))
+        
+        assert p.status == 'active'
+        assert p.collaborationtype == 'cooperation'
+
+        #As the admin of the project, close the collaboration
+        r = self.app.post('/organizationstool/update_collaboration_status',
+            params=dict(
+                collaborationid = str(p._id),
+                collaborationtype = 'cooperation',
+                status = 'closed'),
+            extra_environ=dict(username='test-admin'))
+        
+        p = OM.ProjectInvolvement.query.get(organization_id=self.org._id)        
+        assert p.status == 'closed'
+        assert p.collaborationtype == 'cooperation'
+
+    @td.with_user_project('test-admin')
+    def test_invite_organization(self):
+        def send_invitation(self):
+            #Try to send an invitation to participate to a project
+            #The user sending the request is the admin of the project
+            r = self.app.post('/organizationstool/invite',
+                params=dict(
+                    organizationid = str(self.org._id),
+                    collaborationtype = 'cooperation'),
+                extra_environ=dict(username='test-admin'))
+            return OM.ProjectInvolvement.query.get(organization_id=self.org._id)
+       
+        p = send_invitation(self)
+        assert p.status == 'invitation'
+        assert p.collaborationtype == 'cooperation'
+
+        #As the admin of the organization, reject pending invitation
+        r = self.app.post('%sadmin/organizationprofile/update_collaboration_status' % self.org.url(),
+            params=dict(
+                collaborationid = str(p._id),
+                collaborationtype = 'cooperation',
+                status = 'remove'),
+            extra_environ=dict(username='test-user-1'))
+
+        p = OM.ProjectInvolvement.query.get(organization_id=self.org._id)        
+        assert p is None
+
+        p = send_invitation(self)
+
+        #As the admin of the organization, accept pending invitation
+        r = self.app.post('%sadmin/organizationprofile/update_collaboration_status' % self.org.url(),
+            params=dict(
+                collaborationid = str(p._id),
+                collaborationtype = 'cooperation',
+                status = 'active'),
+            extra_environ=dict(username='test-user-1'))
+        
+        assert p.status == 'active'
+        assert p.collaborationtype == 'cooperation'
+
+        #As the admin of the organization, close the collaboration
+        r = self.app.post('%sadmin/organizationprofile/update_collaboration_status' % self.org.url(),
+            params=dict(
+                collaborationid = str(p._id),
+                collaborationtype = 'cooperation',
+                status = 'closed'),
+            extra_environ=dict(username='test-user-1'))
+        
+        p = OM.ProjectInvolvement.query.get(organization_id=self.org._id)        
+        assert p.status == 'closed'
+        assert p.collaborationtype == 'cooperation'

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

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

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/787ef235/ForgeOrganization/forgeorganization/tool/controller/organizationtool.py
----------------------------------------------------------------------
diff --git a/ForgeOrganization/forgeorganization/tool/controller/organizationtool.py b/ForgeOrganization/forgeorganization/tool/controller/organizationtool.py
new file mode 100644
index 0000000..af64a81
--- /dev/null
+++ b/ForgeOrganization/forgeorganization/tool/controller/organizationtool.py
@@ -0,0 +1,144 @@
+from tg import expose, validate, redirect, flash
+from tg.decorators import with_trailing_slash
+from pylons import c
+from allura.lib import validators as V
+from allura.lib.decorators import require_post
+from allura import model as M
+from allura.lib.security import require_authenticated
+from allura.controllers import BaseController
+import forgeorganization.tool.widgets.forms as forms 
+from forgeorganization.organization.model import Organization
+from forgeorganization.organization.model import ProjectInvolvement
+import re
+from datetime import datetime
+
+class Forms(object):
+    search_form=forms.SearchOrganizationForm(action='search')
+    invite_form=forms.InviteOrganizationForm(action='invite')
+    collaboration_request_form=forms.SendCollaborationRequestForm(
+        action='send_request')
+    def new_change_status_form(self):
+        return forms.ChangeCollaborationStatusForm(
+            action='update_collaboration_status')
+
+F = Forms()
+
+class OrganizationToolController(BaseController):
+
+    @expose('jinja:forgeorganization:tool/templates/index.html')
+    @with_trailing_slash
+    def index(self, **kw):
+        is_admin=c.user.username in c.project.admins()
+        cooperations=[el for el in c.project.organizations
+                      if el.collaborationtype=='cooperation']
+        participations=[el for el in c.project.organizations
+                        if el.collaborationtype=='participation']
+        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, 
+            participations=participations,
+            is_admin=is_admin,
+            forms = F)
+
+    @expose('jinja:forgeorganization:tool/templates/search_results.html')
+    @require_post()
+    @with_trailing_slash
+    @validate(F.search_form, error_handler=index)
+    def search(self, organization, **kw):
+        regx = re.compile(organization, re.IGNORECASE)
+        orgs = Organization.query.find(dict(fullname=regx))
+        return dict(
+            orglist = orgs, 
+            forms = F,
+            search_string = organization)
+
+    @expose()
+    @require_post()
+    @validate(V.NullValidator(), error_handler=index)
+    def invite(self, organizationid, collaborationtype, **kw):
+        require_authenticated()
+        is_admin=c.user.username in c.project.admins()
+        if not is_admin:
+            flash("You are not allowed to perform this action", "error")
+            redirect(".")
+            return
+        org = Organization.getById(organizationid)
+        if not org:
+            flash("Invalid organization.", "error")
+            redirect(".")
+            return
+        result = ProjectInvolvement.insert(
+            status='invitation', 
+            collaborationtype=collaborationtype, 
+            organization_id=organizationid, 
+            project_id=c.project._id)
+        if not result:
+            flash("This organization is already involved in this project.",
+                  "error")
+        else:
+            flash("Invitation correctly sent.")
+        redirect(".")
+
+    @expose()
+    @require_post()
+    @validate(V.NullValidator(), error_handler=index)
+    def update_collaboration_status(self, collaborationid, collaborationtype, status, **kw):
+        require_authenticated()
+        is_admin=c.user.username in c.project.admins()
+        if not is_admin:
+            flash("You are not allowed to perform this action", "error")
+            redirect(".")
+            return
+
+        allowed = True
+        coll = ProjectInvolvement.getById(collaborationid)
+        if not coll:
+            allowed = False
+        if coll.status != status:
+            if coll.status=='invitation' and status!='remove':
+                allowed=False
+            elif coll.status=='closed':
+                allowed=False
+            elif coll.status=='active' and status!='closed':
+                allowed=False
+            elif coll.status=='request' and status not in ['active','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('.')
+
+    @expose()
+    @require_post()
+    @validate(V.NullValidator(), error_handler=index)
+    def send_request(self, organization, coll_type, **kw):
+        organization = Organization.getById(organization)     
+
+        if (not organization) or not (c.user.username in organization.project().admins()):
+            flash("You are not allowed to perform this action.", "error")
+            redirect(".")
+            return
+        
+        for org in c.project.organizations:
+            if org.organization == organization and org.status!='closed':
+                flash(
+                    "This organization is already included in this project.",
+                    "error")
+                redirect(".")
+                return
+        ProjectInvolvement.insert('request', coll_type, organization._id, 
+            c.project._id)
+        flash("Your collaboration request was successfully sent.")
+        redirect(".")
+        return

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/787ef235/ForgeOrganization/forgeorganization/tool/main.py
----------------------------------------------------------------------
diff --git a/ForgeOrganization/forgeorganization/tool/main.py b/ForgeOrganization/forgeorganization/tool/main.py
new file mode 100644
index 0000000..cf866ee
--- /dev/null
+++ b/ForgeOrganization/forgeorganization/tool/main.py
@@ -0,0 +1,91 @@
+#-*- python -*-
+import logging
+from pylons import c
+import formencode
+from formencode import validators
+from webob import exc
+
+from allura.app import Application, SitemapEntry
+from allura.lib import helpers as h
+from allura.lib.security import has_access
+from allura import model as M
+
+from forgeorganization import version
+from forgeorganization.tool.controller import OrganizationToolController
+
+from ming.orm import session
+
+log = logging.getLogger(__name__)
+
+class ForgeOrganizationToolApp(Application):
+    __version__ = version.__version__
+    tool_label='Organizations'
+    default_mount_label='Organizations'
+    default_mount_point='organizations'
+    permissions = ['configure', 'read', 'write',
+                    'unmoderated_post', 'post', 'moderate', 'admin']
+    ordinal=15
+    installable=False
+    config_options = Application.config_options
+    default_external_feeds = []
+    icons={
+        24:'images/org_24.png',
+        32:'images/org_32.png',
+        48:'images/org_48.png'
+    }
+    root = OrganizationToolController()
+
+    def __init__(self, project, config):
+        Application.__init__(self, project, 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')]
+
+    def main_menu(self):
+        return [SitemapEntry(self.config.options.mount_label.title(), '.')]
+
+    @property
+    @h.exceptionless([], log)
+    def sitemap(self):
+        menu_id = self.config.options.mount_label.title()
+        with h.push_config(c, app=self):
+            return [
+                SitemapEntry(menu_id, '.')[self.sidebar_menu()] ]
+
+    @property
+    def show_discussion(self):
+        if 'show_discussion' in self.config.options:
+            return self.config.options['show_discussion']
+        else:
+            return True
+
+    @h.exceptionless([], log)
+    def sidebar_menu(self):
+        base = c.app.url
+        links = [SitemapEntry('Home', base)]
+        return links
+
+    def admin_menu(self):
+        admin_url=c.project.url()+'admin/'+self.config.options.mount_point+'/'
+        links = [SitemapEntry(
+                     'Involved organizations',
+                     admin_url + 'edit_label', 
+                     className='admin_modal')]
+        return links
+
+    def install(self, project):
+        #It doesn't make any sense to install the tool twice on the same 
+        #project therefore, if it already exists, it doesn't install it
+        #a second time.
+        for tool in project.app_configs:
+            if tool.tool_name == 'organizationstool':
+                if self.config.options.mount_point!=tool.options.mount_point:
+                    project.uninstall_app(self.config.options.mount_point)
+                    return
+
+    def uninstall(self, project):
+        self.config.delete()
+        session(self.config).flush()
+

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/787ef235/ForgeOrganization/forgeorganization/tool/templates/index.html
----------------------------------------------------------------------
diff --git a/ForgeOrganization/forgeorganization/tool/templates/index.html b/ForgeOrganization/forgeorganization/tool/templates/index.html
new file mode 100644
index 0000000..8092a39
--- /dev/null
+++ b/ForgeOrganization/forgeorganization/tool/templates/index.html
@@ -0,0 +1,114 @@
+{% extends g.theme.master %}
+
+{% block title %}Organizations involved in {{c.project.name}}{% endblock %}
+
+{% block header %}Organizations involved in {{c.project.name}}{% endblock %}
+
+{% block content %}
+
+  <div class="grid-20">
+    <h2>Involved organizations</h2>
+  </div>
+
+  <div class="grid-20">
+    <h3>Organizations cooperating at the project as main partners</h3>
+    {% if cooperations %}
+      {% if is_admin %}
+        <table>
+          <thead>
+            <tr>
+              <th>Organization</th>
+              <th>Type</th>
+              <th>Status</th>
+              <th>Actions</th>
+            </tr>
+          </thead>
+          <tbody>
+            {% for oc in cooperations %}
+              <tr>{{forms.new_change_status_form().display(collaboration=oc)}}</tr>
+            {% endfor %}
+          </tbody>
+        </table>
+      {% else %}
+        <table>
+          <thead>
+            <tr>
+              <th>Organization</th>
+              <th>Status</th>
+            </tr>
+          </thead>
+          <tbody>
+            {% for oc in cooperations %}
+              <tr>
+                <td><a href="{{oc.organization.url()}}">{{oc.organization.fullname}}</a></td>
+                <td>{{oc.status.capitalize()}}</td>
+              </tr>
+            {% endfor %}
+          </tbody>
+        </table>
+      {% endif %}
+    {% else %}
+      <p>There are no organizations involved as main cooperators in this project.</p>
+    {% endif %}
+  </div>
+
+  <div class="grid-20">
+    <h3>Other organizations participating at the project</h3>
+    {% if participations %}
+      {% if is_admin %}
+        <table>
+          <thead>
+            <tr>
+              <th>Organization</th>
+              <th>Type</th>
+              <th>Status</th>
+              <th>Actions</th>
+            </tr>
+          </thead>
+          <tbody>
+            {% for oc in participations %}
+              <tr>{{forms.new_change_status_form().display(collaboration=oc)}}</tr>
+            {% endfor %}
+          </tbody>
+        </table>
+      {% else %}
+        <table>
+          <thead>
+            <tr>
+              <th>Organization</th>
+              <th>Status</th>
+            </tr>
+          </thead>
+          <tbody>
+            {% for oc in participations %}
+              <tr>
+                <td><a href="{{oc.organization.url()}}">{{oc.organization.fullname}}</a></td>
+                <td>{{oc.status.capitalize()}}</td>
+              </tr>
+            {% endfor %}
+          </tbody>
+        </table>
+      {% endif %}
+    {% else %}
+      <p>There are no organizations involved as participants in this project.</p>
+    {% endif %}
+  </div>
+
+  {% if is_admin %}
+    <div class="grid-20">
+      <h2>Invite an organization</h2>
+    </div>
+    <div class="grid-20">
+      {{forms.search_form.display()}}
+    </div>
+  {% else %}
+    {% if user_organizations %}
+      <div class="grid-20">
+        <h2>Send collaboration request</h2>
+      </div>
+      <div class="grid-20">
+        {{forms.collaboration_request_form.display(organizations=user_organizations)}}
+      </div>
+    {% endif %}
+  {% endif %}
+{% endblock %}

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/787ef235/ForgeOrganization/forgeorganization/tool/templates/search_results.html
----------------------------------------------------------------------
diff --git a/ForgeOrganization/forgeorganization/tool/templates/search_results.html b/ForgeOrganization/forgeorganization/tool/templates/search_results.html
new file mode 100644
index 0000000..4b4eee2
--- /dev/null
+++ b/ForgeOrganization/forgeorganization/tool/templates/search_results.html
@@ -0,0 +1,37 @@
+{% extends g.theme.master %}
+
+{% block title %}Invite an organization to collaborate to {{c.project.name}}{% endblock %}
+
+{% block header %}Invite an organization to collaborate to {{c.project.name}}{% endblock %}
+
+{% block content %}
+
+  <div class="grid-20">
+    <h2>Search results for {{search_string}}</h2>
+  </div>
+
+  <div class="grid-20">
+    {%if orglist%}
+      <table>
+        <thead>
+          <th>Organization</th>
+          <th>Collaboration type</th>
+          <th>Action</th>
+        </thead>
+        <tbody>
+          {%for org in orglist%}
+            {{forms.invite_form.display(organization=org)}}
+          {%endfor%}
+        </tbody>
+      </table>
+    {%else%}
+      <p>No result was found matching the string "{{search_string}}".</p>
+    {%endif%}
+  </div>
+
+  <div class="grid-20">
+    <h2>Search again</h2>
+    {{forms.search_form.display()}}
+  </div>
+  
+{% endblock %}

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

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/787ef235/ForgeOrganization/forgeorganization/tool/widgets/forms.py
----------------------------------------------------------------------
diff --git a/ForgeOrganization/forgeorganization/tool/widgets/forms.py b/ForgeOrganization/forgeorganization/tool/widgets/forms.py
new file mode 100644
index 0000000..6d50fea
--- /dev/null
+++ b/ForgeOrganization/forgeorganization/tool/widgets/forms.py
@@ -0,0 +1,152 @@
+from allura.lib import validators as V
+from allura.lib.widgets.forms import ForgeForm
+
+from formencode import validators as fev
+
+import ew as ew_core
+import ew.jinja2_ew as ew
+
+class SearchOrganizationForm(ForgeForm):
+    defaults=dict(ForgeForm.defaults, submit_text='Search')
+
+    class fields(ew_core.NameList):
+        organization=ew.TextField(
+            label='Organization',
+            validator=fev.UnicodeString(not_empty=True))
+
+class InviteOrganizationForm(ForgeForm):
+    defaults=dict(ForgeForm.defaults, submit_text=None, show_errors=False)
+
+    def display(self, **kw):
+        org = kw.get('organization')
+        orgnamefield = '<a href="%s">%s</a>' % (org.url(), org.fullname)
+
+        self.fields = [
+            ew.RowField(
+                show_errors=False,
+                hidden_fields=[
+                    ew.HiddenField(
+                        name="organizationid",
+                        attrs={'value':str(org._id)},
+                        show_errors=False)
+                ],
+                fields=[
+                    ew.HTMLField(
+                        text=orgnamefield,
+                        show_errors=False),
+                    ew.SingleSelectField(
+                        name='collaborationtype',
+                        options = [
+                            ew.Option(
+                                py_value='cooperation', 
+                                label='Cooperation'),
+                            ew.Option(
+                                py_value='participation',
+                                label='Participation')],
+                        validator=fev.UnicodeString(not_empty=True)),
+                    ew.SubmitButton(
+                        show_label=False,
+                        attrs={'value':'Invite'},
+                        show_errors=False)])]
+        return super(InviteOrganizationForm, self).display(**kw)
+
+class SendCollaborationRequestForm(ForgeForm):
+    defaults=dict(ForgeForm.defaults, submit_text='Send')
+
+    class fields(ew_core.NameList):
+        organization = ew.SingleSelectField(
+            label='Organization',
+            options = [],
+            validator=fev.UnicodeString(not_empty=True))
+
+        coll_type = ew.SingleSelectField(
+            label='Collaboration Type',
+            options = [
+                ew.Option(
+                    py_value='cooperation', 
+                    label='Cooperation'),
+                ew.Option(
+                    py_value='participation',
+                    label='Participation')],
+             validator=fev.UnicodeString(not_empty=True))
+
+    def display(self, **kw):
+        orgs = kw.get('organizations')
+        opts=[ew.Option(py_value=org._id,label=org.fullname) for org in orgs]
+        self.fields['organization'].options = opts
+        return super(SendCollaborationRequestForm, self).display(**kw)
+
+class ChangeCollaborationStatusForm(ForgeForm):
+    defaults=dict(ForgeForm.defaults, submit_text=None, show_errors=False)
+
+    def display(self, **kw):
+        coll = kw.get('collaboration')
+        org = coll.organization
+        orgnamefield = '<a href="%s">%s</a>' % (org.url(), org.fullname)
+        
+        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='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='Decline request',
+                    selected=False),
+                ew.Option(
+                    py_value='active',
+                    label='Accept 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=orgnamefield,
+                        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/version.py
----------------------------------------------------------------------
diff --git a/ForgeOrganization/forgeorganization/version.py b/ForgeOrganization/forgeorganization/version.py
new file mode 100644
index 0000000..6514373
--- /dev/null
+++ b/ForgeOrganization/forgeorganization/version.py
@@ -0,0 +1,2 @@
+__version_info__ = (0, 0)
+__version__ = '.'.join(map(str, __version_info__))

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/787ef235/ForgeOrganization/setup.py
----------------------------------------------------------------------
diff --git a/ForgeOrganization/setup.py b/ForgeOrganization/setup.py
new file mode 100644
index 0000000..e7389d6
--- /dev/null
+++ b/ForgeOrganization/setup.py
@@ -0,0 +1,33 @@
+from setuptools import setup, find_packages
+import sys, os
+
+from forgeorganization.version import __version__
+
+setup(name='ForgeOrganization',
+      version=__version__,
+      description="",
+      long_description="""\
+""",
+      classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
+      keywords='',
+      author='',
+      author_email='',
+      url='',
+      license='',
+      packages=find_packages(exclude=['ez_setup', 'examples', 'tests']),
+      include_package_data=True,
+      zip_safe=False,
+      install_requires=[
+          # -*- Extra requirements: -*-
+          'allura',
+      ],
+      entry_points="""
+      # -*- Entry points: -*-
+      [allura.organization]
+      organization=forgeorganization.organization.main:ForgeOrganizationApp
+
+      [allura]
+      organizationprofile = forgeorganization.organization_profile.organization_main:OrganizationProfileApp
+      organizationstool=forgeorganization.tool.main:ForgeOrganizationToolApp
+      """,
+      )

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/787ef235/ForgeOrganization/test.ini
----------------------------------------------------------------------
diff --git a/ForgeOrganization/test.ini b/ForgeOrganization/test.ini
new file mode 100644
index 0000000..dd41628
--- /dev/null
+++ b/ForgeOrganization/test.ini
@@ -0,0 +1,57 @@
+#
+# allura - TurboGears 2 testing environment configuration
+#
+# The %(here)s variable will be replaced with the parent directory of this file
+#
+[DEFAULT]
+debug = true
+organizations.enable = true
+
+[server:main]
+use = egg:Paste#http
+host = 0.0.0.0
+port = 5000
+
+[app:main]
+use = config:../Allura/test.ini
+
+[app:main_without_authn]
+use = config:../Allura/test.ini#main_without_authn
+
+[app:main_with_amqp]
+use = config:../Allura/test.ini#main_with_amqp
+
+[loggers]
+keys = root, allura, tool
+
+[handlers]
+keys = test
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = INFO
+handlers = test
+
+[logger_allura]
+level = DEBUG
+handlers =
+qualname = allura
+
+[logger_tool]
+level = DEBUG
+handlers =
+qualname = forgeorganization
+
+[handler_test]
+class = FileHandler
+args = ('test.log',)
+level = NOTSET
+formatter = generic
+
+[formatter_generic]
+format = %(asctime)s,%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
+datefmt = %H:%M:%S
+
+

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/787ef235/ForgeOrganizationStats/.svn/all-wcprops
----------------------------------------------------------------------
diff --git a/ForgeOrganizationStats/.svn/all-wcprops b/ForgeOrganizationStats/.svn/all-wcprops
new file mode 100644
index 0000000..3f221cf
--- /dev/null
+++ b/ForgeOrganizationStats/.svn/all-wcprops
@@ -0,0 +1,11 @@
+K 25
+svn:wc:ra_dav:version-url
+V 37
+/svn/allura/!svn/ver/3/ForgeUserStats
+END
+setup.py
+K 25
+svn:wc:ra_dav:version-url
+V 46
+/svn/allura/!svn/ver/1/ForgeUserStats/setup.py
+END

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/787ef235/ForgeOrganizationStats/.svn/entries
----------------------------------------------------------------------
diff --git a/ForgeOrganizationStats/.svn/entries b/ForgeOrganizationStats/.svn/entries
new file mode 100644
index 0000000..a6a6ad7
--- /dev/null
+++ b/ForgeOrganizationStats/.svn/entries
@@ -0,0 +1,65 @@
+10
+
+dir
+4
+https://xp-dev.com/svn/allura/ForgeUserStats
+https://xp-dev.com/svn/allura
+
+
+
+2012-10-19T08:28:36.749162Z
+3
+stefanoinvernizzi
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+46ed536d-f66c-413e-a53e-834384f708db
+
+forgeuserstats
+dir
+
+setup.py
+file
+
+
+
+
+2012-11-05T14:43:25.737756Z
+304c2b65bdf3d07e2f434208b164af4f
+2012-10-17T19:55:53.450112Z
+1
+stefanoinvernizzi
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+775
+

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/787ef235/ForgeOrganizationStats/.svn/text-base/setup.py.svn-base
----------------------------------------------------------------------
diff --git a/ForgeOrganizationStats/.svn/text-base/setup.py.svn-base b/ForgeOrganizationStats/.svn/text-base/setup.py.svn-base
new file mode 100644
index 0000000..dc2f07b
--- /dev/null
+++ b/ForgeOrganizationStats/.svn/text-base/setup.py.svn-base
@@ -0,0 +1,29 @@
+from setuptools import setup, find_packages
+import sys, os
+
+from forgeuserstats.version import __version__
+
+setup(name='ForgeUserStats',
+      version=__version__,
+      description="",
+      long_description="""\
+""",
+      classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
+      keywords='',
+      author='',
+      author_email='',
+      url='',
+      license='',
+      packages=find_packages(exclude=['ez_setup', 'examples', 'tests']),
+      include_package_data=True,
+      zip_safe=False,
+      install_requires=[
+          # -*- Extra requirements: -*-
+          'allura',
+      ],
+      entry_points="""
+      # -*- Entry points: -*-
+      [allura.stats]
+      userstats=forgeuserstats.main:ForgeUserStatsApp
+      """,
+      )

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/787ef235/ForgeOrganizationStats/forgeorganizationstats/.svn/all-wcprops
----------------------------------------------------------------------
diff --git a/ForgeOrganizationStats/forgeorganizationstats/.svn/all-wcprops b/ForgeOrganizationStats/forgeorganizationstats/.svn/all-wcprops
new file mode 100644
index 0000000..822e983
--- /dev/null
+++ b/ForgeOrganizationStats/forgeorganizationstats/.svn/all-wcprops
@@ -0,0 +1,23 @@
+K 25
+svn:wc:ra_dav:version-url
+V 52
+/svn/allura/!svn/ver/3/ForgeUserStats/forgeuserstats
+END
+__init__.py
+K 25
+svn:wc:ra_dav:version-url
+V 64
+/svn/allura/!svn/ver/1/ForgeUserStats/forgeuserstats/__init__.py
+END
+main.py
+K 25
+svn:wc:ra_dav:version-url
+V 60
+/svn/allura/!svn/ver/3/ForgeUserStats/forgeuserstats/main.py
+END
+version.py
+K 25
+svn:wc:ra_dav:version-url
+V 63
+/svn/allura/!svn/ver/1/ForgeUserStats/forgeuserstats/version.py
+END

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/787ef235/ForgeOrganizationStats/forgeorganizationstats/.svn/entries
----------------------------------------------------------------------
diff --git a/ForgeOrganizationStats/forgeorganizationstats/.svn/entries b/ForgeOrganizationStats/forgeorganizationstats/.svn/entries
new file mode 100644
index 0000000..d934292
--- /dev/null
+++ b/ForgeOrganizationStats/forgeorganizationstats/.svn/entries
@@ -0,0 +1,136 @@
+10
+
+dir
+4
+https://xp-dev.com/svn/allura/ForgeUserStats/forgeuserstats
+https://xp-dev.com/svn/allura
+
+
+
+2012-10-19T08:28:36.749162Z
+3
+stefanoinvernizzi
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+46ed536d-f66c-413e-a53e-834384f708db
+
+main.py
+file
+
+
+
+
+2012-11-05T14:43:25.733756Z
+9c5d8215ba783648e8e8682a00d519cc
+2012-10-19T08:28:36.749162Z
+3
+stefanoinvernizzi
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+8711
+
+version.py
+file
+
+
+
+
+2012-11-05T14:43:25.729756Z
+b5b12fd0365a9c5043213c216f3e889b
+2012-10-17T19:55:53.450112Z
+1
+stefanoinvernizzi
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+77
+
+templates
+dir
+
+model
+dir
+
+__init__.py
+file
+
+
+
+
+2012-11-05T14:43:25.733756Z
+f8653a729acb235bf0939944a047199a
+2012-10-17T19:55:53.450112Z
+1
+stefanoinvernizzi
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+42
+

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/787ef235/ForgeOrganizationStats/forgeorganizationstats/.svn/text-base/__init__.py.svn-base
----------------------------------------------------------------------
diff --git a/ForgeOrganizationStats/forgeorganizationstats/.svn/text-base/__init__.py.svn-base b/ForgeOrganizationStats/forgeorganizationstats/.svn/text-base/__init__.py.svn-base
new file mode 100644
index 0000000..34738da
--- /dev/null
+++ b/ForgeOrganizationStats/forgeorganizationstats/.svn/text-base/__init__.py.svn-base
@@ -0,0 +1 @@
+from main import ForgeUserStatsController

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/787ef235/ForgeOrganizationStats/forgeorganizationstats/.svn/text-base/main.py.svn-base
----------------------------------------------------------------------
diff --git a/ForgeOrganizationStats/forgeorganizationstats/.svn/text-base/main.py.svn-base b/ForgeOrganizationStats/forgeorganizationstats/.svn/text-base/main.py.svn-base
new file mode 100644
index 0000000..949ae74
--- /dev/null
+++ b/ForgeOrganizationStats/forgeorganizationstats/.svn/text-base/main.py.svn-base
@@ -0,0 +1,227 @@
+import logging
+
+import pylons
+pylons.c = pylons.tmpl_context
+pylons.g = pylons.app_globals
+from pylons import c, g, request
+from tg import expose, validate, config
+from tg.decorators import with_trailing_slash
+from paste.deploy.converters import asbool
+from webob import exc
+from datetime import datetime, date, timedelta
+
+from allura.app import Application, SitemapEntry
+from allura.eventslistener import EventsListener
+from allura import version
+from allura.controllers import BaseController
+from allura.lib.security import require_authenticated
+from model.stats import UserStats
+import allura.model as M
+
+from ming.orm.ormsession import ThreadLocalORMSession
+
+log = logging.getLogger(__name__)
+
+class ForgeUserStatsController(BaseController):
+
+    @expose()
+    def _lookup(self, part, *remainder):
+        user = M.User.query.get(username=part)
+
+        if not hasattr(c, 'userstats') :
+            c.userstats = user
+            c.category = None
+            return ForgeUserStatsController(), remainder
+        if part == "category" :
+            return ForgeUserStatsCatController(), remainder
+        if part == "metric" :
+            return ForgeUserStatsMetricController(), remainder
+
+    @expose('jinja:forgeuserstats:templates/index.html')
+    @with_trailing_slash
+    def index(self, **kw):
+        if not c.userstats : return dict(user=None)
+        stats = c.userstats.stats[0]
+
+        ret_dict = _getDataForCategory(None)
+        ret_dict['user'] = c.userstats
+        ret_dict['registration_date'] = stats.registration_date
+
+        ret_dict['totlogins'] = stats.tot_logins_count
+        ret_dict['last_login'] = stats.last_login
+        if stats.last_login :
+          ret_dict['last_login_days'] = (datetime.utcnow()-stats.last_login).days
+
+        categories = {}
+        for p in c.userstats.my_projects() :
+            for cat in p.trove_topic :
+                cat = M.TroveCategory.query.get(_id = cat)
+                if categories.get(cat) : categories[cat] += 1
+                else : categories[cat] = 1
+        categories = sorted(categories.items(), key=lambda (x,y) : y,reverse=True)
+
+        ret_dict['lastmonth_logins'] = stats.getLastMonthLogins()
+        ret_dict['categories'] = categories
+        days = ret_dict['days']
+        if days >= 30 : 
+            ret_dict['permonthlogins'] = \
+              round(stats.tot_logins_count*30.0/days,2)
+        else : ret_dict['permonthlogins'] = 'n/a'
+
+
+        ret_dict['codestars'] = stats.codeRanking()
+        ret_dict['discussionstars'] = stats.discussionRanking()
+        ret_dict['ticketsstars'] = stats.ticketsRanking()
+        return ret_dict
+
+class ForgeUserStatsCatController(BaseController):
+    @expose()
+    def _lookup(self, category, *remainder):
+        c.category = M.TroveCategory.query.get(fullname=category)
+        return ForgeUserStatsCatController(), remainder
+
+    @expose('jinja:forgeuserstats:templates/index.html')
+    @with_trailing_slash
+    def index(self, **kw):
+        if not c.userstats : return dict(user=None)
+        stats = c.userstats.stats[0]
+        
+        cat_id = None
+        if c.category : cat_id = c.category._id
+        ret_dict = _getDataForCategory(cat_id)
+        ret_dict['user'] = c.userstats
+        ret_dict['registration_date'] = stats.registration_date
+        ret_dict['category'] = c.category
+        
+        return ret_dict
+
+class ForgeUserStatsMetricController(BaseController):
+
+    @expose('jinja:forgeuserstats:templates/commits.html')
+    @with_trailing_slash
+    def commits(self, **kw):
+        if not c.userstats : return dict(user=None)
+        stats = c.userstats.stats[0]
+        
+        commits = stats.getCommitsByCategory()
+        return dict(user = c.userstats,
+                    data = commits) 
+
+    @expose('jinja:forgeuserstats:templates/artifacts.html')
+    @with_trailing_slash
+    def artifacts(self, **kw):
+        if not c.userstats : return dict(user=None)
+
+        stats = c.userstats.stats[0]        
+        artifacts = stats.getArtifactsByCategory(detailed=True)
+        return dict(user = c.userstats,
+                    data = artifacts) 
+
+    @expose('jinja:forgeuserstats:templates/tickets.html')
+    @with_trailing_slash
+    def tickets(self, **kw):
+        if not c.userstats : return dict(user=None)
+
+        stats = c.userstats.stats[0]        
+        artifacts = stats.getTicketsByCategory()
+        return dict(user = c.userstats,
+                    data = artifacts) 
+
+def _getDataForCategory(category) :
+    stats = c.userstats.stats[0]
+    totcommits = stats.getCommits(category)
+    tottickets = stats.getTickets(category)
+    averagetime = tottickets.get('averagesolvingtime')
+    artifacts_by_type = stats.getArtifactsByType(category)
+    totartifacts = artifacts_by_type.get(None) 
+    if totartifacts : del artifacts_by_type[None]
+    else : totartifacts = {'created' : 0, 'modified' : 0}
+    lmcommits = stats.getLastMonthCommits(category)
+    lm_artifacts_by_type = stats.getLastMonthArtifactsByType(category)
+    lm_totartifacts = stats.getLastMonthArtifacts(category)
+    lm_tickets = stats.getLastMonthTickets(category)
+
+    averagetime = lm_tickets.get('averagesolvingtime')
+
+    days = (datetime.utcnow() - stats.registration_date).days
+    if days >= 30 : 
+        pmartifacts = {'created' : round(totartifacts['created']*30.0/days,2),
+                       'modified': round(totartifacts['modified']*30.0/days,2)}
+        pmcommits = {'number': round(totcommits['number']*30.0/days,2),
+                     'lines' : round(totcommits['lines']*30.0/days,2)}
+        pmtickets = {'assigned' : round(tottickets['assigned']*30.0/days,2),
+                     'revoked' : round(tottickets['revoked']*30.0/days,2),
+                     'solved' : round(tottickets['solved']*30.0/days,2),
+                     'averagesolvingtime' : 'n/a'}
+        for key in artifacts_by_type :
+            value = artifacts_by_type[key]
+            artifacts_by_type[key]['pmcreated'] = \
+                round(value['created']*30.0/days,2)
+            artifacts_by_type[key]['pmmodified']= \
+                round(value['modified']*30.0/days,2)
+    else : 
+        pmartifacts = {'created'  : 'n/a',
+                       'modified' : 'n/a'}
+        pmcommits = {'number': 'n/a',
+                         'lines' : 'n/a'}
+        pmtickets = {'assigned'           : 'n/a',
+                     'revoked'            : 'n/a',
+                     'solved'             : 'n/a',
+                     'averagesolvingtime' : 'n/a'}
+        for key in artifacts_by_type :
+            value = artifacts_by_type[key]
+            artifacts_by_type[key]['pmcreated'] = 'n/a'
+            artifacts_by_type[key]['pmmodified']= 'n/a'
+
+    return dict(days = days,
+                totcommits = totcommits,
+                lastmonthcommits = lmcommits,
+                lastmonthtickets = lm_tickets,
+                tottickets = tottickets,
+                permonthcommits = pmcommits,
+                totartifacts = totartifacts,
+                lastmonthartifacts = lm_totartifacts,
+                permonthartifacts = pmartifacts,
+                artifacts_by_type = artifacts_by_type,
+                lastmonth_artifacts_by_type = lm_artifacts_by_type,
+                permonthtickets = pmtickets)
+
+class UserStatsListener(EventsListener) :
+    def newArtifact(self, art_type, art_datetime, project, user):
+        stats = UserStats.query.get(userid=user._id)
+        stats.addNewArtifact(art_type, art_datetime, project)
+
+    def modifiedArtifact(self, art_type, art_datetime, project, user):
+        stats = UserStats.query.get(userid=user._id)
+        stats.addModifiedArtifact(art_type, art_datetime, project)
+
+    def newUser(self, user):
+        UserStats(userid=user._id,
+                  registration_date = datetime.utcnow())
+
+    def ticketEvent(self, event_type, ticket, project, user):
+        if user is None : return
+        stats = UserStats.query.get(userid=user._id)
+        if event_type == "assigned" : 
+            stats.addAssignedTicket(ticket, project)
+        elif event_type == "revoked" :
+            stats.addRevokedTicket(ticket, project)
+        elif event_type == "closed" :
+            stats.addClosedTicket(ticket, project)
+
+    def newCommit(self, newcommit, project, user):
+        stats = user.stats[0]
+        stats.addCommit(newcommit, project)
+
+    def addUserLogin(self, user):
+        stats = user.stats[0]
+        stats.addLogin()
+
+class ForgeUserStatsApp :
+    root = ForgeUserStatsController()
+    listener = UserStatsListener()
+
+    @classmethod
+    def createlink(cls, user) :
+        return ("/userstats/%s/" % user.username, 
+                "%s personal statistcs" % user.display_name)

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/787ef235/ForgeOrganizationStats/forgeorganizationstats/.svn/text-base/version.py.svn-base
----------------------------------------------------------------------
diff --git a/ForgeOrganizationStats/forgeorganizationstats/.svn/text-base/version.py.svn-base b/ForgeOrganizationStats/forgeorganizationstats/.svn/text-base/version.py.svn-base
new file mode 100644
index 0000000..6514373
--- /dev/null
+++ b/ForgeOrganizationStats/forgeorganizationstats/.svn/text-base/version.py.svn-base
@@ -0,0 +1,2 @@
+__version_info__ = (0, 0)
+__version__ = '.'.join(map(str, __version_info__))

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

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

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/787ef235/ForgeOrganizationStats/forgeorganizationstats/controllers/organizationstats.py
----------------------------------------------------------------------
diff --git a/ForgeOrganizationStats/forgeorganizationstats/controllers/organizationstats.py b/ForgeOrganizationStats/forgeorganizationstats/controllers/organizationstats.py
new file mode 100644
index 0000000..6c3c590
--- /dev/null
+++ b/ForgeOrganizationStats/forgeorganizationstats/controllers/organizationstats.py
@@ -0,0 +1,311 @@
+from pylons import c
+from tg import expose, validate, redirect
+from tg.decorators import with_trailing_slash
+from datetime import datetime, timedelta
+from allura.controllers import BaseController
+import allura.model as M
+from forgeorganization.organization.model import Organization
+from forgeorganizationstats.model import OrganizationStats
+from allura.lib.graphics.graphic_methods import create_histogram, create_progress_bar
+from forgeorganizationstats.widgets.forms import StatsPreferencesForm
+from allura.lib.decorators import require_post
+from allura.lib.security import require_access
+from allura.lib import validators as V
+
+stats_preferences_form = StatsPreferencesForm()
+
+class ForgeOrgStatsCatController(BaseController):
+    @expose()
+    def _lookup(self, category, *remainder):
+        cat = M.TroveCategory.query.get(shortname=category)
+        return ForgeOrgStatsCatController(self.organization, cat), remainder
+
+    def __init__(self, category=None):
+        self.category = category
+        super(ForgeOrgStatsCatController, self).__init__()
+
+    @expose('jinja:forgeorganizationstats:templates/index.html')
+    @with_trailing_slash
+    def index(self, **kw):
+        self.organization = c.project.organization_project_of
+        if not self.organization: 
+            return None
+        stats = self.organization.stats
+        if not stats: 
+            stats = OrganizationStats.create(self.organization)
+        if (not stats.visible) and (c.user.username not in c.project.admins()):
+            return dict(organization=self.organization)
+        
+        cat_id = None
+        if self.category: 
+            cat_id = self.category._id
+        ret_dict = _getDataForCategory(cat_id, self.organization.stats)
+        ret_dict['organization'] = self.organization
+        ret_dict['registration_date'] = stats.registration_date
+        ret_dict['category'] = self.category
+        
+        return ret_dict
+
+class ForgeOrgStatsController(BaseController):
+
+    category = ForgeOrgStatsCatController()
+
+    @expose('jinja:forgeorganizationstats:templates/index.html')
+    @with_trailing_slash
+    def index(self, **kw):
+        self.organization = c.project.organization_project_of
+        if not self.organization: 
+            return dict(organization=None)
+        stats = self.organization.stats
+        if not stats:
+            stats = OrganizationStats.create(self.organization)
+
+        if (not stats.visible) and (not (c.user.username in c.project.admins())):
+            return dict(organization=self.organization)
+
+        ret_dict = _getDataForCategory(None, stats)
+        ret_dict['organization'] = self.organization
+
+        ret_dict['registration_date'] = stats.registration_date
+
+        categories = {}
+        for el in self.organization.project_involvements:
+            if el.status == 'active':
+                p = el.project
+                for cat in p.trove_topic:
+                    cat = M.TroveCategory.query.get(_id = cat)
+                    if categories.get(cat):
+                        categories[cat] += 1
+                    else:
+                        categories[cat] = 1
+        categories = sorted(
+            categories.items(), 
+            key=lambda (x,y): y,
+            reverse=True)
+
+        ret_dict['maxcodecontrib'], ret_dict['averagecodecontrib'] =\
+            stats.getMaxAndAverageCodeContribution()
+        ret_dict['maxdisccontrib'], ret_dict['averagedisccontrib'] =\
+            stats.getMaxAndAverageDiscussionContribution()
+        ret_dict['maxticketcontrib'], ret_dict['averageticketcontrib'] =\
+            stats.getMaxAndAverageTicketsSolvingPercentage()
+        members = [m for m in self.organization.memberships 
+                   if m.status=='active']
+        now = datetime.utcnow()
+        newmembers = [m for m in self.organization.memberships
+                      if m.startdate and now - m.startdate < timedelta(30)]
+        leftmembers = [m for m in self.organization.memberships
+                       if m.closeddate and now - m.closeddate < timedelta(30)]
+        new_cooperations = [p for p in self.organization.project_involvements
+            if p.startdate and now - p.startdate < timedelta(30) and 
+               p.collaborationtype=='cooperation']
+        new_participations = [p for p in self.organization.project_involvements
+            if p.startdate and now - p.startdate < timedelta(30) and 
+               p.collaborationtype=='participation']
+        old_cooperations = [p for p in self.organization.project_involvements
+            if p.closeddate and now - p.closeddate < timedelta(30) and 
+               p.collaborationtype=='cooperation']
+        old_participations = [p for p in self.organization.project_involvements
+            if p.closeddate and now - p.closeddate < timedelta(30) and 
+               p.collaborationtype=='participation']
+
+        return dict(
+            ret_dict,
+            categories = categories,
+            codepercentage = stats.codeRanking(),
+            discussionpercentage = stats.discussionRanking(),
+            ticketspercentage = stats.ticketsRanking(),
+            codecontribution = stats.getCodeContribution(),
+            discussioncontribution = stats.getDiscussionContribution(),
+            ticketcontribution = stats.getTicketsContribution(),
+            membersnumber = len(members),
+            newmembers = len(newmembers),
+            leftmembers = len(leftmembers),
+            coopnumber=len(self.organization.getActiveCooperations()),
+            participnumber = len(self.organization.getActiveParticipations()),
+            newcooperations = len(new_cooperations),
+            newparticipations = len(new_participations),
+            oldcooperations = len(old_cooperations),
+            oldparticipations = len(old_participations),
+            permemberartifacts = stats.getLastMonthArtifactsPerMember(),
+            permembertickets = stats.getLastMonthTicketsPerMember(),
+            permembercommits = stats.getLastMonthCommitsPerMember())
+
+    @expose()
+    def categories_graph(self):
+        categories = {}
+        for el in self.organization.project_involvements:
+            if el.status == 'active':
+                p = el.project
+                for cat in p.trove_topic:
+                    cat = M.TroveCategory.query.get(_id = cat)
+                    if categories.get(cat):
+                        categories[cat] += 1
+                    else:
+                        categories[cat] = 1
+        data = []
+        labels = []
+        i = 0
+        for cat in sorted(categories.keys(), key=lambda x:x.fullname):
+            n = categories[cat]
+            data = data + [i] * n
+            label = cat.fullname
+            if len(label) > 15:
+                label = label[:15] + "..."
+            labels.append(label)
+            i += 1
+
+        return create_histogram(data, labels, 
+            'Number of projects', 'Projects by category')
+
+    @expose()
+    def code_ranking_bar(self):
+        return create_progress_bar(self.organization.stats.codeRanking())
+
+    @expose()
+    def discussion_ranking_bar(self):
+        return create_progress_bar(self.organization.stats.discussionRanking())
+
+    @expose()
+    def tickets_ranking_bar(self):
+        return create_progress_bar(self.organization.stats.ticketsRanking())
+
+    @expose('jinja:forgeorganizationstats:templates/commits.html')
+    @with_trailing_slash
+    def commits(self, **kw):
+        self.organization = c.project.organization_project_of
+        if not self.organization: 
+            return None
+        stats = self.organization.stats
+        if not stats: 
+            stats = OrganizationStats.create(self.organization)
+        if (not stats.visible) and (c.user.username not in c.project.admins()):
+            return dict(organization=self.organization)
+        
+        commits = stats.getCommitsByCategory()
+        return dict(organization = self.organization,
+                    data = commits) 
+
+    @expose('jinja:forgeorganizationstats:templates/artifacts.html')
+    @with_trailing_slash
+    def artifacts(self, **kw):
+        self.organization = c.project.organization_project_of
+        if not self.organization: 
+            return None
+        stats = self.organization.stats
+        if not stats: 
+            stats = OrganizationStats.create(self.organization)
+        if (not stats.visible) and (c.user.username not in c.project.admins()):
+            return dict(organization=self.organization)
+
+        artifacts = stats.getArtifactsByCategory(detailed=True)
+        return dict(organization = self.organization, data = artifacts) 
+
+    @expose('jinja:forgeorganizationstats:templates/tickets.html')
+    @with_trailing_slash
+    def tickets(self, **kw):
+        self.organization = c.project.organization_project_of
+        if not self.organization: 
+            return None
+        stats = self.organization.stats
+        if not stats: 
+            stats = OrganizationStats.create(self.organization)
+        if (not stats.visible) and (c.user.username not in c.project.admins()):
+            return dict(organization=self.organization)
+
+        artifacts = stats.getTicketsByCategory()
+        return dict(organization= self.organization, data = artifacts) 
+
+    @expose('jinja:forgeorganizationstats:templates/settings.html')
+    @with_trailing_slash
+    def settings(self, **kw):
+        require_access(c.project, 'admin')
+
+        self.organization = c.project.organization_project_of
+        if not self.organization: 
+            return dict(organization=None)
+        if not self.organization.stats:
+            OrganizationStats.create(self.organization)
+        return dict(
+            organization = self.organization, 
+            form = StatsPreferencesForm(
+                action = c.project.url() + 'organizationstats/change_settings'))
+
+    @expose()
+    @require_post()
+    @validate(stats_preferences_form, error_handler=settings)
+    def change_settings(self, **kw):
+        require_access(c.project, 'admin')
+
+        self.organization = c.project.organization_project_of
+        if not self.organization: 
+            return dict(organization=None)
+        if not self.organization.stats:
+            OrganizationStats.create(self.organization)
+        visible = kw.get('visible')
+        self.organization.stats.visible = visible
+        redirect(c.project.url() + 'organizationstats/settings')
+
+def _getDataForCategory(category, stats):
+    totcommits = stats.getCommits(category)
+    tottickets = stats.getTickets(category)
+    averagetime = tottickets.get('averagesolvingtime')
+    artifacts_by_type = stats.getArtifactsByType(category)
+    totartifacts = artifacts_by_type.get(None) 
+    if totartifacts:
+        del artifacts_by_type[None]
+    else:
+        totartifacts = dict(created=0, modified=0)
+    lmcommits = stats.getLastMonthCommits(category)
+    lm_artifacts_by_type = stats.getLastMonthArtifactsByType(category)
+    lm_totartifacts = stats.getLastMonthArtifacts(category)
+    lm_tickets = stats.getLastMonthTickets(category)
+
+    averagetime = lm_tickets.get('averagesolvingtime')
+
+    days = (datetime.utcnow() - stats.registration_date).days
+    if days >= 30: 
+        pmartifacts = dict(
+            created = round(totartifacts['created']*30.0/days,2),
+            modified=round(totartifacts['modified']*30.0/days,2))
+        pmcommits = dict(
+            number=round(totcommits['number']*30.0/days,2),
+            lines=round(totcommits['lines']*30.0/days,2))
+        pmtickets = dict(
+            assigned=round(tottickets['assigned']*30.0/days,2),
+            revoked=round(tottickets['revoked']*30.0/days,2),
+            solved=round(tottickets['solved']*30.0/days,2),
+            averagesolvingtime='n/a')
+        for key in artifacts_by_type:
+            value = artifacts_by_type[key]
+            artifacts_by_type[key]['pmcreated'] = \
+                round(value['created']*30.0/days,2)
+            artifacts_by_type[key]['pmmodified']= \
+                round(value['modified']*30.0/days,2)
+    else: 
+        pmartifacts = dict(created='n/a', modified='n/a')
+        pmcommits = dict(number='n/a', lines='n/a')
+        pmtickets = dict(
+            assigned='n/a',
+            revoked='n/a',
+            solved='n/a',
+            averagesolvingtime='n/a')
+        for key in artifacts_by_type:
+            value = artifacts_by_type[key]
+            artifacts_by_type[key]['pmcreated'] = 'n/a'
+            artifacts_by_type[key]['pmmodified']= 'n/a'
+
+    return dict(
+        days = days,
+        totcommits = totcommits,
+        lastmonthcommits = lmcommits,
+        lastmonthtickets = lm_tickets,
+        tottickets = tottickets,
+        permonthcommits = pmcommits,
+        totartifacts = totartifacts,
+        lastmonthartifacts = lm_totartifacts,
+        permonthartifacts = pmartifacts,
+        artifacts_by_type = artifacts_by_type,
+        lastmonth_artifacts_by_type = lm_artifacts_by_type,
+        permonthtickets = pmtickets)
+

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/787ef235/ForgeOrganizationStats/forgeorganizationstats/main.py
----------------------------------------------------------------------
diff --git a/ForgeOrganizationStats/forgeorganizationstats/main.py b/ForgeOrganizationStats/forgeorganizationstats/main.py
new file mode 100644
index 0000000..5349964
--- /dev/null
+++ b/ForgeOrganizationStats/forgeorganizationstats/main.py
@@ -0,0 +1,145 @@
+import logging
+from datetime import datetime
+
+from pylons import c
+from allura import model as M
+from allura.eventslistener import EventsListener
+from model import OrganizationStats
+from controllers.organizationstats import ForgeOrgStatsController
+from forgeorganizationstats import version
+from allura.app import Application, SitemapEntry
+from allura.lib import helpers as h
+
+log = logging.getLogger(__name__)
+
+class OrganizationStatsListener(EventsListener):
+    def newArtifact(self, art_type, art_datetime, project, user):
+        for org in _getInterestedOrganizations(user, project):
+            stats = org.stats
+            if not stats:
+                stats = OrganizationStats.create(org)
+            stats.addNewArtifact(art_type, art_datetime, project)
+
+    def modifiedArtifact(self, art_type, art_datetime, project, user):
+        for org in _getInterestedOrganizations(user, project):
+            stats = org.stats
+            if not stats:
+                stats = OrganizationStats.create(org)
+            stats.addModifiedArtifact(art_type, art_datetime,project)
+
+    def newOrganization(self, organization):
+        stats = OrganizationStats.create(organization)
+
+    def newUser(self, user):
+        pass
+
+    def ticketEvent(self, event_type, ticket, project, user):
+        if user is None:
+            return
+        organizations = _getInterestedOrganizations(user, project)
+        if event_type=="assigned":
+            for org in organizations:
+                stats = org.stats
+                if not stats:
+                    stats = OrganizationStats.create(org)
+                stats.addAssignedTicket(ticket.mod_date, project)
+        elif event_type=="revoked":
+            for org in organizations:
+                stats = org.stats
+                if not stats:
+                    stats = OrganizationStats.create(org)
+                stats.addRevokedTicket(ticket.mod_date, project)
+        elif event_type=="closed":
+            for org in organizations:
+                stats = org.stats
+                if not stats:
+                    stats = OrganizationStats.create(org)
+                stats.addClosedTicket(ticket.created_date, ticket.mod_date, project)
+
+    def newCommit(self, newcommit, project, user):
+        for org in _getInterestedOrganizations(user, project):
+            stats = org.stats
+            if not stats:
+                stats = OrganizationStats.create(org)
+            stats.addCommit(newcommit, datetime.utcnow(), project)
+
+    def addUserLogin(self, user):
+        pass
+
+def _getInterestedOrganizations(user, project):
+    proj_organizations=\
+        [org.organization for org in project.organizations
+         if org.status=='active']
+    return [m.organization for m in user.memberships
+            if m.status=='active' and m.organization in proj_organizations]
+
+class ForgeOrganizationStatsApp(Application):
+    __version__ = version.__version__
+    tool_label='Statistics'
+    default_mount_label='Statistics'
+    default_mount_point='organizationstats'
+    permissions = ['configure', 'read', 'write',
+                    'unmoderated_post', 'post', 'moderate', 'admin']
+    ordinal=15
+    installable=False
+    config_options = Application.config_options
+    default_external_feeds = []
+    icons={
+        24:'images/stats_24.png',
+        32:'images/stats_32.png',
+        48:'images/stats_48.png'
+    }
+    root = ForgeOrgStatsController()
+
+    def __init__(self, project, config):
+        Application.__init__(self, project, 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')]
+
+    def main_menu(self):
+        return [SitemapEntry(self.config.options.mount_label.title(), '.')]
+
+    @property
+    @h.exceptionless([], log)
+    def sitemap(self):
+        menu_id = self.config.options.mount_label.title()
+        with h.push_config(c, app=self):
+            return [
+                SitemapEntry(menu_id, '.')[self.sidebar_menu()] ]
+
+    @property
+    def show_discussion(self):
+        if 'show_discussion' in self.config.options:
+            return self.config.options['show_discussion']
+        else:
+            return True
+
+    @h.exceptionless([], log)
+    def sidebar_menu(self):
+        base = c.app.url
+        links = [SitemapEntry('Overview', base),
+                 SitemapEntry('Commits', base + 'commits'),
+                 SitemapEntry('Artifacts', base + 'artifacts'),
+                 SitemapEntry('Tickets', base + 'tickets')]
+        return links
+
+    def admin_menu(self):
+        links = [SitemapEntry(
+                     'Settings', c.project.url() + 'organizationstats/settings')]
+        return links
+
+    def install(self, project):
+        #It doesn't make any sense to install the tool twice on the same 
+        #project therefore, if it already exists, it doesn't install it
+        #a second time.
+        for tool in project.app_configs:
+            if tool.tool_name == 'organizationstats':
+                if self.config.options.mount_point!=tool.options.mount_point:
+                    project.uninstall_app(self.config.options.mount_point)
+                    return
+
+    def uninstall(self, project):
+        pass

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/787ef235/ForgeOrganizationStats/forgeorganizationstats/model/.svn/all-wcprops
----------------------------------------------------------------------
diff --git a/ForgeOrganizationStats/forgeorganizationstats/model/.svn/all-wcprops b/ForgeOrganizationStats/forgeorganizationstats/model/.svn/all-wcprops
new file mode 100644
index 0000000..a5d5661
--- /dev/null
+++ b/ForgeOrganizationStats/forgeorganizationstats/model/.svn/all-wcprops
@@ -0,0 +1,17 @@
+K 25
+svn:wc:ra_dav:version-url
+V 58
+/svn/allura/!svn/ver/3/ForgeUserStats/forgeuserstats/model
+END
+stats.py
+K 25
+svn:wc:ra_dav:version-url
+V 67
+/svn/allura/!svn/ver/3/ForgeUserStats/forgeuserstats/model/stats.py
+END
+__init__.py
+K 25
+svn:wc:ra_dav:version-url
+V 70
+/svn/allura/!svn/ver/1/ForgeUserStats/forgeuserstats/model/__init__.py
+END

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/787ef235/ForgeOrganizationStats/forgeorganizationstats/model/.svn/entries
----------------------------------------------------------------------
diff --git a/ForgeOrganizationStats/forgeorganizationstats/model/.svn/entries b/ForgeOrganizationStats/forgeorganizationstats/model/.svn/entries
new file mode 100644
index 0000000..c26dfd9
--- /dev/null
+++ b/ForgeOrganizationStats/forgeorganizationstats/model/.svn/entries
@@ -0,0 +1,96 @@
+10
+
+dir
+4
+https://xp-dev.com/svn/allura/ForgeUserStats/forgeuserstats/model
+https://xp-dev.com/svn/allura
+
+
+
+2012-10-19T08:28:36.749162Z
+3
+stefanoinvernizzi
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+46ed536d-f66c-413e-a53e-834384f708db
+
+stats.py
+file
+
+
+
+
+2012-11-05T14:43:25.729756Z
+21591047edf4fabfb1b70150af5bd0c2
+2012-10-19T08:28:36.749162Z
+3
+stefanoinvernizzi
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+23647
+
+__init__.py
+file
+
+
+
+
+2012-11-05T14:43:25.729756Z
+d41d8cd98f00b204e9800998ecf8427e
+2012-10-17T19:55:53.450112Z
+1
+stefanoinvernizzi
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+0
+

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/787ef235/ForgeOrganizationStats/forgeorganizationstats/model/.svn/text-base/__init__.py.svn-base
----------------------------------------------------------------------
diff --git a/ForgeOrganizationStats/forgeorganizationstats/model/.svn/text-base/__init__.py.svn-base b/ForgeOrganizationStats/forgeorganizationstats/model/.svn/text-base/__init__.py.svn-base
new file mode 100644
index 0000000..e69de29