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