source: timingandestimationplugin/branches/trac1.0-Permissions/timingandestimationplugin/ticket_daemon.py

Last change on this file was 17107, checked in by Russ Tyndall, 5 years ago

1.5.9(b) - fixed Decimal cast in ticket_daemon re #13405

File size: 7.7 KB
RevLine 
[5162]1from trac.ticket import ITicketChangeListener, Ticket, ITicketManipulator
[4271]2from trac.perm import PermissionCache
[1119]3from trac.core import *
[1710]4import datetime
[8141]5import dbhelper
[1119]6
[1710]7def identity(x):
8    return x;
9
[6268]10def convertfloat(x):
11    "some european countries use , as the decimal separator"
[16688]12    if not x:
13        return 0.0
14    if isinstance(x, float) or isinstance(x, int):
15        return x
[6268]16    x = str(x).strip()
17    if len(x) > 0:
18        return float(x.replace(',','.'))
19    else: 
20        return 0.0
21
[2015]22try:
23    import trac.util.datefmt
[8131]24    to_timestamp = trac.util.datefmt.to_utimestamp
[2015]25except Exception:
26    to_timestamp = identity
[1710]27
[2015]28
[1119]29def save_custom_field_value( db, ticket_id, field, value ):
30    cursor = db.cursor();
31    cursor.execute("SELECT * FROM ticket_custom " 
32                   "WHERE ticket=%s and name=%s", (ticket_id, field))
33    if cursor.fetchone():
34        cursor.execute("UPDATE ticket_custom SET value=%s "
35                       "WHERE ticket=%s AND name=%s",
36                       (value, ticket_id, field))
37    else:
[1578]38        cursor.execute("INSERT INTO ticket_custom (ticket,name, "
[1119]39                       "value) VALUES(%s,%s,%s)",
[1578]40                       (ticket_id, field, value))
[14419]41
42def update_hours_to_floats(db, ticket_id):
43    cursor = db.cursor()
44    cursor.execute("SELECT time, newvalue FROM ticket_change"
45                   " WHERE newvalue like '%,%' AND  ticket=%s AND field='hours'", 
46                   (ticket_id,))
47    data = list(cursor.fetchall())
48    for (time, newvalue) in data:
49        cursor.execute("UPDATE ticket_change SET newvalue=%s "
50                       "WHERE ticket=%s AND time=%s AND field='hours'",
51                       (str(convertfloat(newvalue)), ticket_id, time))
[2893]52   
[14401]53def update_totalhours_custom( db, ticket_id):
54    cursor = db.cursor()
55    sumSql = """
56       (SELECT SUM( CASE WHEN newvalue = '' OR newvalue IS NULL THEN 0
[17107]57                         ELSE CAST( newvalue AS DECIMAL(10,2) ) END ) as total
[14401]58          FROM ticket_change
59         WHERE ticket=%s and field='hours')  """
60    cursor.execute("UPDATE ticket_custom SET value="+sumSql+
61                   "WHERE ticket=%s AND name='totalhours'",
62               (ticket_id,ticket_id))
63    if cursor.rowcount==0:
64        cursor.execute("INSERT INTO ticket_custom (name, value, ticket) "+
65                       "VALUES('totalhours',"+sumSql+",%s)",
66                       (ticket_id,ticket_id))
[4164]67
[14401]68def insert_totalhours_changes( db, ticket_id):
69    sql = """
70       INSERT INTO ticket_change (ticket, author, time, field, oldvalue, newvalue)
71       SELECT ticket, author, time, 'totalhours', 
72               (SELECT SUM( CASE WHEN newvalue = '' OR newvalue IS NULL THEN 0
[17107]73                           ELSE CAST( newvalue AS DECIMAL(10,2) ) END ) as total
[14401]74               FROM ticket_change as guts
75               WHERE guts.ticket = ticket_change.ticket AND guts.field='hours'
76                 AND guts.time < ticket_change.time
77              ) as oldvalue,
78              (SELECT SUM( CASE WHEN newvalue = '' OR newvalue IS NULL THEN 0
[17107]79                           ELSE CAST( newvalue AS DECIMAL(10,2) ) END ) as total
[14401]80               FROM ticket_change as guts
81               WHERE guts.ticket = ticket_change.ticket AND guts.field='hours'
82                 AND guts.time <= ticket_change.time
83              ) as newvalue
84          FROM ticket_change
85         WHERE ticket=%s and field='hours'
86           AND NOT EXISTS( SELECT ticket
87                             FROM ticket_change as guts
88                            WHERE guts.ticket=ticket_change.ticket
89                              AND guts.author=ticket_change.author
90                              AND guts.time=ticket_change.time
91                              AND field='totalhours')
[4166]92    """
[14401]93    cursor = db.cursor()
94    cursor.execute(sql, (ticket_id,))
[4164]95
[14401]96def delete_ticket_change( comp, ticket_id, change_time, field):
[4271]97    """ removes a ticket change from the database """
[12828]98    if isinstance(change_time, datetime.datetime):
99        change_time = to_timestamp(change_time)
[4271]100    sql = """DELETE FROM ticket_change 
[14401]101             WHERE ticket=%s and time=%s and field=%s""" 
102    dbhelper.execute_non_query(comp.env, sql, ticket_id, change_time, field)
[4271]103
[1119]104class TimeTrackingTicketObserver(Component):
[4270]105    implements(ITicketChangeListener)
[1119]106    def __init__(self):
107        pass
108
[14402]109    def watch_hours(self, ticket, author=None):
[4164]110        ticket_id = ticket.id
[14401]111        hours = convertfloat(ticket['hours'])
112        change_time = ticket['changetime']
113        # no hours, changed
114        if hours == 0:
115            return
116
[14402]117        # if we dont have an author just skip permissions check
118        # its probably not important as this is generated data
119        # and it was probably caused by someone with ticket admin modifying history
120        if author:
121            self.log.debug("Checking permissions")       
122            perm = PermissionCache(self.env, author)
123            if not perm or not perm.has_permission("TIME_RECORD"):
124                self.log.debug("Skipping recording because no permission to affect time")
125                tup = (ticket_id, change_time, "hours")
126                self.log.debug("deleting ticket change %s %s %s %s" % tup)
127                try:
128                    delete_ticket_change(self, ticket_id, change_time, "hours")
129                except Exception, e:
130                    self.log.exception("FAIL: %s" % e)
131                self.log.debug("hours change deleted")
132                return
133            self.log.debug("passed permissions check")
[8141]134        @self.env.with_transaction()
135        def fn(db):
[14419]136            update_hours_to_floats(db, ticket_id)
[14401]137            save_custom_field_value( db, ticket_id, "hours", '0')
138            insert_totalhours_changes( db, ticket_id )
139            update_totalhours_custom ( db, ticket_id )
[4164]140
[1119]141    def ticket_created(self, ticket):
142        """Called when a ticket is created."""
[14407]143        hours = convertfloat(ticket['hours'])
144        self.watch_hours(ticket, ticket['reporter']) # clears the hours
[14408]145        #makes the hours a ticket change like all the other hours records
146        t = Ticket (self.env, ticket.id)
[14407]147        if hours > 0:
[14408]148            t['hours']=str(hours);
149            t.save_changes(ticket['reporter'])
[1119]150
[1286]151    def ticket_changed(self, ticket, comment, author, old_values):
[1119]152        """Called when a ticket is modified.
153       
154        `old_values` is a dictionary containing the previous values of the
155        fields that have changed.
156        """
[14402]157        self.watch_hours(ticket, author)
158
[14863]159    def ticket_change_deleted(self, ticket, cdate, changes):
[14402]160        """called when a ticket change is deleted"""
[1119]161        self.watch_hours(ticket)
162
163    def ticket_deleted(self, ticket):
164        """Called when a ticket is deleted."""
[5162]165
166class TimeTrackingTicketValidator(Component):
167    implements(ITicketManipulator)
168
169    def __init__(self):
170        pass
171
172    def prepare_ticket(req, ticket, fields, actions):
173        """not currently called"""
174
175    def validate_ticket(self, req, ticket):
176        """Validate a ticket after it's been populated from user input.
177
178        Must return a list of `(field, message)` tuples, one for each problem
179        detected. `field` can be `None` to indicate an overall problem with the
180        ticket. Therefore, a return value of `[]` means everything is OK."""
181        errors = []
182        try:
183            convertfloat(ticket.values['hours'])
[6902]184        except KeyError:
185            self.log.exception("The hours field was not submitted")
[5162]186        except ValueError:
187            errors.append(('Add Hours to Ticket', 'Value must be a number'))
188        try:
189            convertfloat(ticket.values['estimatedhours'])
[6902]190        except KeyError:
191            self.log.exception("The estimatedhours field was not submitted")
[5162]192        except ValueError:
193            errors.append(('Estimated Number of Hours', 'Value must be a number'))
194        return errors
Note: See TracBrowser for help on using the repository browser.