Merged nereid-activity-stream #2153

* This patch include Activity stream generation for any activity created
  on Project Management System

Issue ID: 336001
Task ID: project-7/task-2153
This commit is contained in:
Simmi Anand 2013-08-08 13:37:15 +05:30
parent 51f50088ee
commit e709542a56
8 changed files with 191 additions and 11 deletions

View File

@ -3,5 +3,7 @@ python:
- "2.6"
- "2.7"
install:
- pip install git+ssh://git@github.com/openlabs/nereid-activity-stream.git@develop
- python setup.py install
script: python setup.py test
source_key: "LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb2dJQkFBS0NBUUVBczFRaEh4WnRvaXl0emVsUDZEaDR4Ynl1S1M1dVBoamQzdkozM0xRa2U3N0xwcDRvCmNpOGtLY1J6WUt0bUNJaHIrbG5KbWVVQVNpUWhZMUlSa2ZwSFo3MDVaRXlmNUxxeDZCczBDYlRZQ1o4dm1YeEwKbzNmRFBWMkRpWkZKY0g0TExEb1B6anhtNG9ldTBFdHJZNllRR2NnaWdmRll5eFVXMTJqb2x4N3RJVlVEcDFmSApoS0s4OGd4WkZ1YkpGeEQxYlVNc1VRRXFSWHZYYkhSaEpiQ01oYzltdzRERVk2TkpwejA3R3RMZGViWk1Ra05QCjI4VTQ2OStJZCtjQURuM2dWcmZDMjJFMGlidmtieGR5cXFHS21aWGJjVVJObUw3NFF4VVdhN1ZSRXkvK2VVdDYKSGlaOElYZ1ZQOEsxeUdyT0tybWxhUGtYVDFteG9OekxudGpkSVFJQkl3S0NBUUJ3dUluWkJzaVIwdkRuMUh0ZQp4R2tyMWE5TkZkN2xORE93dGFwdGVJdjJBdVpvdXk5ZHR6dE5kQ3RFRS9iK0RKdWtyWGRvREVsVExLZEZ2bzZ6CmgyQVZVbDZXMFJRTUhaUm1Bbmg3SVRkV2dWRWVwZVptd0U3V0hZWGhjVVFhMHVKemtqWFNGMWFkQk5SSWFldTYKK3FzSTVETHlsN1FuNFY3UWl4WGJWVVNZbXNpdDkwSlZmdU15MGVOY2s3TWNCME9RTU9mdjBBSnVFbVRUbjR5ZApsbW5aK21TWVBGU2FQSWE1THZVSWVsOTIybVNLcUpaTk1vaGVIL3N0WVlkMnJhZENXUE1hdVk5dENjL3FXSzRiCjFhbEtRVDF5Zkg3SjgrVTlhY0l6ZHpVTTFsOThwMGMvVklSZHRZNUszeXowdWFyMDNhZ1c0OXh5UTI2a1BtdTIKT0tKVEFvR0JBT0haRnRESVlqOVUwcWRNUTArOFgweFNWUk56MlE1YkpEaFArd2t0SkxDR01DUURrczBoREVsWQp0MWlqb0lHaDJYVndpRUtpd3dncE9SSGJyUGpJSjYrcFJyNVBnRlpBelJKSEpwNXkxSEpQVElBSVJXSzJhRE5pCkFQK09GRVhxUmFBdERYTXVkWlM0Y3R6ZE9YTEtRRmRUWmtrQjA2dTBHdURwd25BUVdqaFhBb0dCQU10RklGTUYKc001WUF3M2w1d3J6Nit3cDVodzh1a2FZVWFHZFFWVmE3Z1VOa09pSDZ4T1RDQ241WGhiU2I2eXlEUWhTT25zeQpqdU55dVdjQ2t6dG1ud09zbmJCVnVhaW5HV1I0NzJJaXlxSTg0WkR0Q2srNFVzRG9HbUhvMThKbDhtbEx2NG56CnFuSHJOZFlDc2RpUUdsczZmbHlmMGkvT01LQ3h6bE1iSkl0SEFvR0JBTTU5VnJCQ0xmRENsTFkzR1BoeHRqY20KdEM4SzFSUndsaVRiYVN6MkV1cUpVSlgwbzNtMzBMZ1dtUUNWbWhBZThyU0VKTTgxdWFFUHZ5WkZNRzV0MnlSQwovdG5pU1hOdWNsbmFwdmRFYW5jajdpdnFUaEVielB2SFVWNnR5V1IrYTRzaDRHbFBER3EzUkhJcFdSaCtaclk5Cm4xaXhObzViaGtuNlMxOGs1TVhMQW9HQWJsaTV4cXRSV2hLRlVMQUJGSk1MQ3l5d0hmVVVxZnJxZkU0TmhodHIKUkpKVitwTHRVOFRZaTlDRGlMdGhNZHovd3JCTG9mYmYybzY4YXg2bnNvZ3FiN1Y2TElaTzErV3VzdUtRbEZ3SAptZWFKRkNqb1Z5bU1BbEljODFLRHdVMUpGSmJWc1VuQzdXSnRyckVCZFpBT1RzZjdkQncrNXNCamltOG0zS2hWCnFyRUNnWUVBakgxRHU2MkVnTzZXV0RkZm9WcEYwaDM1N1llN3V5djZncTFMYldNay9oMXlEcTB4UkpzRitnc1UKb1ZEZkVkS3VTSXBIK3VFVkwxK2Uyd2VjdWhqcURzdnZ4UGp4TTdkQmJWaDNKWjZ4OGRuSndJSFlpeU8xeittMwpLVXp1RUJsM01IZjduUDgvVGhUM1NsM3pzTmNIdXJhdElVMWJFWDEyMHpUZnEzeFF4cVU9Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg=="

View File

@ -8,9 +8,11 @@
'''
from trytond.pool import Pool
from project import WebSite, ProjectUsers, ProjectInvitation, \
ProjectWorkInvitation, TimesheetEmployeeDay, Project, Tag, TaskTags, \
ProjectHistory, ProjectWorkCommit
from project import (
WebSite, ProjectUsers, ProjectInvitation,
ProjectWorkInvitation, TimesheetEmployeeDay, Project, Tag,
TaskTags, ProjectHistory, ProjectWorkCommit, Activity,
)
from company import Company, CompanyProjectAdmins, NereidUser
@ -28,6 +30,7 @@ def register():
TaskTags,
ProjectHistory,
ProjectWorkCommit,
Activity,
Company,
CompanyProjectAdmins,
NereidUser,

View File

@ -41,7 +41,7 @@ from trytond.backend import TableHandler
__all__ = ['WebSite', 'ProjectUsers', 'ProjectInvitation',
'TimesheetEmployeeDay', 'ProjectWorkInvitation', 'Project', 'Tag',
'TaskTags', 'ProjectHistory', 'ProjectWorkCommit']
'TaskTags', 'ProjectHistory', 'ProjectWorkCommit', 'Activity',]
__metaclass__ = PoolMeta
@ -373,6 +373,26 @@ class Project:
self.constraint_finish_time.isoformat() or None,
}
def _json(self):
'''
Serialize the work and returns a dictionary
'''
rv = {
'id': self.id,
'displayName': self.name,
'type': self.type,
'objectType': self.__name__,
}
if self.type == 'project':
rv['url'] = url_for(
'project.work.render_project', active_id=self.id
)
else:
rv['url'] = url_for(
'project.work.render_task', active_id=self.id
)
return rv
@classmethod
def rst_to_html(cls):
"""
@ -534,6 +554,7 @@ class Project:
POST will create a new project
"""
Activity = Pool().get('nereid.activity')
if not request.nereid_user.is_project_admin():
flash("Sorry! You are not allowed to create new projects." +
" Contact your project admin for the same.")
@ -544,6 +565,12 @@ class Project:
'name': request.form['name'],
'type': 'project',
})
Activity.create({
'actor': request.nereid_user.id,
'object_': 'project.work, %d' % project.id,
'verb': 'created_project',
'project': project.id,
})
flash("Project successfully created.")
return redirect(
url_for('project.work.render_project', project_id=project.id)
@ -559,6 +586,7 @@ class Project:
POST will create a new task
"""
NereidUser = Pool().get('nereid.user')
Activity = Pool().get('nereid.activity')
project = self.get_project(self.id)
@ -588,6 +616,13 @@ class Project:
constraint_finish_time, '%m/%d/%Y')
task = self.create(data)
Activity.create({
'actor': request.nereid_user.id,
'object_': 'project.work, %d' % task.id,
'verb': 'created_task',
'target': 'project.work, %d' % project.id,
'project': project.id,
})
email_receivers = [p.email for p in self.all_participants]
if request.form.get('assign_to', False):
@ -615,13 +650,20 @@ class Project:
"""
Edit the task
"""
Activity = Pool().get('nereid.activity')
task = self.get_task(self.id)
self.write([task], {
'name': request.form.get('name'),
'comment': request.form.get('comment')
})
Activity.create({
'actor': request.nereid_user.id,
'object_': 'project.work, %d' % task.id,
'verb': 'edited_task',
'target': 'project.work, %d' % task.parent.id,
'project': task.parent.id,
})
if request.is_xhr:
return jsonify({
'success': True,
@ -745,6 +787,7 @@ class Project:
"""
NereidUser = Pool().get('nereid.user')
ProjectInvitation = Pool().get('project.work.invitation')
Activity = Pool().get('nereid.activity')
if not request.method == 'POST':
return abort(404)
@ -777,6 +820,12 @@ class Project:
'participants': [('add', [existing_user[0].id])]
}
)
Activity.create({
'actor': existing_user[0].id,
'object_': 'project.work, %d' % project.id,
'verb': 'joined_project',
'project': project.id,
})
flash_message = "%s has been invited to the project" \
% existing_user[0].display_name
@ -808,6 +857,7 @@ class Project:
def remove_participant(self, participant_id):
"""Remove the participant form project
"""
Activity = Pool().get('nereid.activity')
# Check if user is among the project admins
if not request.nereid_user.is_project_admin():
flash("Sorry! You are not allowed to remove participants." +
@ -835,6 +885,13 @@ class Project:
self.__class__(rec_id), records_to_update_ids
), {'participants': [('unlink', [participant_id])]}
)
Activity.create({
'actor': request.nereid_user.id,
'object_': 'nereid.user, %d' % participant_id,
'target': 'project.work, %d' % self.id,
'verb': 'removed_participant',
'project': self.id,
})
return jsonify({
'success': True,
@ -1566,6 +1623,7 @@ class Project:
"""
History = Pool().get('project.work.history')
TimesheetLine = Pool().get('timesheet.line')
Activity = Pool().get('nereid.activity')
task = cls.get_task(task_id)
@ -1601,6 +1659,7 @@ class Project:
current_participant_ids:
new_participant_ids.append(new_assignee_id)
if task_changes:
# Only write change if anything has really changed
cls.write([task], task_changes)
@ -1614,7 +1673,13 @@ class Project:
else:
# Just comment, no update to task
comment = History.create(history_data)
Activity.create({
'actor': request.nereid_user.id,
'object_': 'project.work.history, %d' % comment.id,
'verb': 'updated_task',
'target': 'project.work, %d' % task.id,
'project': task.parent.id,
})
if request.nereid_user.id not in current_participant_ids:
# Add the user to the participants if not already in the list
@ -1633,11 +1698,18 @@ class Project:
hours = request.form.get('hours', None, type=float)
if hours and request.nereid_user.employee:
TimesheetLine.create({
timesheet_line = TimesheetLine.create({
'employee': request.nereid_user.employee.id,
'hours': hours,
'work': task.work.id
})
Activity.create({
'actor': request.nereid_user.id,
'object_': 'timesheet.line, %d' % timesheet_line.id,
'verb': 'reported_time',
'target': 'project.work, %d' % task.id,
'project': task.parent.id,
})
# Send the email since all thats required is done
comment.send_mail()
@ -1662,11 +1734,19 @@ class Project:
:param task_id: ID of task
:param tag_id: ID of tag
"""
Activity = Pool().get('nereid.activity')
task = cls.get_task(task_id)
cls.write(
[task], {'tags': [('add', [tag_id])]}
)
Activity.create({
'actor': request.nereid_user.id,
'object_': 'project.work.tag, %d' % tag_id,
'verb': 'added_tag_to_task',
'target': 'project.work, %d' % task.id,
'project': task.parent.id,
})
if request.method == 'POST':
flash('Tag added to task %s' % task.name)
@ -1684,11 +1764,19 @@ class Project:
:param task_id: ID of task
:param tag_id: ID of tag
"""
Activity = Pool().get('nereid.activity')
task = cls.get_task(task_id)
cls.write(
[task], {'tags': [('unlink', [tag_id])]}
)
Activity.create({
'actor': request.nereid_user.id,
'object_': 'project.work, %d' % task.id,
'verb': 'removed_tag_from_task',
'target': 'project.work, %d' % task.parent.id,
'project': task.parent.id,
})
if request.method == 'POST':
flash('Tag removed from task %s' % task.name)
@ -1747,6 +1835,7 @@ class Project:
:param task_id: Id of Task
"""
NereidUser = Pool().get('nereid.user')
Activity = Pool().get('nereid.activity')
task = cls.get_task(task_id)
@ -1761,6 +1850,13 @@ class Project:
'participants': [('add', [new_assignee.id])]
})
task.history[-1].send_mail()
Activity.create({
'actor': request.nereid_user.id,
'object_': 'project.work, %d' % task.id,
'verb': 'assigned_task_to',
'target': 'nereid.user, %d' % new_assignee.id,
'project': task.parent.id,
})
if request.is_xhr:
return jsonify({
'success': True,
@ -1899,6 +1995,20 @@ class Tag(ModelSQL, ModelView):
'''
return "#999"
def _json(self):
'''
Serialize the tag and returns a dictionary.
'''
return {
"url": url_for(
'project.work.render_task_list', project_id=self.project.id,
state="opened", tag=self.id
),
"objectType": self.__name__,
"id": self.id,
"displayName": self.name,
}
@classmethod
@login_required
def create_tag(cls, project_id):
@ -1908,6 +2018,7 @@ class Tag(ModelSQL, ModelView):
:params project_id: Project id for which need to be created
"""
Project = Pool().get('project.work')
Activity = Pool().get('nereid.activity')
project = Project.get_project(project_id)
@ -1918,11 +2029,18 @@ class Tag(ModelSQL, ModelView):
return redirect(request.referrer)
if request.method == 'POST':
cls.create({
tag = cls.create({
'name': request.form['name'],
'color': request.form['color'],
'project': project.id
})
Activity.create({
'actor': request.nereid_user.id,
'object_': 'project.work.tag, %d' % tag.id,
'verb': 'created_tag',
'target': 'project.work, %d' % project.id,
'project': project.id,
})
flash("Successfully created tag")
return redirect(request.referrer)
@ -2037,6 +2155,19 @@ class ProjectHistory(ModelSQL, ModelView):
'''
return datetime.utcnow()
def _json(self):
'''
Serialize the history and returns a dictionary.
'''
return {
"url": url_for(
'project.work.render_task_list', comment=self.comment.id
),
"objectType": self.__name__,
"id": self.id,
"displayName": self.display_name,
}
@classmethod
def create_history_line(cls, project, changed_values):
"""
@ -2266,6 +2397,7 @@ def invitation_new_user_handler(nereid_user_id):
Invitation = Pool().get('project.work.invitation')
Project = Pool().get('project.work')
NereidUser = Pool().get('nereid.user')
Activity = Pool().get('nereid.activity')
except KeyError:
# Just return silently. This KeyError is cause if the module is not
@ -2315,3 +2447,20 @@ def invitation_new_user_handler(nereid_user_id):
'participants': [('add', [nereid_user_id])]
}
)
Activity.create({
'actor': nereid_user_id,
'object_': 'project.work, %d' % invitation.project.id,
'verb': 'joined_project',
'project': invitation.project.id,
})
class Activity:
'''
Nereid user activity
'''
__name__ = "nereid.activity"
project = fields.Many2One(
'project.work', 'Project', domain=[('type', '=', 'project')]
)

View File

@ -54,6 +54,27 @@ of this repository contains the full copyright notices and license terms. -->
</field>
</record>
<record model="nereid.activity.allowed_model"
id="activity_allowed_model_work">
<field name="name">ProjectWork</field>
<field name="model" search="[('model', '=', 'project.work')]"/>
</record>
<record model="nereid.activity.allowed_model"
id="activity_allowed_model_work_tag">
<field name="name">ProjectWorkTag</field>
<field name="model" search="[('model', '=', 'project.work.tag')]"/>
</record>
<record model="nereid.activity.allowed_model"
id="activity_allowed_model_nereid_user">
<field name="name">NereidUser</field>
<field name="model" search="[('model', '=', 'nereid.user')]"/>
</record>
<record model="nereid.activity.allowed_model"
id="activity_allowed_model_work_history">
<field name="name">ProjectWorkHistory</field>
<field name="model" search="[('model', '=', 'project.work.history')]"/>
</record>
<record id="permission_project_admin" model="nereid.permission">
<field name="name">Project Admin</field>
<field name="value">project.admin</field>

View File

@ -14,7 +14,7 @@
<br>
<a class="btn btn-mini btn-danger btn-remove-participant" href=""
title="Remove {{ user.display_name }}" rel="tooltip" data-id="{{ user.id }}"
data-url="{{ url_for('project.work.remove_participant', project_id=project.id, participant_id=user.id) }}">
data-url="{{ url_for('project.work.remove_participant', active_id=project.id, participant_id=user.id) }}">
<i class="icon-minus-sign icon-white"></i> {{ _('Remove') }}
</a>
{% endif %}
@ -57,12 +57,12 @@
<strong>{{ invitation.email }}</strong><br>
<a class="btn btn-small btn-reinvite-participant"
title="Re-Invite {{ invitation.email }}" rel="tooltip" data-id="{{ invitation.id }}"
data-url="{{ url_for('project.work.invitation.resend_invite', invitation_id=invitation.id) }}">
data-url="{{ url_for('project.work.invitation.resend_invite', active_id=invitation.id) }}">
<i class="icon-share-alt icon-white"></i> {{ _('Resend Invite') }}
</a>
<a class="btn btn-small btn-warning btn-remove-invite"
title="Revoke invitation to {{ invitation.email }}" rel="tooltip" data-id="{{ invitation.id }}"
data-url="{{ url_for('project.work.invitation.remove_invite', invitation_id=invitation.id) }}">
data-url="{{ url_for('project.work.invitation.remove_invite', active_id=invitation.id) }}">
<i class="icon-minus-sign icon-white"></i> {{ _('Revoke Invitation') }}
</a>
</div>

View File

@ -41,6 +41,8 @@ class TestNereidProject(NereidTestCase):
this method is called before each test function execution.
"""
trytond.tests.test_tryton.install_module('nereid_project')
self.ActivityAllowedModel = POOL.get('nereid.activity.allowed_model')
self.Model = POOL.get('ir.model')
self.Project = POOL.get('project.work')
self.Company = POOL.get('company.company')
self.Employee = POOL.get('company.employee')

View File

@ -40,6 +40,8 @@ class TestTask(NereidTestCase):
this method is called before each test function execution.
"""
trytond.tests.test_tryton.install_module('nereid_project')
self.ActivityAllowedModel = POOL.get('nereid.activity.allowed_model')
self.Model = POOL.get('ir.model')
self.Company = POOL.get('company.company')
self.Employee = POOL.get('company.employee')
self.Currency = POOL.get('currency.currency')

View File

@ -10,6 +10,7 @@ depends:
project_revenue
project_plan
nereid
nereid_activity_stream
xml:
urls.xml
company.xml