← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~cjwatson/launchpad:stormify-bugactivity into launchpad:master

 

Colin Watson has proposed merging ~cjwatson/launchpad:stormify-bugactivity into launchpad:master.

Commit message:
Convert BugActivity to Storm

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/423361
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:stormify-bugactivity into launchpad:master.
diff --git a/lib/lp/bugs/browser/bug.py b/lib/lp/bugs/browser/bug.py
index b1b798f..1e9de86 100644
--- a/lib/lp/bugs/browser/bug.py
+++ b/lib/lp/bugs/browser/bug.py
@@ -626,11 +626,11 @@ class BugActivity(BugView):
 
     page_title = 'Activity log'
 
-    @property
+    @cachedproperty
     def activity(self):
-        activity = IBug(self.context).activity
+        activity = list(IBug(self.context).activity)
         list(getUtility(IPersonSet).getPrecachedPersonsFromIDs(
-            [a.personID for a in activity], need_validity=True))
+            [a.person_id for a in activity], need_validity=True))
         return activity
 
 
diff --git a/lib/lp/bugs/browser/bugtask.py b/lib/lp/bugs/browser/bugtask.py
index 03be66f..14f37aa 100644
--- a/lib/lp/bugs/browser/bugtask.py
+++ b/lib/lp/bugs/browser/bugtask.py
@@ -605,7 +605,7 @@ class BugTaskView(LaunchpadView, BugViewMixin, FeedsMixin):
             if interesting_match(activity_item.whatchanged) is not None]
         # Pre-load the doers of the activities in one query.
         person_ids = {
-            activity_item.personID for activity_item in activity_items}
+            activity_item.person_id for activity_item in activity_items}
         list(getUtility(IPersonSet).getPrecachedPersonsFromIDs(
             person_ids, need_validity=True))
 
diff --git a/lib/lp/bugs/browser/tests/test_bug_views.py b/lib/lp/bugs/browser/tests/test_bug_views.py
index b82ae92..2cca466 100644
--- a/lib/lp/bugs/browser/tests/test_bug_views.py
+++ b/lib/lp/bugs/browser/tests/test_bug_views.py
@@ -858,7 +858,7 @@ class TestBugActivityView(TestCaseWithFactory):
             view = create_initialized_view(
                 bug.default_bugtask, name='+activity')
             view.render()
-        self.assertThat(recorder, HasQueryCount(Equals(7)))
+        self.assertThat(recorder, HasQueryCount(Equals(5)))
 
 
 class TestMainBugView(BrowserTestCase):
diff --git a/lib/lp/bugs/browser/tests/test_bugtask.py b/lib/lp/bugs/browser/tests/test_bugtask.py
index df43cf9..5971901 100644
--- a/lib/lp/bugs/browser/tests/test_bugtask.py
+++ b/lib/lp/bugs/browser/tests/test_bugtask.py
@@ -1889,7 +1889,7 @@ class TestBugActivityItem(TestCaseWithFactory):
                 self.factory.makePerson(displayname="Foo &<>", name='foo'))
         self.assertEqual(
             "nobody &#8594; Foo &amp;&lt;&gt; (foo)",
-            BugActivityItem(task.bug.activity[-1]).change_details)
+            BugActivityItem(task.bug.activity.last()).change_details)
 
     def test_escapes_title(self):
         with celebrity_logged_in('admin'):
@@ -1897,7 +1897,7 @@ class TestBugActivityItem(TestCaseWithFactory):
             self.setAttribute(bug, 'title', "bar &<>")
         self.assertEqual(
             "- foo<br />+ bar &amp;&lt;&gt;",
-            BugActivityItem(bug.activity[-1]).change_details)
+            BugActivityItem(bug.activity.last()).change_details)
 
     def test_change_details_bug_locked_with_reason(self):
         target = self.factory.makeProduct()
@@ -1910,7 +1910,7 @@ class TestBugActivityItem(TestCaseWithFactory):
             )
         self.assertEqual(
             'Metadata changes locked (too hot) and limited to project staff',
-            BugActivityItem(bug.activity[-1]).change_details
+            BugActivityItem(bug.activity.last()).change_details
         )
 
     def test_change_details_bug_locked_without_reason(self):
@@ -1923,7 +1923,7 @@ class TestBugActivityItem(TestCaseWithFactory):
             )
         self.assertEqual(
             'Metadata changes locked and limited to project staff',
-            BugActivityItem(bug.activity[-1]).change_details
+            BugActivityItem(bug.activity.last()).change_details
         )
 
     def test_change_details_bug_unlocked(self):
@@ -1937,7 +1937,7 @@ class TestBugActivityItem(TestCaseWithFactory):
             bug.unlock(who=target.owner)
         self.assertEqual(
             'Metadata changes unlocked',
-            BugActivityItem(bug.activity[-1]).change_details
+            BugActivityItem(bug.activity.last()).change_details
         )
 
     def test_change_details_bug_lock_reason_set(self):
@@ -1951,7 +1951,7 @@ class TestBugActivityItem(TestCaseWithFactory):
             bug.setLockReason('too hot', who=target.owner)
         self.assertEqual(
             'too hot',
-            BugActivityItem(bug.activity[-1]).change_details
+            BugActivityItem(bug.activity.last()).change_details
         )
 
     def test_change_details_bug_lock_reason_updated(self):
@@ -1966,7 +1966,7 @@ class TestBugActivityItem(TestCaseWithFactory):
             bug.setLockReason('too hot!', who=target.owner)
         self.assertEqual(
             'too hot &rarr; too hot!',
-            BugActivityItem(bug.activity[-1]).change_details
+            BugActivityItem(bug.activity.last()).change_details
         )
 
     def test_change_details_bug_lock_reason_unset(self):
@@ -1981,7 +1981,7 @@ class TestBugActivityItem(TestCaseWithFactory):
             bug.setLockReason(None, who=target.owner)
         self.assertEqual(
             'Unset',
-            BugActivityItem(bug.activity[-1]).change_details
+            BugActivityItem(bug.activity.last()).change_details
         )
 
     def test_change_details_bugtask_importance_explanation_set(self):
@@ -1993,7 +1993,7 @@ class TestBugActivityItem(TestCaseWithFactory):
             )
         self.assertEqual(
             'unset &#8594; Critical',
-            BugActivityItem(task.bug.activity[-1]).change_details
+            BugActivityItem(task.bug.activity.last()).change_details
         )
 
     def test_change_details_bugtask_status_explanation_set(self):
@@ -2006,7 +2006,7 @@ class TestBugActivityItem(TestCaseWithFactory):
 
         self.assertEqual(
             'unset &#8594; This is blocked on foo',
-            BugActivityItem(task.bug.activity[-1]).change_details
+            BugActivityItem(task.bug.activity.last()).change_details
         )
 
     def test_change_details_bugtask_importance_explanation_unset(self):
@@ -2023,7 +2023,7 @@ class TestBugActivityItem(TestCaseWithFactory):
 
         self.assertEqual(
             'Critical &#8594; unset',
-            BugActivityItem(task.bug.activity[-1]).change_details
+            BugActivityItem(task.bug.activity.last()).change_details
         )
 
     def test_change_details_bugtask_status_explanation_unset(self):
@@ -2040,7 +2040,7 @@ class TestBugActivityItem(TestCaseWithFactory):
 
         self.assertEqual(
             'This is blocked on foo &#8594; unset',
-            BugActivityItem(task.bug.activity[-1]).change_details
+            BugActivityItem(task.bug.activity.last()).change_details
         )
 
     def test_change_details_bugtask_importance_explanation_changed(self):
@@ -2056,7 +2056,7 @@ class TestBugActivityItem(TestCaseWithFactory):
             )
         self.assertEqual(
             'Critical &#8594; Critical!',
-            BugActivityItem(task.bug.activity[-1]).change_details
+            BugActivityItem(task.bug.activity.last()).change_details
         )
 
     def test_change_details_bugtask_status_explanation_changed(self):
@@ -2073,7 +2073,7 @@ class TestBugActivityItem(TestCaseWithFactory):
 
         self.assertEqual(
             'This is blocked on foo &#8594; This is blocked on bar',
-            BugActivityItem(task.bug.activity[-1]).change_details
+            BugActivityItem(task.bug.activity.last()).change_details
         )
 
 
diff --git a/lib/lp/bugs/doc/bug-set-status.txt b/lib/lp/bugs/doc/bug-set-status.txt
index f89dee0..926c274 100644
--- a/lib/lp/bugs/doc/bug-set-status.txt
+++ b/lib/lp/bugs/doc/bug-set-status.txt
@@ -53,7 +53,9 @@ BugActivity records are created.
            Status: New => Confirmed
 
     >>> from lp.bugs.model.bugactivity import BugActivity
-    >>> latest_activity = BugActivity.selectFirst(orderBy='-id')
+    >>> from lp.services.database.interfaces import IStore
+    >>> latest_activity = IStore(BugActivity).find(
+    ...     BugActivity).order_by(BugActivity.id).last()
     >>> print(latest_activity.whatchanged)
     firefox: status
     >>> print(latest_activity.oldvalue)
diff --git a/lib/lp/bugs/doc/bugactivity.txt b/lib/lp/bugs/doc/bugactivity.txt
index 7c4ec8c..73ab1e1 100644
--- a/lib/lp/bugs/doc/bugactivity.txt
+++ b/lib/lp/bugs/doc/bugactivity.txt
@@ -37,7 +37,7 @@ User files a bug
     ...     comment="this is only a test bug\nplease ignore",
     ...     owner=user)
     >>> bug = firefox.createBug(params)
-    >>> latest_activity = bug.activity[-1]
+    >>> latest_activity = bug.activity.last()
     >>> latest_activity.person == user
     True
     >>> print(latest_activity.whatchanged)
@@ -51,7 +51,7 @@ Bug title edited
 
     >>> with notify_modified(bug, ["title", "description"]):
     ...     bug.title = "new bug title"
-    >>> latest_activity = bug.activity[-1]
+    >>> latest_activity = bug.activity.last()
     >>> print(latest_activity.whatchanged)
     summary
     >>> print(latest_activity.oldvalue)
@@ -77,7 +77,7 @@ Source package assignment edited
     >>> with notify_modified(source_package_assignment, edit_fields):
     ...     source_package_assignment.transitionToStatus(
     ...         BugTaskStatus.CONFIRMED, getUtility(ILaunchBag).user)
-    >>> latest_activity = bug.activity[-1]
+    >>> latest_activity = bug.activity.last()
     >>> print(latest_activity.whatchanged)
     mozilla-firefox (Ubuntu): status
     >>> latest_activity.oldvalue == BugTaskStatus.NEW.title
@@ -100,11 +100,12 @@ typically the same as `whatchanged`.  However, in some cases (ideally,
 whenever necessary), it is normalized to show the actual attribute name.
 For instance, look at the attributes on the previous activity.
 
-    >>> print(bug.activity[-2].target)
+    >>> second_latest_activity = list(bug.activity)[-2]
+    >>> print(second_latest_activity.target)
     None
-    >>> print(bug.activity[-2].whatchanged)
+    >>> print(second_latest_activity.whatchanged)
     summary
-    >>> bug.activity[-2].attribute
+    >>> second_latest_activity.attribute
     'title'
 
 (This is covered more comprehensively in tests/test_bugchanges.py).
@@ -120,7 +121,7 @@ Upstream product assignment edited
     >>> with notify_modified(product_assignment, edit_fields):
     ...     product_assignment.transitionToStatus(
     ...         BugTaskStatus.INVALID, getUtility(ILaunchBag).user)
-    >>> latest_activity = bug.activity[-1]
+    >>> latest_activity = bug.activity.last()
     >>> print(latest_activity.whatchanged)
     thunderbird: status
     >>> print(latest_activity.target)
@@ -142,7 +143,7 @@ Bug report is marked as a duplicate of another bug report
     >>> with notify_modified(bug, edit_fields):
     ...     latest_bug = factory.makeBug()
     ...     bug.markAsDuplicate(latest_bug)
-    >>> latest_activity = bug.activity[-1]
+    >>> latest_activity = bug.activity.last()
     >>> print(latest_activity.whatchanged)
     marked as duplicate
     >>> latest_activity.oldvalue is None
@@ -160,7 +161,7 @@ Bug report has its duplicate marker changed to another bug report
     >>> with notify_modified(bug, edit_fields):
     ...     another_bug = factory.makeBug()
     ...     bug.markAsDuplicate(another_bug)
-    >>> latest_activity = bug.activity[-1]
+    >>> latest_activity = bug.activity.last()
     >>> print(latest_activity.whatchanged)
     changed duplicate marker
     >>> latest_activity.oldvalue == str(latest_bug.id)
@@ -177,7 +178,7 @@ The bug report is un-duplicated
     ...     "security_related"]
     >>> with notify_modified(bug, edit_fields):
     ...     bug.markAsDuplicate(None)
-    >>> latest_activity = bug.activity[-1]
+    >>> latest_activity = bug.activity.last()
     >>> print(latest_activity.whatchanged)
     removed duplicate marker
     >>> latest_activity.oldvalue == str(another_bug.id)
@@ -211,14 +212,14 @@ the initial bug against it.
 Now, we confirm the activity log for the other bugs correctly list the
 final_bug as their master bug.
 
-    >>> latest_activity = dupe_one.activity[-1]
+    >>> latest_activity = dupe_one.activity.last()
     >>> print(latest_activity.whatchanged)
     changed duplicate marker
     >>> latest_activity.oldvalue == str(initial_bug.id)
     True
     >>> latest_activity.newvalue == str(final_bug.id)
     True
-    >>> latest_activity = dupe_two.activity[-1]
+    >>> latest_activity = dupe_two.activity.last()
     >>> print(latest_activity.whatchanged)
     changed duplicate marker
     >>> latest_activity.oldvalue == str(initial_bug.id)
diff --git a/lib/lp/bugs/doc/bugtask-expiration.txt b/lib/lp/bugs/doc/bugtask-expiration.txt
index 360f9fb..74899b6 100644
--- a/lib/lp/bugs/doc/bugtask-expiration.txt
+++ b/lib/lp/bugs/doc/bugtask-expiration.txt
@@ -545,7 +545,7 @@ expired by the expiration process. Although ubuntu was never returned
 by findExpirableBugTasks(), it was expired because its primary (hoary)
 was expired. The remaining bugtasks are unchanged.
 
-    >>> summarize_bugtasks(bugtasks)
+    >>> summarize_bugtasks(bugtasks)  # noqa
     ROLE             EXPIRE  AGE  STATUS      ASSIGNED  DUP    MILE   REPLIES
     ubuntu           False     0  Expired     False     False  False  False
     hoary            False     0  Expired     False     False  False  False
@@ -578,7 +578,7 @@ primary and replica bugtasks were expired.
 
 The bug's activity log was updated too with the status change.
 
-    >>> activity = hoary_bugtask.bug.activity[-1]
+    >>> activity = hoary_bugtask.bug.activity.last()
     >>> print("%s  %s  %s  %s" % (
     ...     activity.person.displayname, activity.whatchanged,
     ...     activity.oldvalue, activity.newvalue))
diff --git a/lib/lp/bugs/interfaces/bugactivity.py b/lib/lp/bugs/interfaces/bugactivity.py
index e282b66..3b7dd2a 100644
--- a/lib/lp/bugs/interfaces/bugactivity.py
+++ b/lib/lp/bugs/interfaces/bugactivity.py
@@ -41,7 +41,7 @@ class IBugActivity(Interface):
                  description=_("The date on which this activity occurred."),
                  readonly=True))
 
-    personID = Attribute('DB ID for Person')
+    person_id = Attribute('DB ID for Person')
     person = exported(PersonChoice(
         title=_('Person'), required=True, vocabulary='ValidPersonOrTeam',
         readonly=True, description=_("The person's Launchpad ID or "
diff --git a/lib/lp/bugs/model/bug.py b/lib/lp/bugs/model/bug.py
index 1d1891d..3f9bc62 100644
--- a/lib/lp/bugs/model/bug.py
+++ b/lib/lp/bugs/model/bug.py
@@ -388,7 +388,7 @@ class Bug(SQLBase, InformationTypeMixin):
         enum=InformationType, allow_none=False, default=InformationType.PUBLIC)
 
     # useful Joins
-    activity = SQLMultipleJoin('BugActivity', joinColumn='bug', orderBy='id')
+    activity = ReferenceSet('id', BugActivity.bug_id, order_by=BugActivity.id)
     messages = SQLRelatedJoin('Message', joinColumn='bug_id',
                            otherColumn='message_id',
                            intermediateTable='BugMessage',
diff --git a/lib/lp/bugs/model/bugactivity.py b/lib/lp/bugs/model/bugactivity.py
index 50a1770..5d02085 100644
--- a/lib/lp/bugs/model/bugactivity.py
+++ b/lib/lp/bugs/model/bugactivity.py
@@ -5,6 +5,13 @@ __all__ = ['BugActivity', 'BugActivitySet']
 
 import re
 
+import pytz
+from storm.locals import (
+    DateTime,
+    Int,
+    Reference,
+    Unicode,
+    )
 from storm.store import Store
 from zope.interface import implementer
 
@@ -29,29 +36,29 @@ from lp.bugs.interfaces.bugactivity import (
     IBugActivitySet,
     )
 from lp.registry.interfaces.person import validate_person
-from lp.services.database.datetimecol import UtcDateTimeCol
-from lp.services.database.sqlbase import SQLBase
-from lp.services.database.sqlobject import (
-    ForeignKey,
-    StringCol,
-    )
+from lp.services.database.stormbase import StormBase
 
 
 @implementer(IBugActivity)
-class BugActivity(SQLBase):
+class BugActivity(StormBase):
     """Bug activity log entry."""
 
-    _table = 'BugActivity'
-    bug = ForeignKey(foreignKey='Bug', dbName='bug', notNull=True)
-    datechanged = UtcDateTimeCol(notNull=True)
-    person = ForeignKey(
-        dbName='person', foreignKey='Person',
-        storm_validator=validate_person,
-        notNull=True)
-    whatchanged = StringCol(notNull=True)
-    oldvalue = StringCol(default=None)
-    newvalue = StringCol(default=None)
-    message = StringCol(default=None)
+    __storm_table__ = 'BugActivity'
+
+    id = Int(primary=True)
+
+    bug_id = Int(name='bug', allow_none=False)
+    bug = Reference(bug_id, 'Bug.id')
+
+    datechanged = DateTime(tzinfo=pytz.UTC, allow_none=False)
+
+    person_id = Int(name='person', allow_none=False, validator=validate_person)
+    person = Reference(person_id, 'Person.id')
+
+    whatchanged = Unicode(allow_none=False)
+    oldvalue = Unicode(allow_none=True, default=None)
+    newvalue = Unicode(allow_none=True, default=None)
+    message = Unicode(allow_none=True, default=None)
 
     # The regular expression we use for matching bug task changes.
     bugtask_change_re = re.compile(
@@ -59,6 +66,16 @@ class BugActivity(SQLBase):
         r'(?P<attribute>assignee|importance explanation|importance|'
         r'milestone|status explanation|status)')
 
+    def __init__(self, bug, datechanged, person, whatchanged,
+                 oldvalue=None, newvalue=None, message=None):
+        self.bug = bug
+        self.datechanged = datechanged
+        self.person = person
+        self.whatchanged = whatchanged
+        self.oldvalue = oldvalue
+        self.newvalue = newvalue
+        self.message = message
+
     @property
     def target(self):
         """Return the target of this BugActivityItem.
diff --git a/lib/lp/bugs/model/bugtasksearch.py b/lib/lp/bugs/model/bugtasksearch.py
index 03b306f..db6b3f2 100644
--- a/lib/lp/bugs/model/bugtasksearch.py
+++ b/lib/lp/bugs/model/bugtasksearch.py
@@ -636,7 +636,7 @@ def _build_query(params):
                         BugMessage.index > 0,
                         BugMessage.owner == params.bug_commenter)),
                 Select(
-                    BugActivity.bugID, tables=[BugActivity],
+                    BugActivity.bug_id, tables=[BugActivity],
                     where=And(
                         BugActivity.person == params.bug_commenter,
                         # This is distressingly fragile, but BugActivity
diff --git a/lib/lp/bugs/subscribers/bugactivity.py b/lib/lp/bugs/subscribers/bugactivity.py
index 942d168..348a55d 100644
--- a/lib/lp/bugs/subscribers/bugactivity.py
+++ b/lib/lp/bugs/subscribers/bugactivity.py
@@ -62,10 +62,10 @@ def get_string_representation(obj):
         return None
 
 
-def what_changed(sqlobject_modified_event):
-    before = sqlobject_modified_event.object_before_modification
-    after = sqlobject_modified_event.object
-    fields = sqlobject_modified_event.edited_fields
+def what_changed(object_modified_event):
+    before = object_modified_event.object_before_modification
+    after = object_modified_event.object
+    fields = object_modified_event.edited_fields
     changes = {}
     for fieldname in fields:
         # XXX 2011-01-21 gmb bug=705955:
@@ -164,15 +164,15 @@ def record_bugsubscription_added(bugsubscription_added, object_created_event):
 
 @block_implicit_flushes
 def record_bugsubscription_edited(bugsubscription_edited,
-                                  sqlobject_modified_event):
-    changes = what_changed(sqlobject_modified_event)
+                                  object_modified_event):
+    changes = what_changed(object_modified_event)
     if changes:
         for changed_field in changes.keys():
             oldvalue, newvalue = changes[changed_field]
             getUtility(IBugActivitySet).new(
                 bug=bugsubscription_edited.bug,
                 datechanged=UTC_NOW,
-                person=IPerson(sqlobject_modified_event.user),
+                person=IPerson(object_modified_event.user),
                 whatchanged="subscriber %s" % (
                     bugsubscription_edited.person.displayname),
                 oldvalue=oldvalue,
diff --git a/lib/lp/bugs/tests/test_bugchanges.py b/lib/lp/bugs/tests/test_bugchanges.py
index b106ada..e89383f 100644
--- a/lib/lp/bugs/tests/test_bugchanges.py
+++ b/lib/lp/bugs/tests/test_bugchanges.py
@@ -214,7 +214,7 @@ class TestBugChanges(TestCaseWithFactory):
         self.bug.unsubscribe(self.user, self.user)
 
         # This checks the activity's attribute and target attributes.
-        activity = self.bug.activity[-1]
+        activity = self.bug.activity.last()
         self.assertEqual(activity.attribute, 'removed_subscriber')
         self.assertEqual(activity.target, None)
 
@@ -244,7 +244,7 @@ class TestBugChanges(TestCaseWithFactory):
         old_title = self.changeAttribute(self.bug, 'title', '42')
 
         # This checks the activity's attribute and target attributes.
-        activity = self.bug.activity[-1]
+        activity = self.bug.activity.last()
         self.assertEqual(activity.attribute, 'title')
         self.assertEqual(activity.target, None)
 
@@ -299,7 +299,7 @@ class TestBugChanges(TestCaseWithFactory):
         bug_watch = self.bug.addWatch(bugtracker, '42', self.user)
 
         # This checks the activity's attribute and target attributes.
-        activity = self.bug.activity[-1]
+        activity = self.bug.activity.last()
         self.assertEqual(activity.attribute, 'watches')
         self.assertEqual(activity.target, None)
 
@@ -371,7 +371,7 @@ class TestBugChanges(TestCaseWithFactory):
         self.bug.removeWatch(bug_watch, self.user)
 
         # This checks the activity's attribute and target attributes.
-        activity = self.bug.activity[-1]
+        activity = self.bug.activity.last()
         self.assertEqual(activity.attribute, 'watches')
         self.assertEqual(activity.target, None)
 
@@ -453,7 +453,7 @@ class TestBugChanges(TestCaseWithFactory):
         self.bug.linkBranch(branch, self.user)
 
         # This checks the activity's attribute and target attributes.
-        activity = self.bug.activity[-1]
+        activity = self.bug.activity.last()
         self.assertEqual(activity.attribute, 'linked_branches')
         self.assertEqual(activity.target, None)
 
@@ -506,7 +506,7 @@ class TestBugChanges(TestCaseWithFactory):
         self.bug.unlinkBranch(branch, self.user)
 
         # This checks the activity's attribute and target attributes.
-        activity = self.bug.activity[-1]
+        activity = self.bug.activity.last()
         self.assertEqual(activity.attribute, 'linked_branches')
         self.assertEqual(activity.target, None)
 
@@ -560,7 +560,7 @@ class TestBugChanges(TestCaseWithFactory):
         self.bug.linkMergeProposal(bmp, self.user)
 
         # This checks the activity's attribute and target attributes.
-        activity = self.bug.activity[-1]
+        activity = self.bug.activity.last()
         self.assertEqual(activity.attribute, 'linked_merge_proposals')
         self.assertEqual(activity.target, None)
 
@@ -616,7 +616,7 @@ class TestBugChanges(TestCaseWithFactory):
         self.bug.unlinkMergeProposal(bmp, self.user)
 
         # This checks the activity's attribute and target attributes.
-        activity = self.bug.activity[-1]
+        activity = self.bug.activity.last()
         self.assertEqual(activity.attribute, 'linked_merge_proposals')
         self.assertEqual(activity.target, None)
 
@@ -776,7 +776,7 @@ class TestBugChanges(TestCaseWithFactory):
         self.bug.linkCVE(cve, self.user)
 
         # This checks the activity's attribute and target attributes.
-        activity = self.bug.activity[-1]
+        activity = self.bug.activity.last()
         self.assertEqual(activity.attribute, 'cves')
         self.assertEqual(activity.target, None)
 
@@ -807,7 +807,7 @@ class TestBugChanges(TestCaseWithFactory):
         self.bug.unlinkCVE(cve, self.user)
 
         # This checks the activity's attribute and target attributes.
-        activity = self.bug.activity[-1]
+        activity = self.bug.activity.last()
         self.assertEqual(activity.attribute, 'cves')
         self.assertEqual(activity.target, None)
 
@@ -840,7 +840,7 @@ class TestBugChanges(TestCaseWithFactory):
             bug=self.bug, owner=self.user, comment=message)
 
         # This checks the activity's attribute and target attributes.
-        activity = self.bug.activity[-1]
+        activity = self.bug.activity.last()
         self.assertEqual(activity.attribute, 'attachments')
         self.assertIsNone(activity.target)
 
@@ -878,7 +878,7 @@ class TestBugChanges(TestCaseWithFactory):
         attachment.removeFromBug(user=self.user)
 
         # This checks the activity's attribute and target attributes.
-        activity = self.bug.activity[-1]
+        activity = self.bug.activity.last()
         self.assertEqual(activity.attribute, 'attachments')
         self.assertEqual(activity.target, None)
 
@@ -1003,7 +1003,7 @@ class TestBugChanges(TestCaseWithFactory):
                 BugTaskImportance.HIGH, user=self.user)
 
         # This checks the activity's attribute and target attributes.
-        activity = self.bug.activity[-1]
+        activity = self.bug.activity.last()
         self.assertEqual(activity.attribute, 'importance')
         self.assertThat(activity.target, StartsWith('product-name'))
 
@@ -1489,7 +1489,7 @@ class TestBugChanges(TestCaseWithFactory):
         self.changeAttribute(duplicate_bug, 'duplicateof', self.bug)
 
         # This checks the activity's attribute and target attributes.
-        activity = duplicate_bug.activity[-1]
+        activity = duplicate_bug.activity.last()
         self.assertEqual(activity.attribute, 'duplicateof')
         self.assertEqual(activity.target, None)
 
@@ -1532,7 +1532,7 @@ class TestBugChanges(TestCaseWithFactory):
         self.changeAttribute(duplicate_bug, 'duplicateof', None)
 
         # This checks the activity's attribute and target attributes.
-        activity = duplicate_bug.activity[-1]
+        activity = duplicate_bug.activity.last()
         self.assertEqual(activity.attribute, 'duplicateof')
         self.assertEqual(activity.target, None)
 
@@ -1568,7 +1568,7 @@ class TestBugChanges(TestCaseWithFactory):
         self.changeAttribute(self.bug, 'duplicateof', bug_two)
 
         # This checks the activity's attribute and target attributes.
-        activity = self.bug.activity[-1]
+        activity = self.bug.activity.last()
         self.assertEqual(activity.attribute, 'duplicateof')
         self.assertEqual(activity.target, None)
 
@@ -1610,7 +1610,7 @@ class TestBugChanges(TestCaseWithFactory):
         self.changeAttribute(public_bug, 'duplicateof', private_bug)
 
         # This checks the activity's attribute and target attributes.
-        activity = public_bug.activity[-1]
+        activity = public_bug.activity.last()
         self.assertEqual(activity.attribute, 'duplicateof')
         self.assertEqual(activity.target, None)
 
@@ -1654,7 +1654,7 @@ class TestBugChanges(TestCaseWithFactory):
         self.changeAttribute(public_bug, 'duplicateof', None)
 
         # This checks the activity's attribute and target attributes.
-        activity = public_bug.activity[-1]
+        activity = public_bug.activity.last()
         self.assertEqual(activity.attribute, 'duplicateof')
         self.assertEqual(activity.target, None)
 
@@ -1695,7 +1695,7 @@ class TestBugChanges(TestCaseWithFactory):
         self.changeAttribute(duplicate_bug, 'duplicateof', public_bug)
 
         # This checks the activity's attribute and target attributes.
-        activity = duplicate_bug.activity[-1]
+        activity = duplicate_bug.activity.last()
         self.assertEqual(activity.attribute, 'duplicateof')
         self.assertEqual(activity.target, None)