Switch to unified view

a/ForgeTracker/forgetracker/model/ticket.py b/ForgeTracker/forgetracker/model/ticket.py
...
...
71
        session = project_orm_session
71
        session = project_orm_session
72
        indexes = [ 'app_config_id' ]
72
        indexes = [ 'app_config_id' ]
73
73
74
    type_s = 'Globals'
74
    type_s = 'Globals'
75
    _id = FieldProperty(schema.ObjectId)
75
    _id = FieldProperty(schema.ObjectId)
76
    app_config_id = ForeignIdProperty('AppConfig', if_missing=lambda:c.app.config._id)
76
    app_config_id = ForeignIdProperty(AppConfig, if_missing=lambda:c.app.config._id)
77
    app_config = RelationProperty(AppConfig, via='app_config_id')
77
    last_ticket_num = FieldProperty(int)
78
    last_ticket_num = FieldProperty(int)
78
    status_names = FieldProperty(str)
79
    status_names = FieldProperty(str)
79
    open_status_names = FieldProperty(str)
80
    open_status_names = FieldProperty(str)
80
    closed_status_names = FieldProperty(str)
81
    closed_status_names = FieldProperty(str)
81
    milestone_names = FieldProperty(str, if_missing='')
82
    milestone_names = FieldProperty(str, if_missing='')
...
...
95
                                                            'created_date': True,
96
                                                            'created_date': True,
96
                                                            'mod_date': True,
97
                                                            'mod_date': True,
97
                                                            'labels': False,
98
                                                            'labels': False,
98
                                                            })
99
                                                            })
99
100
100
    @classmethod
101
    def next_ticket_num(cls):
101
    def next_ticket_num(self):
102
        gbl = cls.query.find_and_modify(
102
        gbl = Globals.query.find_and_modify(
103
            query=dict(app_config_id=c.app.config._id),
103
            query=dict(app_config_id=self.app_config_id),
104
            update={'$inc': { 'last_ticket_num': 1}},
104
            update={'$inc': { 'last_ticket_num': 1}},
105
            new=True)
105
            new=True)
106
        session(cls).expunge(gbl)
106
        session(gbl).expunge(gbl)
107
        return gbl.last_ticket_num
107
        return gbl.last_ticket_num
108
108
109
    @property
109
    @property
110
    def all_status_names(self):
110
    def all_status_names(self):
111
        return ' '.join([self.open_status_names, self.closed_status_names])
111
        return ' '.join([self.open_status_names, self.closed_status_names])
...
...
187
        d = dict(name=name, hits=0, closed=0)
187
        d = dict(name=name, hits=0, closed=0)
188
        if not (fld_name and m_name):
188
        if not (fld_name and m_name):
189
            return d
189
            return d
190
        mongo_query = {'custom_fields.%s' % fld_name: m_name}
190
        mongo_query = {'custom_fields.%s' % fld_name: m_name}
191
        r = Ticket.query.find(dict(
191
        r = Ticket.query.find(dict(
192
            mongo_query, app_config_id=c.app.config._id, deleted=False))
192
            mongo_query, app_config_id=self.app_config_id, deleted=False))
193
        tickets = [t for t in r if security.has_access(t, 'read')]
193
        tickets = [t for t in r if security.has_access(t, 'read')]
194
        d['hits'] = len(tickets)
194
        d['hits'] = len(tickets)
195
        d['closed'] = sum(1 for t in tickets
195
        d['closed'] = sum(1 for t in tickets
196
                          if t.status in c.app.globals.set_of_closed_status_names)
196
                          if t.status in self.set_of_closed_status_names)
197
        return d
197
        return d
198
198
199
    def invalidate_bin_counts(self):
199
    def invalidate_bin_counts(self):
200
        '''Force expiry of bin counts and queue them to be updated.'''
200
        '''Force expiry of bin counts and queue them to be updated.'''
201
        # To prevent multiple calls to this method from piling on redundant
201
        # To prevent multiple calls to this method from piling on redundant
...
...
227
            for field in self.custom_fields
227
            for field in self.custom_fields
228
            if field.get('show_in_search')]
228
            if field.get('show_in_search')]
229
229
230
    def has_deleted_tickets(self):
230
    def has_deleted_tickets(self):
231
        return Ticket.query.find(dict(
231
        return Ticket.query.find(dict(
232
            app_config_id=c.app.config._id, deleted=True)).count() > 0
232
            app_config_id=self.app_config_id, deleted=True)).count() > 0
233
233
234
    def move_tickets(self, ticket_ids, destination_tracker_id):
234
    def move_tickets(self, ticket_ids, destination_tracker_id):
235
        tracker = AppConfig.query.get(_id=destination_tracker_id)
235
        tracker = AppConfig.query.get(_id=destination_tracker_id)
236
        tickets = Ticket.query.find(dict(
236
        tickets = Ticket.query.find(dict(
237
            _id={'$in': [ObjectId(id) for id in ticket_ids]},
237
            _id={'$in': [ObjectId(id) for id in ticket_ids]},
238
            app_config_id=c.app.config._id)).sort('ticket_num').all()
238
            app_config_id=self.app_config_id)).sort('ticket_num').all()
239
        filtered = self.filtered_by_subscription({t._id: t for t in tickets})
239
        filtered = self.filtered_by_subscription({t._id: t for t in tickets})
240
        original_ticket_nums = {t._id: t.ticket_num for t in tickets}
240
        original_ticket_nums = {t._id: t.ticket_num for t in tickets}
241
        users = User.query.find({'_id': {'$in': filtered.keys()}}).all()
241
        users = User.query.find({'_id': {'$in': filtered.keys()}}).all()
242
        moved_tickets = {}
242
        moved_tickets = {}
243
        for ticket in tickets:
243
        for ticket in tickets:
...
...
245
            moved_tickets[moved._id] = moved
245
            moved_tickets[moved._id] = moved
246
        mail = dict(
246
        mail = dict(
247
            fromaddr = str(c.user.email_address_header()),
247
            fromaddr = str(c.user.email_address_header()),
248
            reply_to = str(c.user.email_address_header()),
248
            reply_to = str(c.user.email_address_header()),
249
            subject = '[%s:%s] Mass ticket moving by %s' % (c.project.shortname,
249
            subject = '[%s:%s] Mass ticket moving by %s' % (c.project.shortname,
250
                                                          c.app.config.options.mount_point,
250
                                                          self.app_config.options.mount_point,
251
                                                          c.user.display_name))
251
                                                          c.user.display_name))
252
        tmpl = g.jinja2_env.get_template('forgetracker:data/mass_move_report.html')
252
        tmpl = g.jinja2_env.get_template('forgetracker:data/mass_move_report.html')
253
253
254
        tmpl_context = {
254
        tmpl_context = {
255
            'original_tracker': '%s:%s' % (c.project.shortname,
255
            'original_tracker': '%s:%s' % (c.project.shortname,
256
                                           c.app.config.options.mount_point),
256
                                           self.app_config.options.mount_point),
257
            'destination_tracker': '%s:%s' % (tracker.project.shortname,
257
            'destination_tracker': '%s:%s' % (tracker.project.shortname,
258
                                              tracker.options.mount_point),
258
                                              tracker.options.mount_point),
259
            'tickets': [],
259
            'tickets': [],
260
        }
260
        }
261
        for user in users:
261
        for user in users:
...
...
268
                message_id = h.gen_message_id(),
268
                message_id = h.gen_message_id(),
269
                text = tmpl.render(tmpl_context),
269
                text = tmpl.render(tmpl_context),
270
                destinations = [str(user._id)]))
270
                destinations = [str(user._id)]))
271
            mail_tasks.sendmail.post(**mail)
271
            mail_tasks.sendmail.post(**mail)
272
272
273
        if c.app.config.options.get('TicketMonitoringType') in (
273
        if self.app_config.options.get('TicketMonitoringType') in (
274
                'AllTicketChanges', 'AllPublicTicketChanges'):
274
                'AllTicketChanges', 'AllPublicTicketChanges'):
275
            monitoring_email = c.app.config.options.get('TicketMonitoringEmail')
275
            monitoring_email = self.app_config.options.get('TicketMonitoringEmail')
276
            tmpl_context['tickets'] = [{
276
            tmpl_context['tickets'] = [{
277
                    'original_num': original_ticket_nums[_id],
277
                    'original_num': original_ticket_nums[_id],
278
                    'destination_num': moved_tickets[_id].ticket_num,
278
                    'destination_num': moved_tickets[_id].ticket_num,
279
                    'summary': moved_tickets[_id].summary
279
                    'summary': moved_tickets[_id].summary
280
                } for _id, t in moved_tickets.iteritems()
280
                } for _id, t in moved_tickets.iteritems()
281
                  if (not t.private or
281
                  if (not t.private or
282
                      c.app.config.options.get('TicketMonitoringType') ==
282
                      self.app_config.options.get('TicketMonitoringType') ==
283
                      'AllTicketChanges')]
283
                      'AllTicketChanges')]
284
            if len(tmpl_context['tickets']) > 0:
284
            if len(tmpl_context['tickets']) > 0:
285
                mail.update(dict(
285
                mail.update(dict(
286
                    message_id = h.gen_message_id(),
286
                    message_id = h.gen_message_id(),
287
                    text = tmpl.render(tmpl_context),
287
                    text = tmpl.render(tmpl_context),
288
                    destinations = [monitoring_email]))
288
                    destinations = [monitoring_email]))
289
                mail_tasks.sendmail.post(**mail)
289
                mail_tasks.sendmail.post(**mail)
290
290
291
        moved_from = '%s/%s' % (c.project.shortname, c.app.config.options.mount_point)
291
        moved_from = '%s/%s' % (c.project.shortname, self.app_config.options.mount_point)
292
        moved_to = '%s/%s' % (tracker.project.shortname, tracker.options.mount_point)
292
        moved_to = '%s/%s' % (tracker.project.shortname, tracker.options.mount_point)
293
        text = 'Tickets moved from %s to %s' % (moved_from, moved_to)
293
        text = 'Tickets moved from %s to %s' % (moved_from, moved_to)
294
        Notification.post_user(c.user, None, 'flash', text=text)
294
        Notification.post_user(c.user, None, 'flash', text=text)
295
295
296
    def update_tickets(self, **post_data):
296
    def update_tickets(self, **post_data):
297
        from forgetracker.tracker_main import get_change_text, get_label
297
        from forgetracker.tracker_main import get_change_text, get_label
298
        tickets = Ticket.query.find(dict(
298
        tickets = Ticket.query.find(dict(
299
                _id={'$in':[ObjectId(id) for id in aslist(post_data['__ticket_ids'])]},
299
                _id={'$in':[ObjectId(id) for id in aslist(post_data['__ticket_ids'])]},
300
                app_config_id=c.app.config._id)).all()
300
                app_config_id=self.app_config_id)).all()
301
301
302
        fields = set(['status'])
302
        fields = set(['status'])
303
        values = {}
303
        values = {}
304
        for k in fields:
304
        for k in fields:
305
            v = post_data.get(k)
305
            v = post_data.get(k)
...
...
312
            if user:
312
            if user:
313
                values['assigned_to_id'] = user._id
313
                values['assigned_to_id'] = user._id
314
314
315
        custom_values = {}
315
        custom_values = {}
316
        custom_fields = {}
316
        custom_fields = {}
317
        for cf in c.app.globals.custom_fields or []:
317
        for cf in self.custom_fields or []:
318
            v = post_data.get(cf.name)
318
            v = post_data.get(cf.name)
319
            if v:
319
            if v:
320
                custom_values[cf.name] = v
320
                custom_values[cf.name] = v
321
                custom_fields[cf.name] = cf
321
                custom_fields[cf.name] = cf
322
322
...
...
367
                yield (changed_tickets[t_id], jinja2.Markup(changes[t_id]))
367
                yield (changed_tickets[t_id], jinja2.Markup(changes[t_id]))
368
        mail = dict(
368
        mail = dict(
369
            fromaddr = str(c.user._id),
369
            fromaddr = str(c.user._id),
370
            reply_to = str(c.user._id),
370
            reply_to = str(c.user._id),
371
            subject = '[%s:%s] Mass edit changes by %s' % (c.project.shortname,
371
            subject = '[%s:%s] Mass edit changes by %s' % (c.project.shortname,
372
                                                           c.app.config.options.mount_point,
372
                                                           self.app_config.options.mount_point,
373
                                                           c.user.display_name),
373
                                                           c.user.display_name),
374
        )
374
        )
375
        tmpl = g.jinja2_env.get_template('forgetracker:data/mass_report.html')
375
        tmpl = g.jinja2_env.get_template('forgetracker:data/mass_report.html')
376
        head = []
376
        head = []
377
        for f, v in sorted(values.iteritems()):
377
        for f, v in sorted(values.iteritems()):
...
...
392
                message_id = h.gen_message_id(),
392
                message_id = h.gen_message_id(),
393
                text = tmpl.render(tmpl_context),
393
                text = tmpl.render(tmpl_context),
394
                destinations = [str(user._id)]))
394
                destinations = [str(user._id)]))
395
            mail_tasks.sendmail.post(**mail)
395
            mail_tasks.sendmail.post(**mail)
396
396
397
        if c.app.config.options.get('TicketMonitoringType') in (
397
        if self.app_config.options.get('TicketMonitoringType') in (
398
                'AllTicketChanges', 'AllPublicTicketChanges'):
398
                'AllTicketChanges', 'AllPublicTicketChanges'):
399
            monitoring_email = c.app.config.options.get('TicketMonitoringEmail')
399
            monitoring_email = self.app_config.options.get('TicketMonitoringEmail')
400
            visible_changes = []
400
            visible_changes = []
401
            for t_id, t in changed_tickets.items():
401
            for t_id, t in changed_tickets.items():
402
                if (not t.private or
402
                if (not t.private or
403
                        c.app.config.options.get('TicketMonitoringType') ==
403
                        self.app_config.options.get('TicketMonitoringType') ==
404
                        'AllTicketChanges'):
404
                        'AllTicketChanges'):
405
                    visible_changes.append(
405
                    visible_changes.append(
406
                            (changed_tickets[t_id], jinja2.Markup(changes[t_id])))
406
                            (changed_tickets[t_id], jinja2.Markup(changes[t_id])))
407
            if visible_changes:
407
            if visible_changes:
408
                tmpl_context['data'].update({'changes': visible_changes})
408
                tmpl_context['data'].update({'changes': visible_changes})
...
...
410
                    message_id = h.gen_message_id(),
410
                    message_id = h.gen_message_id(),
411
                    text = tmpl.render(tmpl_context),
411
                    text = tmpl.render(tmpl_context),
412
                    destinations = [monitoring_email]))
412
                    destinations = [monitoring_email]))
413
                mail_tasks.sendmail.post(**mail)
413
                mail_tasks.sendmail.post(**mail)
414
414
415
        c.app.globals.invalidate_bin_counts()
415
        self.invalidate_bin_counts()
416
        ThreadLocalORMSession.flush_all()
416
        ThreadLocalORMSession.flush_all()
417
        app = '%s/%s' % (c.project.shortname, c.app.config.options.mount_point)
417
        app = '%s/%s' % (c.project.shortname, self.app_config.options.mount_point)
418
        count = len(tickets)
418
        count = len(tickets)
419
        text = 'Updated {} ticket{} in {}'.format(count, 's' if count != 1 else '', app)
419
        text = 'Updated {} ticket{} in {}'.format(count, 's' if count != 1 else '', app)
420
        Notification.post_user(c.user, None, 'flash', text=text)
420
        Notification.post_user(c.user, None, 'flash', text=text)
421
421
422
    def filtered_by_subscription(self, tickets, project_id=None, app_config_id=None):
422
    def filtered_by_subscription(self, tickets, project_id=None, app_config_id=None):
423
        p_id = project_id if project_id else c.project._id
423
        p_id = project_id if project_id else c.project._id
424
        ac_id = app_config_id if app_config_id else c.app.config._id
424
        ac_id = app_config_id if app_config_id else self.app_config_id
425
        ticket_ids = tickets.keys()
425
        ticket_ids = tickets.keys()
426
        tickets_index_id = {ticket.index_id(): t_id for t_id, ticket in tickets.iteritems()}
426
        tickets_index_id = {ticket.index_id(): t_id for t_id, ticket in tickets.iteritems()}
427
        subscriptions = Mailbox.query.find({
427
        subscriptions = Mailbox.query.find({
428
            'project_id': p_id,
428
            'project_id': p_id,
429
            'app_config_id': ac_id,
429
            'app_config_id': ac_id,
...
...
676
    def open_or_closed(self):
676
    def open_or_closed(self):
677
        return 'closed' if self.status in c.app.globals.set_of_closed_status_names else 'open'
677
        return 'closed' if self.status in c.app.globals.set_of_closed_status_names else 'open'
678
678
679
    @property
679
    @property
680
    def monitoring_email(self):
680
    def monitoring_email(self):
681
        return c.app.config.options.get('TicketMonitoringEmail')
681
        return self.app_config.options.get('TicketMonitoringEmail')
682
682
683
    @property
683
    @property
684
    def notify_post(self):
684
    def notify_post(self):
685
        monitoring_type = c.app.config.options.get('TicketMonitoringType')
685
        monitoring_type = self.app_config.options.get('TicketMonitoringType')
686
        return monitoring_type == 'AllTicketChanges' or (
686
        return monitoring_type == 'AllTicketChanges' or (
687
                monitoring_type == 'AllPublicTicketChanges' and
687
                monitoring_type == 'AllPublicTicketChanges' and
688
                not self.private)
688
                not self.private)
689
689
690
    def get_custom_user(self, custom_user_field_name):
690
    def get_custom_user(self, custom_user_field_name):