Beispiel #1
0
 def commit(self):
     ss = VersionedArtifact.commit(self)
     session(self).flush()
     if self.version > 1:
         v1 = self.get_version(self.version - 1)
         v2 = self
         la = [line + '\n' for line in v1.text.splitlines()]
         lb = [line + '\n' for line in v2.text.splitlines()]
         diff = ''.join(difflib.unified_diff(
             la, lb,
             'v%d' % v1.version,
             'v%d' % v2.version))
         description = '<pre>' + diff + '</pre>'
         if v1.title != v2.title:
             subject = '%s renamed page %s to %s' % (
                 context.user.username, v1.title, v2.title)
         else:
             subject = '%s modified page %s' % (
                 context.user.username, self.title)
     else:
         description = self.text
         subject = '%s created page %s' % (
             context.user.username, self.title)
     Feed.post(self, title=None, description=description)
     Notification.post(
         artifact=self, topic='metadata', text=description, subject=subject)
     return ss
Beispiel #2
0
 def reply(self, text):
     Feed.post(self, text)
     # Get thread
     thread = Thread.query.get(artifact_id=self._id)
     return Post(discussion_id=thread.discussion_id,
                 thread_id=thread._id,
                 text=text)
Beispiel #3
0
 def commit(self):
     ss = VersionedArtifact.commit(self)
     session(self).flush()
     if self.version > 1:
         v1 = self.get_version(self.version - 1)
         v2 = self
         la = [line + '\n' for line in v1.text.splitlines()]
         lb = [line + '\n' for line in v2.text.splitlines()]
         diff = ''.join(difflib.unified_diff(
             la, lb,
             'v%d' % v1.version,
             'v%d' % v2.version))
         description = '<pre>' + diff + '</pre>'
         if v1.title != v2.title:
             subject = '%s renamed page %s to %s' % (
                 context.user.username, v1.title, v2.title)
         else:
             subject = '%s modified page %s' % (
                 context.user.username, self.title)
     else:
         description = self.text
         subject = '%s created page %s' % (
             context.user.username, self.title)
     Feed.post(self, title=None, description=description)
     Notification.post(
         artifact=self, topic='metadata', text=description, subject=subject)
     return ss
Beispiel #4
0
 def commit(self):
     VersionedArtifact.commit(self)
     monitoring_email = self.app.config.options.get('TicketMonitoringEmail')
     if self.version > 1:
         hist = TicketHistory.query.get(artifact_id=self._id, version=self.version-1)
         old = hist.data
         changes = ['Ticket %s has been modified: %s' % (
                 self.ticket_num, self.summary),
                    'Edited By: %s (%s)' % (c.user.get_pref('display_name'), c.user.username)]
         fields = [
             ('Summary', old.summary, self.summary),
             ('Status', old.status, self.status) ]
         if old.status != self.status and self.status in c.app.globals.set_of_closed_status_names:
             h.log_action(log, 'closed').info('')
             g.statsUpdater.ticketEvent("closed", self, self.project, self.assigned_to)
         for key in self.custom_fields:
             fields.append((key, old.custom_fields.get(key, ''), self.custom_fields[key]))
         for title, o, n in fields:
             if o != n:
                 changes.append('%s updated: %r => %r' % (
                         title, o, n))
         o = hist.assigned_to
         n = self.assigned_to
         if o != n:
             changes.append('Owner updated: %r => %r' % (
                     o and o.username, n and n.username))
             self.subscribe(user=n)
             g.statsUpdater.ticketEvent("assigned", self, self.project, n)
             if o:
                 g.statsUpdater.ticketEvent("revoked", self, self.project, o)
         if old.description != self.description:
             changes.append('Description updated:')
             changes.append('\n'.join(
                     difflib.unified_diff(
                         a=old.description.split('\n'),
                         b=self.description.split('\n'),
                         fromfile='description-old',
                         tofile='description-new')))
         description = '\n'.join(changes)
     else:
         self.subscribe()
         if self.assigned_to_id:
             user = User.query.get(_id=self.assigned_to_id)
             g.statsUpdater.ticketEvent("assigned", self, self.project, user)
             self.subscribe(user=user)
         description = ''
         subject = self.email_subject
         Thread.new(discussion_id=self.app_config.discussion_id,
                ref_id=self.index_id())
         n = Notification.post(artifact=self, topic='metadata', text=description, subject=subject)
         if monitoring_email and n and (not self.private or
                 self.app.config.options.get('TicketMonitoringType') in (
                     'NewTicketsOnly', 'AllTicketChanges')):
             n.send_simple(monitoring_email)
     Feed.post(
         self,
         title=self.summary,
         description=description if description else self.description,
         author=self.reported_by,
         pubdate=self.created_date)
Beispiel #5
0
 def reply(self, text):
     Feed.post(self, text)
     # Get thread
     thread = Thread.query.get(artifact_id=self._id)
     return Post(
         discussion_id=thread.discussion_id,
         thread_id=thread._id,
         text=text)
 def commit(self):
     VersionedArtifact.commit(self)
     monitoring_email = self.app.config.options.get('TicketMonitoringEmail')
     if self.version > 1:
         hist = TicketHistory.query.get(artifact_id=self._id,
                                        version=self.version - 1)
         old = hist.data
         changes = [
             'Ticket %s has been modified: %s' %
             (self.ticket_num, self.summary),
             'Edited By: %s (%s)' %
             (c.user.get_pref('display_name'), c.user.username)
         ]
         fields = [('Summary', old.summary, self.summary),
                   ('Status', old.status, self.status)]
         if old.status != self.status and self.status in c.app.globals.set_of_closed_status_names:
             h.log_action(log, 'closed').info('')
         for key in self.custom_fields:
             fields.append(
                 (key, old.custom_fields.get(key,
                                             ''), self.custom_fields[key]))
         for title, o, n in fields:
             if o != n:
                 changes.append('%s updated: %r => %r' % (title, o, n))
         o = hist.assigned_to
         n = self.assigned_to
         if o != n:
             changes.append('Owner updated: %r => %r' %
                            (o and o.username, n and n.username))
             self.subscribe(user=n)
         if old.description != self.description:
             changes.append('Description updated:')
             changes.append('\n'.join(
                 difflib.unified_diff(a=old.description.split('\n'),
                                      b=self.description.split('\n'),
                                      fromfile='description-old',
                                      tofile='description-new')))
         description = '\n'.join(changes)
     else:
         self.subscribe()
         if self.assigned_to_id:
             self.subscribe(user=User.query.get(_id=self.assigned_to_id))
         description = ''
         subject = self.email_subject
         Thread(discussion_id=self.app_config.discussion_id,
                ref_id=self.index_id())
         n = Notification.post(artifact=self,
                               topic='metadata',
                               text=description,
                               subject=subject)
         if monitoring_email and n:
             n.send_simple(monitoring_email)
     Feed.post(self, description)
def send_notifications(repo, commit_ids):
    '''Create appropriate notification and feed objects for a refresh'''
    from allura.model import Feed, Notification
    commit_msgs = []
    base_url = tg.config['base_url']
    for oids in utils.chunked_iter(commit_ids, QSIZE):
        chunk = list(oids)
        index = dict(
            (doc._id, doc)
            for doc in Commit.query.find(dict(_id={'$in': chunk})))
        for oid in chunk:
            ci = index[oid]
            href = repo.url_for_commit(oid)
            title = _title(ci.message)
            summary = _summarize(ci.message)
            Feed.post(
                repo, title=title,
                description='%s<br><a href="%s">View Changes</a>' % (
                    summary, href),
                author_link=ci.author_url,
                author_name=ci.authored.name,
                link=href,
                unique_id=href)
            branches = repo.symbolics_for_commit(ci)[0]
            commit_msgs.append('%s: %s by %s %s%s' % (
                ",".join(b for b in branches),
                summary, ci.authored.name, base_url, ci.url()))
    if commit_msgs:
        if len(commit_msgs) > 1:
            subject = '%d new commits to %s %s' % (
                len(commit_msgs), repo.app.project.name, repo.app.config.options.mount_label)
            text = '\n\n'.join(commit_msgs)
        else:
            subject = '{0} - {1}: {2}'.format(
                repo.shorthand_for_commit(ci._id),
                ci.authored.name,
                summary)
            branches = repo.symbolics_for_commit(ci)[0]
            text_branches = ('%s: ' % ",".join(b for b in branches)
                             if branches else '')
            text = "%s%s %s%s" % (text_branches,
                                  ci.message,
                                  base_url, ci.url())

        Notification.post(
            artifact=repo,
            topic='metadata',
            subject=subject,
            text=text)
def send_notifications(repo, commit_ids):
    '''Create appropriate notification and feed objects for a refresh'''
    from allura.model import Feed, Notification
    commit_msgs = []
    base_url = tg.config.get('base_url', 'sourceforge.net')
    for oids in utils.chunked_iter(commit_ids, QSIZE):
        chunk = list(oids)
        index = dict(
            (doc._id, doc)
            for doc in Commit.query.find(dict(_id={'$in':chunk})))
        for oid in chunk:
            ci = index[oid]
            href = repo.url_for_commit(oid)
            summary = _summarize(ci.message)
            Feed.post(
                repo, title='New commit',
                description='%s<br><a href="%s">View Changes</a>' % (
                    summary, href),
                author_link=ci.author_url,
                author_name=ci.authored.name)
            branches = repo.symbolics_for_commit(ci)[0]
            commit_msgs.append('%s: %s by %s %s%s' % (
                    ",".join(b for b in branches),
                    summary, ci.authored.name, base_url, ci.url()))
    if commit_msgs:
        if len(commit_msgs) > 1:
            subject = '%d new commits to %s %s' % (
                len(commit_msgs), repo.app.project.name, repo.app.config.options.mount_label)
            text='\n\n'.join(commit_msgs)
        else:
            subject = '%s committed to %s %s: %s' % (
                ci.authored.name,
                repo.app.project.name,
                repo.app.config.options.mount_label,
                summary)
            branches = repo.symbolics_for_commit(ci)[0]
            text = "%s: %s %s%s" % (",".join(b for b in branches),
                               ci.message,
                               base_url, ci.url())

        Notification.post(
            artifact=repo,
            topic='metadata',
            subject=subject,
            text=text)
Beispiel #9
0
 def feed(self, since=None, until=None, page=None, limit=None):
     user = c.project.user_project_of
     if request.environ['PATH_INFO'].endswith('.atom'):
         feed_type = 'atom'
     else:
         feed_type = 'rss'
     title = 'Recent posts by %s' % user.display_name
     feed = Feed.feed({'author_link': user.url()}, feed_type, title,
                      c.project.url(), title, since, until, page, limit)
     response.headers['Content-Type'] = ''
     response.content_type = 'application/xml'
     return feed.writeString('utf-8')
    def feed(self, since=None, until=None, page=None, limit=None):
        if request.environ['PATH_INFO'].endswith('.atom'):
            feed_type = 'atom'
        else:
            feed_type = 'rss'
        title = 'Recent posts to %s' % c.app.config.options.mount_label

        feed = Feed.feed(
            dict(project_id=c.project._id, app_config_id=c.app.config._id),
            feed_type, title, c.app.url, title, since, until, page, limit)
        response.headers['Content-Type'] = ''
        response.content_type = 'application/xml'
        return feed.writeString('utf-8')
 def testd(self):
     post = M.BlogPost()
     post.title = 'test'
     post.text = 'test message'
     post.state = 'published'
     post.timestamp = datetime(2012, 10, 29, 9, 57, 21, 465000)
     post.neighborhood_id = c.project.neighborhood_id
     post.make_slug()
     post.commit()
     f = Feed.post(post,
                   title=post.title,
                   description=post.text,
                   author=post.author(),
                   pubdate=post.timestamp)
     assert_equal(f.pubdate, datetime(2012, 10, 29, 9, 57, 21, 465000))
 def testd(self):
     post = M.BlogPost()
     post.title = 'test'
     post.text = 'test message'
     post.state = 'published'
     post.timestamp = datetime(2012, 10, 29, 9, 57, 21, 465000)
     post.neighborhood_id = c.project.neighborhood_id
     post.make_slug()
     post.commit()
     f = Feed.post(
         post,
         title=post.title,
         description=post.text,
         author=post.author(),
         pubdate=post.timestamp)
     assert_equal(f.pubdate, datetime(2012, 10, 29, 9, 57, 21, 465000))
 def test_feed(self):
     t = Ticket(
     app_config_id=c.app.config._id,
     ticket_num=1,
     summary='test ticket',
     description='test description',
     created_date=datetime(2012, 10, 29, 9, 57, 21, 465000))
     assert_equal(t.created_date, datetime(2012, 10, 29, 9, 57, 21, 465000))
     f = Feed.post(
         t,
         title=t.summary,
         description=t.description,
         pubdate=t.created_date)
     assert_equal(f.pubdate, datetime(2012, 10, 29, 9, 57, 21, 465000))
     assert_equal(f.title, 'test ticket')
     assert_equal(f.description, '<div class="markdown_content"><p>test description</p></div>')
 def test_feed(self):
     t = Ticket(app_config_id=c.app.config._id,
                ticket_num=1,
                summary='test ticket',
                description='test description',
                created_date=datetime(2012, 10, 29, 9, 57, 21, 465000))
     assert_equal(t.created_date, datetime(2012, 10, 29, 9, 57, 21, 465000))
     f = Feed.post(t,
                   title=t.summary,
                   description=t.description,
                   pubdate=t.created_date)
     assert_equal(f.pubdate, datetime(2012, 10, 29, 9, 57, 21, 465000))
     assert_equal(f.title, 'test ticket')
     assert_equal(
         f.description,
         '<div class="markdown_content"><p>test description</p></div>')
 def feed(self, since=None, until=None, page=None, limit=None):
     user = c.project.user_project_of
     if request.environ['PATH_INFO'].endswith('.atom'):
         feed_type = 'atom'
     else:
         feed_type = 'rss'
     title = 'Recent posts by %s' % user.display_name
     feed = Feed.feed(
         {'author_link':user.url()},
         feed_type,
         title,
         c.project.url(),
         title,
         since, until, page, limit)
     response.headers['Content-Type'] = ''
     response.content_type = 'application/xml'
     return feed.writeString('utf-8')
Beispiel #16
0
    def feed(self, since=None, until=None, page=None, limit=None):
        if request.environ['PATH_INFO'].endswith('.atom'):
            feed_type = 'atom'
        else:
            feed_type = 'rss'
        title = 'Recent posts to %s' % c.app.config.options.mount_label

        feed = Feed.feed(
            dict(project_id=c.project._id, app_config_id=c.app.config._id),
            feed_type,
            title,
            c.app.url,
            title,
            since, until, page, limit)
        response.headers['Content-Type'] = ''
        response.content_type = 'application/xml'
        return feed.writeString('utf-8')
Beispiel #17
0
def send_notifications(repo, commit_ids):
    """Create appropriate notification and feed objects for a refresh

    :param repo: A repository artifact instance.
    :type repo: Repository

    :param commit_ids: A list of commit hash strings, oldest to newest
    :type commit_ids: list
    """
    from allura.model import Feed, Notification
    commit_msgs = []
    base_url = tg.config['base_url']
    for oids in utils.chunked_iter(commit_ids, QSIZE):
        chunk = list(oids)
        index = dict((doc._id, doc)
                     for doc in Commit.query.find(dict(_id={'$in': chunk})))
        for oid in chunk:
            ci = index[oid]
            href = repo.url_for_commit(oid)
            title = _title(ci.message)
            summary = _summarize(ci.message)
            Feed.post(repo,
                      title=title,
                      description='%s<br><a href="%s">View Changes</a>' %
                      (summary, href),
                      author_link=ci.author_url,
                      author_name=ci.authored.name,
                      link=href,
                      unique_id=href)

            summary = g.markdown_commit.convert(
                ci.message) if ci.message else ""
            current_branch = repo.symbolics_for_commit(ci)[
                0]  # only the head of a branch will have this
            commit_msgs.append(
                dict(author=ci.authored.name,
                     date=ci.authored.date.strftime("%m/%d/%Y %H:%M"),
                     summary=summary,
                     branches=current_branch,
                     commit_url=base_url + href,
                     shorthand_id=ci.shorthand_id()))

    # fill out the branch info for all the other commits
    prev_branch = None
    for c_msg in reversed(commit_msgs):
        if not c_msg['branches']:
            c_msg['branches'] = prev_branch
        prev_branch = c_msg['branches']

    # mark which ones are first on a branch and need the branch name shown
    last_branch = None
    for c_msg in commit_msgs:
        if c_msg['branches'] != last_branch:
            c_msg['show_branch_name'] = True
        last_branch = c_msg['branches']

    if commit_msgs:
        if len(commit_msgs) > 1:
            subject = u"{} new commits to {}".format(
                len(commit_msgs), repo.app.config.options.mount_label)
        else:
            commit = commit_msgs[0]
            subject = u'New commit {} by {}'.format(commit['shorthand_id'],
                                                    commit['author'])
        text = g.jinja2_env.get_template(
            "allura:templates/mail/commits.md").render(
                commit_msgs=commit_msgs,
                max_num_commits=asint(
                    tg.config.get('scm.notify.max_commits', 100)),
            )

        Notification.post(artifact=repo,
                          topic='metadata',
                          subject=subject,
                          text=text)
Beispiel #18
0
def send_notifications(repo, commit_ids):
    """Create appropriate notification and feed objects for a refresh

    :param repo: A repository artifact instance.
    :type repo: Repository

    :param commit_ids: A list of commit hash strings, oldest to newest
    :type commit_ids: list
    """
    from allura.model import Feed, Notification
    commit_msgs = []
    base_url = tg.config['base_url']
    for oids in utils.chunked_iter(commit_ids, QSIZE):
        chunk = list(oids)
        index = dict(
            (doc._id, doc)
            for doc in Commit.query.find(dict(_id={'$in': chunk})))
        for oid in chunk:
            ci = index[oid]
            href = repo.url_for_commit(oid)
            title = _title(ci.message)
            summary = _summarize(ci.message)
            Feed.post(
                repo, title=title,
                description='%s<br><a href="%s">View Changes</a>' % (
                    summary, href),
                author_link=ci.author_url,
                author_name=ci.authored.name,
                link=href,
                unique_id=href)

            summary = g.markdown_commit.convert(ci.message) if ci.message else ""
            current_branch = repo.symbolics_for_commit(ci)[0]  # only the head of a branch will have this
            commit_msgs.append(dict(
                author=ci.authored.name,
                date=ci.authored.date.strftime("%m/%d/%Y %H:%M"),
                summary=summary,
                branches=current_branch,
                commit_url=base_url + href,
                shorthand_id=ci.shorthand_id()))

    # fill out the branch info for all the other commits
    prev_branch = None
    for c_msg in reversed(commit_msgs):
        if not c_msg['branches']:
            c_msg['branches'] = prev_branch
        prev_branch = c_msg['branches']

    # mark which ones are first on a branch and need the branch name shown
    last_branch = None
    for c_msg in commit_msgs:
        if c_msg['branches'] != last_branch:
            c_msg['show_branch_name'] = True
        last_branch = c_msg['branches']

    if commit_msgs:
        if len(commit_msgs) > 1:
            subject = u"{} new commits to {}".format(len(commit_msgs), repo.app.config.options.mount_label)
        else:
            commit = commit_msgs[0]
            subject = u'New commit {} by {}'.format(commit['shorthand_id'], commit['author'])
        text = g.jinja2_env.get_template("allura:templates/mail/commits.md").render(
            commit_msgs=commit_msgs,
            max_num_commits=asint(tg.config.get('scm.notify.max_commits', 100)),
        )

        Notification.post(
            artifact=repo,
            topic='metadata',
            subject=subject,
            text=text)
Beispiel #19
0
def send_notifications(repo, commit_ids):
    """Create appropriate notification and feed objects for a refresh

    :param repo: A repository artifact instance.
    :type repo: Repository

    :param commit_ids: A list of commit hash strings.
    :type commit_ids: list
    """
    from allura.model import Feed, Notification
    commit_msgs = []
    base_url = tg.config['base_url']
    last_branch = []
    for oids in utils.chunked_iter(commit_ids, QSIZE):
        chunk = list(oids)
        index = dict(
            (doc._id, doc)
            for doc in Commit.query.find(dict(_id={'$in': chunk})))
        for oid in chunk:
            ci = index[oid]
            href = repo.url_for_commit(oid)
            title = _title(ci.message)
            summary = _summarize(ci.message)
            Feed.post(
                repo, title=title,
                description='%s<br><a href="%s">View Changes</a>' % (
                    summary, href),
                author_link=ci.author_url,
                author_name=ci.authored.name,
                link=href,
                unique_id=href)

            summary = g.markdown_commit.convert(ci.message) if ci.message else ""

            current_branch = repo.symbolics_for_commit(ci)[0]
            if last_branch == current_branch:
                branches = []
            else:
                branches = current_branch
                last_branch = branches

            commit_msgs.append(dict(
                author=ci.authored.name,
                date=ci.authored.date.strftime("%m/%d/%Y %H:%M"),
                summary=summary,
                branches=branches,
                commit_url=base_url + href))

    if commit_msgs:
        if len(commit_msgs) > 1:
            subject = u"{} new commits to {}".format(len(commit_msgs), repo.app.config.options.mount_label)
        else:
            subject = u'New commit by {}'.format(commit_msgs[0]['author'])
        template = g.jinja2_env.get_template("allura:templates/mail/commits.md")
        text = u"\n\n-----".join([template.render(d) for d in commit_msgs])

        Notification.post(
            artifact=repo,
            topic='metadata',
            subject=subject,
            text=text)
Beispiel #20
0
 def commit(self):
     VersionedArtifact.commit(self)
     monitoring_email = self.app.config.options.get('TicketMonitoringEmail')
     if self.version > 1:
         hist = TicketHistory.query.get(artifact_id=self._id,
                                        version=self.version - 1)
         old = hist.data
         changes = [
             'Ticket %s has been modified: %s' %
             (self.ticket_num, self.summary),
             'Edited By: %s (%s)' %
             (c.user.get_pref('display_name'), c.user.username)
         ]
         fields = [('Summary', old.summary, self.summary),
                   ('Status', old.status, self.status)]
         if old.status != self.status and self.status in c.app.globals.set_of_closed_status_names:
             h.log_action(log, 'closed').info('')
             g.statsUpdater.ticketEvent("closed", self, self.project,
                                        self.assigned_to)
         for key in self.custom_fields:
             fields.append(
                 (key, old.custom_fields.get(key,
                                             ''), self.custom_fields[key]))
         for title, o, n in fields:
             if o != n:
                 changes.append('%s updated: %r => %r' % (title, o, n))
         o = hist.assigned_to
         n = self.assigned_to
         if o != n:
             changes.append('Owner updated: %r => %r' %
                            (o and o.username, n and n.username))
             self.subscribe(user=n)
             g.statsUpdater.ticketEvent("assigned", self, self.project, n)
             if o:
                 g.statsUpdater.ticketEvent("revoked", self, self.project,
                                            o)
         if old.description != self.description:
             changes.append('Description updated:')
             changes.append('\n'.join(
                 difflib.unified_diff(a=old.description.split('\n'),
                                      b=self.description.split('\n'),
                                      fromfile='description-old',
                                      tofile='description-new')))
         description = '\n'.join(changes)
     else:
         self.subscribe()
         if self.assigned_to_id:
             user = User.query.get(_id=self.assigned_to_id)
             g.statsUpdater.ticketEvent("assigned", self, self.project,
                                        user)
             self.subscribe(user=user)
         description = ''
         subject = self.email_subject
         Thread.new(discussion_id=self.app_config.discussion_id,
                    ref_id=self.index_id())
         # First ticket notification. Use persistend Message-ID (self.message_id()).
         # Thus we can group notification emails in one thread later.
         n = Notification.post(message_id=self.message_id(),
                               artifact=self,
                               topic='metadata',
                               text=description,
                               subject=subject)
         if monitoring_email and n and (
                 not self.private
                 or self.app.config.options.get('TicketMonitoringType')
                 in ('NewTicketsOnly', 'AllTicketChanges')):
             n.send_simple(monitoring_email)
     Feed.post(self,
               title=self.summary,
               description=description if description else self.description,
               author=self.reported_by,
               pubdate=self.created_date)
Beispiel #21
0
 def commit(self, **kwargs):
     VersionedArtifact.commit(self)
     monitoring_email = self.app.config.options.get("TicketMonitoringEmail")
     if self.version > 1:
         hist = TicketHistory.query.get(artifact_id=self._id, version=self.version - 1)
         old = hist.data
         changes = [
             "Ticket %s has been modified: %s" % (self.ticket_num, self.summary),
             "Edited By: %s (%s)" % (c.user.get_pref("display_name"), c.user.username),
         ]
         fields = [("Summary", old.summary, self.summary), ("Status", old.status, self.status)]
         if old.status != self.status and self.status in c.app.globals.set_of_closed_status_names:
             h.log_action(log, "closed").info("")
             g.statsUpdater.ticketEvent("closed", self, self.project, self.assigned_to)
         for key in self.custom_fields:
             fields.append((key, old.custom_fields.get(key, ""), self.custom_fields[key]))
         for title, o, n in fields:
             if o != n:
                 changes.append("%s updated: %r => %r" % (title, o, n))
         o = hist.assigned_to
         n = self.assigned_to
         if o != n:
             changes.append("Owner updated: %r => %r" % (o and o.username, n and n.username))
             self.subscribe(user=n)
             g.statsUpdater.ticketEvent("assigned", self, self.project, n)
             if o:
                 g.statsUpdater.ticketEvent("revoked", self, self.project, o)
         if old.description != self.description:
             changes.append("Description updated:")
             changes.append(
                 "\n".join(
                     difflib.unified_diff(
                         a=old.description.split("\n"),
                         b=self.description.split("\n"),
                         fromfile="description-old",
                         tofile="description-new",
                     )
                 )
             )
         description = "\n".join(changes)
     else:
         self.subscribe()
         if self.assigned_to_id:
             user = User.query.get(_id=self.assigned_to_id)
             g.statsUpdater.ticketEvent("assigned", self, self.project, user)
             self.subscribe(user=user)
         description = ""
         subject = self.email_subject
         Thread.new(discussion_id=self.app_config.discussion_id, ref_id=self.index_id())
         # First ticket notification. Use persistend Message-ID (self.message_id()).
         # Thus we can group notification emails in one thread later.
         n = Notification.post(
             message_id=self.message_id(), artifact=self, topic="metadata", text=description, subject=subject
         )
         if (
             monitoring_email
             and n
             and (
                 not self.private
                 or self.app.config.options.get("TicketMonitoringType") in ("NewTicketsOnly", "AllTicketChanges")
             )
         ):
             n.send_simple(monitoring_email)
     Feed.post(
         self,
         title=self.summary,
         description=description if description else self.description,
         author=self.reported_by,
         pubdate=self.created_date,
     )