[#8422] greedily prefetch thread artifact refs and attachments in batches
diff --git a/Allura/allura/lib/widgets/discuss.py b/Allura/allura/lib/widgets/discuss.py
index 60e1222..3532878 100644
--- a/Allura/allura/lib/widgets/discuss.py
+++ b/Allura/allura/lib/widgets/discuss.py
@@ -351,10 +351,27 @@
         # bulk fetch backrefs to save on many queries within EW
         thread: M.Thread = context['value']
         posts = thread.posts
-        index_ids = [a.index_id() for a in posts]
-        q = M.ArtifactReference.query.find(dict(references={'$in': index_ids})).all()
-        for a in posts:
-            a._backrefs = [aref._id for aref in q if a.index_id() in (aref.references or [])]
+        post_ids = [post._id for post in posts]
+        post_index_ids = [post.index_id() for post in posts]
+
+        # prefill refs
+        refs = M.ArtifactReference.query.find(dict(_id={'$in': post_index_ids})).all()
+        for post in posts:
+            for ref in refs:
+                if post.index_id() == ref._id:
+                    post._ref = ref
+                    break
+
+        # prefill backrefs
+        refs = M.ArtifactReference.query.find(dict(references={'$in': post_index_ids})).all()
+        for post in posts:
+            post._backrefs = [ref._id for ref in refs if post.index_id() in (ref.references or [])]
+
+        # prefill attachments
+        refs = thread.attachment_class().query.find(
+            dict(app_config_id=thread.app_config_id, artifact_id={'$in': post_ids}, type='attachment')).all()
+        for post in posts:
+            post._attachments = [ref for ref in refs if post._id == ref.post_id]
         return context
 
     def resources(self):
diff --git a/Allura/allura/model/artifact.py b/Allura/allura/model/artifact.py
index fe9beb8..6ff629e 100644
--- a/Allura/allura/model/artifact.py
+++ b/Allura/allura/model/artifact.py
@@ -145,6 +145,9 @@
         Artifact.
 
         """
+        if hasattr(self, '_ref'):
+            return self._ref
+
         return ArtifactReference.from_artifact(self)
 
     @LazyProperty
@@ -452,8 +455,11 @@
 
     @LazyProperty
     def attachments(self):
-        atts = self.attachment_class().query.find(dict(
-            app_config_id=self.app_config_id, artifact_id=self._id, type='attachment')).all()
+        if hasattr(self, '_attachments'):
+            atts = self._attachments
+        else:
+            atts = self.attachment_class().query.find(dict(
+                app_config_id=self.app_config_id, artifact_id=self._id, type='attachment')).all()
         return utils.unique_attachments(atts)
 
     def delete(self):
diff --git a/Allura/allura/model/discuss.py b/Allura/allura/model/discuss.py
index 6709f6e..a91ea93 100644
--- a/Allura/allura/model/discuss.py
+++ b/Allura/allura/model/discuss.py
@@ -635,12 +635,6 @@
                     subject = getattr(artifact, 'email_subject', '')
         return subject or '(no subject)'
 
-    @LazyProperty
-    def attachments(self):
-        atts = self.attachment_class().query.find(dict(
-            post_id=self._id, type='attachment')).all()
-        return utils.unique_attachments(atts)
-
     def add_multiple_attachments(self, file_info):
         if isinstance(file_info, list):
             for fi in file_info: