[schema/view] add assigned_to relation to Ticket (closes #1433333)

authorAlexandre Richardson <alexandre.richardson@logilab.fr>
changesetcbab3eb96b35
branchdefault
phasepublic
hiddenno
parent revision#bc95e67dcc89 [security] Change 'assigned_to' permission: a project owner can set the relation (closes #3299942).
child revision#a135cd56e72c Display filter in tickets table by default (closes #3552735)
files modified by this revision
migration/1.15.0_Any.py
schema.py
test/unittest_security.py
views/project.py
# HG changeset patch
# User Alexandre Richardson <alexandre.richardson@logilab.fr>
# Date 1392389060 -3600
# Fri Feb 14 15:44:20 2014 +0100
# Node ID cbab3eb96b35a200110f9a693d883600b0985307
# Parent bc95e67dcc890cdbe804fbf8e577942d5715ed0e
[schema/view] add assigned_to relation to Ticket (closes #1433333)

diff --git a/migration/1.15.0_Any.py b/migration/1.15.0_Any.py
@@ -1,3 +1,12 @@
1  # Change permission 'assigned_to'. A project owner can set the relation.
2  rename_relation_type('todo_by', 'assigned_to')
3  sync_schema_props_perms('assigned_to')
4 +
5 +## Add assigned_to for Tickets
6 +add_relation_definition('Ticket', 'assigned_to', 'CWUser')
7 +rset = rql('Any TI,TG WHERE TG name ILIKE "todoby_%", TG tags TI, TG is Tag')
8 +for ticket, tag in rset.iter_rows_with_entities():
9 +    user_login = tag.name.split('_')[1]
10 +    rql('SET T assigned_to U WHERE U is CWUser, U login %(user)s, T eid %(ticket)s',
11 +        {'user': user_login, 'ticket': ticket.eid})
12 +commit()
13 \ No newline at end of file
diff --git a/schema.py b/schema.py
@@ -176,10 +176,20 @@
14                                   constraints=[RQLVocabularyConstraint(
15                                       'S concerns P, O concerns PO, '
16                                       'PO identity P OR '
17                                       'EXISTS(P uses PO) OR '
18                                       'EXISTS(PO subproject_of P)')])
19 +    assigned_to = SubjectRelation('CWUser',
20 +                               __permissions__ = {
21 +                                   'read':   ('managers', 'users', 'guests'),
22 +                                   'add':    ('managers', RRQLExpression('S concerns P, P owned_by U'),), 
23 +                                   'delete': ('managers', RRQLExpression('S concerns P, P owned_by U'),),
24 +                                   },
25 +                               cardinality='?*',
26 +                               description=_("user who has to execute this ticket"),
27 +                               constraints=[RQLVocabularyConstraint(
28 +                                     'O in_state ST, ST name "activated"')])
29 
30 
31 
32 
33 
diff --git a/test/unittest_security.py b/test/unittest_security.py
@@ -369,8 +369,62 @@
34          self._orig_cnx[0].commit()
35          req = cnx.request()
36          rset = req.execute('Any X WHERE X eid %(x)s', {'x': t1.eid})
37          self.assertFalse(req.vreg['actions'].select_or_none('movetonext', req, rset=rset))
38 
39 +    def test_assigned_to_relation(self):
40 +        # staff user is the owner of the project
41 +        cnx = self.mylogin('staffuser')
42 +        cu = cnx.cursor()
43 +        cu.execute(*create_project_rql("todoby_proj"))
44 +        ticket = cu.execute(*create_ticket_rql('a ticket', 'todoby_proj')).get_entity(0, 0)
45 +        ticket_eid = ticket.eid
46 +        cnx.commit()
47 +
48 +        rql = 'SET T assigned_to U WHERE U is CWUSER, U login %(user)s, T eid %(ticket)s'
49 +
50 +        # prj1client could not set the assigned_to relation
51 +        cnx = self.mylogin('prj1client')
52 +        cu = cnx.cursor()
53 +        with self.assertRaises(Unauthorized):
54 +            cu.execute(rql, {'user': 'prj1developer', 'ticket': ticket_eid})
55 +            cnx.commit()
56 +        cnx.rollback()
57 +
58 +        # staffuser can set the assigned_to relation
59 +        cnx = self.mylogin('staffuser')
60 +        cu = cnx.cursor()
61 +        cu.execute(rql, {'user': 'prj1developer', 'ticket': ticket_eid})
62 +        cnx.commit()
63 +        rset = self.execute('Any U WHERE T is Ticket, T assigned_to U')
64 +        self.assertEqual(len(rset), 1)
65 +        self.assertEqual(rset.get_entity(0, 0).login, 'prj1developer')
66 +
67 +    def test_assigned_to_relation_to_deactivated_user(self):
68 +        user = self.create_user(self.session, u'deactivateddev',
69 +                                groups=('users', 'proj1developers'))
70 +        self.commit()
71 +        user.cw_adapt_to('IWorkflowable').fire_transition('deactivate')
72 +        self.commit()
73 +
74 +        cnx = self.mylogin('staffuser')
75 +        cu = cnx.cursor()
76 +
77 +        rql = create_project_rql("todoby_proj")
78 +        cu.execute(*rql)
79 +
80 +        rql = create_ticket_rql('a ticket', 'todoby_proj')
81 +        ticket = cu.execute(*rql).get_entity(0, 0)
82 +        ticket_eid = ticket.eid
83 +        cnx.commit()
84 +
85 +        rql = 'SET T assigned_to U WHERE U is CWUSER, U login %(user)s, T eid %(ticket)s'
86 +        cu.execute(rql, {'user': 'deactivated', 'ticket': ticket_eid})
87 +        cnx.commit()
88 +
89 +        rset = cu.execute('Any U WHERE T eid %(ticket)s, T assigned_to U',
90 +                          {'ticket': ticket_eid})
91 +        self.assertEqual(0, len(rset))
92 +
93  if __name__ == '__main__':
94      from logilab.common.testlib import unittest_main
95      unittest_main()
diff --git a/views/project.py b/views/project.py
@@ -95,16 +95,17 @@
96      SORT_DEFS = (('in_state', 'S'), ('num', 'VN'), ('type', 'TT'), ('priority', 'TP'))
97      TICKET_DEFAULT_STATE_RESTR = 'S name IN ("created","identified","released","scheduled")'
98 
99      def tickets_rql(self):
100          # prefetch everything we can for optimization
101 -        return ('Any T,TTI,TT,TP,TD,TDF,TCD,TMD,S,SN,V,VN,U,UL %s WHERE '
102 +        return ('Any T,TTI,TT,TP,TD,TDF,TCD,TMD,S,SN,V,VN,U,UL,TDU %s WHERE '
103                  'T title TTI, T type TT, T priority TP, '
104                  'T description TD, T description_format TDF, '
105                  'T creation_date TCD, T modification_date TMD, '
106                  'T in_state S, S name SN, '
107                  'T done_in V?, V num VN, '
108 +                'T assigned_to TDU?, '
109                  'T created_by U?, U login UL, '
110                  'T concerns P, P eid %%(x)s'
111                  % fixed_orderby_rql(self.SORT_DEFS))
112 
113      def active_tickets_rql(self):
@@ -131,21 +132,23 @@
114 
115  class ProjectTicketsTable(tableview.EntityTableView):
116      __regid__ = 'tracker.tickets.table'
117      __select__ = is_instance('Ticket')
118      columns = ['ticket', 'type', 'priority', 'in_state', 'done_in',
119 -               'creation_date', 'modification_date', 'created_by']
120 +               'creation_date', 'modification_date', 'created_by', 'assigned_to']
121      column_renderers = {
122          'ticket': tableview.MainEntityColRenderer(),
123          'creation_date': RelativeDateColRenderer(header=_('created')),
124          'modification_date': RelativeDateColRenderer(header=_('modified')),
125          'created_by': tableview.RelatedEntityColRenderer(
126                  getrelated=lambda x: x.creator),
127          'in_state': tableview.EntityTableColRenderer(
128                  renderfunc=lambda w,x: w(x.cw_adapt_to('IWorkflowable').printable_state)),
129          'done_in': tableview.RelatedEntityColRenderer(
130                  getrelated=lambda x: x.done_in and x.done_in[0] or None),
131 +        'assigned_to': tableview.RelatedEntityColRenderer(
132 +                getrelated=lambda x: x.assigned_to and x.assigned_to[0] or None),
133          }
134      layout_args = {
135          'display_filter': 'top',
136          'add_view_actions': True,
137          }