We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Remove the TODO "7 days" restriction for edit and move topics
Right now we have a restriction to move just the messages in the last week in method:
`update_messages_for_topic_edit` file `zerver/lib/topic.py`
```
       # We only change messages up to 7 days in the past, to avoid hammering our
        # DB by changing an unbounded amount of messages
        #
        # TODO: Look at removing this restriction and/or add a "change_last_week"
        # option; this behavior feels buggy.
```

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `zerver/lib/topic.py`
Content:
```
1 import datetime
2 
3 from django.db import connection
4 from django.db.models.query import QuerySet, Q
5 from django.utils.timezone import now as timezone_now
6 
7 from sqlalchemy.sql import (
8     column,
9     literal,
10     func,
11 )
12 
13 from zerver.lib.request import REQ
14 from zerver.models import (
15     Message,
16     Recipient,
17     Stream,
18     UserMessage,
19     UserProfile,
20 )
21 
22 from typing import Any, Dict, List, Optional, Tuple
23 
24 # Only use these constants for events.
25 ORIG_TOPIC = "orig_subject"
26 TOPIC_NAME = "subject"
27 TOPIC_LINKS = "topic_links"
28 MATCH_TOPIC = "match_subject"
29 
30 # This constant is actually embedded into
31 # the JSON data for message edit history,
32 # so we'll always need to handle legacy data
33 # unless we do a pretty tricky migration.
34 LEGACY_PREV_TOPIC = "prev_subject"
35 
36 # This constant is pretty closely coupled to the
37 # database, but it's the JSON field.
38 EXPORT_TOPIC_NAME = "subject"
39 
40 '''
41 The following functions are for user-facing APIs
42 where we'll want to support "subject" for a while.
43 '''
44 
45 def get_topic_from_message_info(message_info: Dict[str, Any]) -> str:
46     '''
47     Use this where you are getting dicts that are based off of messages
48     that may come from the outside world, especially from third party
49     APIs and bots.
50 
51     We prefer 'topic' to 'subject' here.  We expect at least one field
52     to be present (or the caller must know how to handle KeyError).
53     '''
54     if 'topic' in message_info:
55         return message_info['topic']
56 
57     return message_info['subject']
58 
59 def REQ_topic() -> Optional[str]:
60     # REQ handlers really return a REQ, but we
61     # lie to make the rest of the type matching work.
62     return REQ(
63         whence='topic',
64         aliases=['subject'],
65         converter=lambda x: x.strip(),
66         default=None,
67     )
68 
69 '''
70 TRY TO KEEP THIS DIVIDING LINE.
71 
72 Below this line we want to make it so that functions are only
73 using "subject" in the DB sense, and nothing customer facing.
74 
75 '''
76 
77 # This is used in low-level message functions in
78 # zerver/lib/message.py, and it's not user facing.
79 DB_TOPIC_NAME = "subject"
80 MESSAGE__TOPIC = 'message__subject'
81 
82 def topic_match_sa(topic_name: str) -> Any:
83     # _sa is short for Sql Alchemy, which we use mostly for
84     # queries that search messages
85     topic_cond = func.upper(column("subject")) == func.upper(literal(topic_name))
86     return topic_cond
87 
88 def topic_column_sa() -> Any:
89     return column("subject")
90 
91 def filter_by_exact_message_topic(query: QuerySet, message: Message) -> QuerySet:
92     topic_name = message.topic_name()
93     return query.filter(subject=topic_name)
94 
95 def filter_by_topic_name_via_message(query: QuerySet, topic_name: str) -> QuerySet:
96     return query.filter(message__subject__iexact=topic_name)
97 
98 def messages_for_topic(stream_recipient_id: int, topic_name: str) -> QuerySet:
99     return Message.objects.filter(
100         recipient_id=stream_recipient_id,
101         subject__iexact=topic_name,
102     )
103 
104 def save_message_for_edit_use_case(message: Message) -> None:
105     message.save(update_fields=[TOPIC_NAME, "content", "rendered_content",
106                                 "rendered_content_version", "last_edit_time",
107                                 "edit_history", "has_attachment", "has_image",
108                                 "has_link", "recipient_id"])
109 
110 
111 def user_message_exists_for_topic(user_profile: UserProfile,
112                                   recipient: Recipient,
113                                   topic_name: str) -> bool:
114     return UserMessage.objects.filter(
115         user_profile=user_profile,
116         message__recipient=recipient,
117         message__subject__iexact=topic_name,
118     ).exists()
119 
120 def update_messages_for_topic_edit(message: Message,
121                                    propagate_mode: str,
122                                    orig_topic_name: str,
123                                    topic_name: Optional[str],
124                                    new_stream: Optional[Stream]) -> List[Message]:
125     propagate_query = Q(recipient = message.recipient, subject = orig_topic_name)
126     if propagate_mode == 'change_all':
127         # We only change messages up to 7 days in the past, to avoid hammering our
128         # DB by changing an unbounded amount of messages
129         #
130         # TODO: Look at removing this restriction and/or add a "change_last_week"
131         # option; this behavior feels buggy.
132         before_bound = timezone_now() - datetime.timedelta(days=7)
133 
134         propagate_query = (propagate_query & ~Q(id = message.id) &
135                            Q(date_sent__range=(before_bound, timezone_now())))
136     if propagate_mode == 'change_later':
137         propagate_query = propagate_query & Q(id__gt = message.id)
138 
139     messages = Message.objects.filter(propagate_query).select_related()
140 
141     update_fields = {}
142 
143     # Evaluate the query before running the update
144     messages_list = list(messages)
145 
146     # The cached ORM objects are not changed by the upcoming
147     # messages.update(), and the remote cache update (done by the
148     # caller) requires the new value, so we manually update the
149     # objects in addition to sending a bulk query to the database.
150     if new_stream is not None:
151         update_fields["recipient"] = new_stream.recipient
152         for m in messages_list:
153             m.recipient = new_stream.recipient
154     if topic_name is not None:
155         update_fields["subject"] = topic_name
156         for m in messages_list:
157             m.set_topic_name(topic_name)
158 
159     messages.update(**update_fields)
160 
161     return messages_list
162 
163 def generate_topic_history_from_db_rows(rows: List[Tuple[str, int]]) -> List[Dict[str, Any]]:
164     canonical_topic_names: Dict[str, Tuple[int, str]] = {}
165 
166     # Sort rows by max_message_id so that if a topic
167     # has many different casings, we use the most
168     # recent row.
169     rows = sorted(rows, key=lambda tup: tup[1])
170 
171     for (topic_name, max_message_id) in rows:
172         canonical_name = topic_name.lower()
173         canonical_topic_names[canonical_name] = (max_message_id, topic_name)
174 
175     history = []
176     for canonical_topic, (max_message_id, topic_name) in canonical_topic_names.items():
177         history.append(dict(
178             name=topic_name,
179             max_id=max_message_id)
180         )
181     return sorted(history, key=lambda x: -x['max_id'])
182 
183 def get_topic_history_for_stream(user_profile: UserProfile,
184                                  recipient: Recipient,
185                                  public_history: bool) -> List[Dict[str, Any]]:
186     cursor = connection.cursor()
187     if public_history:
188         query = '''
189         SELECT
190             "zerver_message"."subject" as topic,
191             max("zerver_message".id) as max_message_id
192         FROM "zerver_message"
193         WHERE (
194             "zerver_message"."recipient_id" = %s
195         )
196         GROUP BY (
197             "zerver_message"."subject"
198         )
199         ORDER BY max("zerver_message".id) DESC
200         '''
201         cursor.execute(query, [recipient.id])
202     else:
203         query = '''
204         SELECT
205             "zerver_message"."subject" as topic,
206             max("zerver_message".id) as max_message_id
207         FROM "zerver_message"
208         INNER JOIN "zerver_usermessage" ON (
209             "zerver_usermessage"."message_id" = "zerver_message"."id"
210         )
211         WHERE (
212             "zerver_usermessage"."user_profile_id" = %s AND
213             "zerver_message"."recipient_id" = %s
214         )
215         GROUP BY (
216             "zerver_message"."subject"
217         )
218         ORDER BY max("zerver_message".id) DESC
219         '''
220         cursor.execute(query, [user_profile.id, recipient.id])
221     rows = cursor.fetchall()
222     cursor.close()
223 
224     return generate_topic_history_from_db_rows(rows)
225 
226 def get_topic_history_for_web_public_stream(recipient: Recipient) -> List[Dict[str, Any]]:
227     cursor = connection.cursor()
228     query = '''
229     SELECT
230         "zerver_message"."subject" as topic,
231         max("zerver_message".id) as max_message_id
232     FROM "zerver_message"
233     WHERE (
234         "zerver_message"."recipient_id" = %s
235     )
236     GROUP BY (
237         "zerver_message"."subject"
238     )
239     ORDER BY max("zerver_message".id) DESC
240     '''
241     cursor.execute(query, [recipient.id])
242     rows = cursor.fetchall()
243     cursor.close()
244 
245     return generate_topic_history_from_db_rows(rows)
246
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/zerver/lib/topic.py b/zerver/lib/topic.py
--- a/zerver/lib/topic.py
+++ b/zerver/lib/topic.py
@@ -1,8 +1,5 @@
-import datetime
-
 from django.db import connection
 from django.db.models.query import QuerySet, Q
-from django.utils.timezone import now as timezone_now
 
 from sqlalchemy.sql import (
     column,
@@ -124,15 +121,7 @@
                                    new_stream: Optional[Stream]) -> List[Message]:
     propagate_query = Q(recipient = message.recipient, subject = orig_topic_name)
     if propagate_mode == 'change_all':
-        # We only change messages up to 7 days in the past, to avoid hammering our
-        # DB by changing an unbounded amount of messages
-        #
-        # TODO: Look at removing this restriction and/or add a "change_last_week"
-        # option; this behavior feels buggy.
-        before_bound = timezone_now() - datetime.timedelta(days=7)
-
-        propagate_query = (propagate_query & ~Q(id = message.id) &
-                           Q(date_sent__range=(before_bound, timezone_now())))
+        propagate_query = propagate_query & ~Q(id = message.id)
     if propagate_mode == 'change_later':
         propagate_query = propagate_query & Q(id__gt = message.id)

{"golden_diff": "diff --git a/zerver/lib/topic.py b/zerver/lib/topic.py\n--- a/zerver/lib/topic.py\n+++ b/zerver/lib/topic.py\n@@ -1,8 +1,5 @@\n-import datetime\n-\n from django.db import connection\n from django.db.models.query import QuerySet, Q\n-from django.utils.timezone import now as timezone_now\n \n from sqlalchemy.sql import (\n     column,\n@@ -124,15 +121,7 @@\n                                    new_stream: Optional[Stream]) -> List[Message]:\n     propagate_query = Q(recipient = message.recipient, subject = orig_topic_name)\n     if propagate_mode == 'change_all':\n-        # We only change messages up to 7 days in the past, to avoid hammering our\n-        # DB by changing an unbounded amount of messages\n-        #\n-        # TODO: Look at removing this restriction and/or add a \"change_last_week\"\n-        # option; this behavior feels buggy.\n-        before_bound = timezone_now() - datetime.timedelta(days=7)\n-\n-        propagate_query = (propagate_query & ~Q(id = message.id) &\n-                           Q(date_sent__range=(before_bound, timezone_now())))\n+        propagate_query = propagate_query & ~Q(id = message.id)\n     if propagate_mode == 'change_later':\n         propagate_query = propagate_query & Q(id__gt = message.id)\n", "issue": "Remove the TODO \"7 days\" restriction for edit and move topics\nRight now we have a restriction to move just the messages in the last week in method:\r\n`update_messages_for_topic_edit` file `zerver/lib/topic.py`\r\n```\r\n       # We only change messages up to 7 days in the past, to avoid hammering our\r\n        # DB by changing an unbounded amount of messages\r\n        #\r\n        # TODO: Look at removing this restriction and/or add a \"change_last_week\"\r\n        # option; this behavior feels buggy.\r\n```\n", "before_files": [{"content": "import datetime\n\nfrom django.db import connection\nfrom django.db.models.query import QuerySet, Q\nfrom django.utils.timezone import now as timezone_now\n\nfrom sqlalchemy.sql import (\n    column,\n    literal,\n    func,\n)\n\nfrom zerver.lib.request import REQ\nfrom zerver.models import (\n    Message,\n    Recipient,\n    Stream,\n    UserMessage,\n    UserProfile,\n)\n\nfrom typing import Any, Dict, List, Optional, Tuple\n\n# Only use these constants for events.\nORIG_TOPIC = \"orig_subject\"\nTOPIC_NAME = \"subject\"\nTOPIC_LINKS = \"topic_links\"\nMATCH_TOPIC = \"match_subject\"\n\n# This constant is actually embedded into\n# the JSON data for message edit history,\n# so we'll always need to handle legacy data\n# unless we do a pretty tricky migration.\nLEGACY_PREV_TOPIC = \"prev_subject\"\n\n# This constant is pretty closely coupled to the\n# database, but it's the JSON field.\nEXPORT_TOPIC_NAME = \"subject\"\n\n'''\nThe following functions are for user-facing APIs\nwhere we'll want to support \"subject\" for a while.\n'''\n\ndef get_topic_from_message_info(message_info: Dict[str, Any]) -> str:\n    '''\n    Use this where you are getting dicts that are based off of messages\n    that may come from the outside world, especially from third party\n    APIs and bots.\n\n    We prefer 'topic' to 'subject' here.  We expect at least one field\n    to be present (or the caller must know how to handle KeyError).\n    '''\n    if 'topic' in message_info:\n        return message_info['topic']\n\n    return message_info['subject']\n\ndef REQ_topic() -> Optional[str]:\n    # REQ handlers really return a REQ, but we\n    # lie to make the rest of the type matching work.\n    return REQ(\n        whence='topic',\n        aliases=['subject'],\n        converter=lambda x: x.strip(),\n        default=None,\n    )\n\n'''\nTRY TO KEEP THIS DIVIDING LINE.\n\nBelow this line we want to make it so that functions are only\nusing \"subject\" in the DB sense, and nothing customer facing.\n\n'''\n\n# This is used in low-level message functions in\n# zerver/lib/message.py, and it's not user facing.\nDB_TOPIC_NAME = \"subject\"\nMESSAGE__TOPIC = 'message__subject'\n\ndef topic_match_sa(topic_name: str) -> Any:\n    # _sa is short for Sql Alchemy, which we use mostly for\n    # queries that search messages\n    topic_cond = func.upper(column(\"subject\")) == func.upper(literal(topic_name))\n    return topic_cond\n\ndef topic_column_sa() -> Any:\n    return column(\"subject\")\n\ndef filter_by_exact_message_topic(query: QuerySet, message: Message) -> QuerySet:\n    topic_name = message.topic_name()\n    return query.filter(subject=topic_name)\n\ndef filter_by_topic_name_via_message(query: QuerySet, topic_name: str) -> QuerySet:\n    return query.filter(message__subject__iexact=topic_name)\n\ndef messages_for_topic(stream_recipient_id: int, topic_name: str) -> QuerySet:\n    return Message.objects.filter(\n        recipient_id=stream_recipient_id,\n        subject__iexact=topic_name,\n    )\n\ndef save_message_for_edit_use_case(message: Message) -> None:\n    message.save(update_fields=[TOPIC_NAME, \"content\", \"rendered_content\",\n                                \"rendered_content_version\", \"last_edit_time\",\n                                \"edit_history\", \"has_attachment\", \"has_image\",\n                                \"has_link\", \"recipient_id\"])\n\n\ndef user_message_exists_for_topic(user_profile: UserProfile,\n                                  recipient: Recipient,\n                                  topic_name: str) -> bool:\n    return UserMessage.objects.filter(\n        user_profile=user_profile,\n        message__recipient=recipient,\n        message__subject__iexact=topic_name,\n    ).exists()\n\ndef update_messages_for_topic_edit(message: Message,\n                                   propagate_mode: str,\n                                   orig_topic_name: str,\n                                   topic_name: Optional[str],\n                                   new_stream: Optional[Stream]) -> List[Message]:\n    propagate_query = Q(recipient = message.recipient, subject = orig_topic_name)\n    if propagate_mode == 'change_all':\n        # We only change messages up to 7 days in the past, to avoid hammering our\n        # DB by changing an unbounded amount of messages\n        #\n        # TODO: Look at removing this restriction and/or add a \"change_last_week\"\n        # option; this behavior feels buggy.\n        before_bound = timezone_now() - datetime.timedelta(days=7)\n\n        propagate_query = (propagate_query & ~Q(id = message.id) &\n                           Q(date_sent__range=(before_bound, timezone_now())))\n    if propagate_mode == 'change_later':\n        propagate_query = propagate_query & Q(id__gt = message.id)\n\n    messages = Message.objects.filter(propagate_query).select_related()\n\n    update_fields = {}\n\n    # Evaluate the query before running the update\n    messages_list = list(messages)\n\n    # The cached ORM objects are not changed by the upcoming\n    # messages.update(), and the remote cache update (done by the\n    # caller) requires the new value, so we manually update the\n    # objects in addition to sending a bulk query to the database.\n    if new_stream is not None:\n        update_fields[\"recipient\"] = new_stream.recipient\n        for m in messages_list:\n            m.recipient = new_stream.recipient\n    if topic_name is not None:\n        update_fields[\"subject\"] = topic_name\n        for m in messages_list:\n            m.set_topic_name(topic_name)\n\n    messages.update(**update_fields)\n\n    return messages_list\n\ndef generate_topic_history_from_db_rows(rows: List[Tuple[str, int]]) -> List[Dict[str, Any]]:\n    canonical_topic_names: Dict[str, Tuple[int, str]] = {}\n\n    # Sort rows by max_message_id so that if a topic\n    # has many different casings, we use the most\n    # recent row.\n    rows = sorted(rows, key=lambda tup: tup[1])\n\n    for (topic_name, max_message_id) in rows:\n        canonical_name = topic_name.lower()\n        canonical_topic_names[canonical_name] = (max_message_id, topic_name)\n\n    history = []\n    for canonical_topic, (max_message_id, topic_name) in canonical_topic_names.items():\n        history.append(dict(\n            name=topic_name,\n            max_id=max_message_id)\n        )\n    return sorted(history, key=lambda x: -x['max_id'])\n\ndef get_topic_history_for_stream(user_profile: UserProfile,\n                                 recipient: Recipient,\n                                 public_history: bool) -> List[Dict[str, Any]]:\n    cursor = connection.cursor()\n    if public_history:\n        query = '''\n        SELECT\n            \"zerver_message\".\"subject\" as topic,\n            max(\"zerver_message\".id) as max_message_id\n        FROM \"zerver_message\"\n        WHERE (\n            \"zerver_message\".\"recipient_id\" = %s\n        )\n        GROUP BY (\n            \"zerver_message\".\"subject\"\n        )\n        ORDER BY max(\"zerver_message\".id) DESC\n        '''\n        cursor.execute(query, [recipient.id])\n    else:\n        query = '''\n        SELECT\n            \"zerver_message\".\"subject\" as topic,\n            max(\"zerver_message\".id) as max_message_id\n        FROM \"zerver_message\"\n        INNER JOIN \"zerver_usermessage\" ON (\n            \"zerver_usermessage\".\"message_id\" = \"zerver_message\".\"id\"\n        )\n        WHERE (\n            \"zerver_usermessage\".\"user_profile_id\" = %s AND\n            \"zerver_message\".\"recipient_id\" = %s\n        )\n        GROUP BY (\n            \"zerver_message\".\"subject\"\n        )\n        ORDER BY max(\"zerver_message\".id) DESC\n        '''\n        cursor.execute(query, [user_profile.id, recipient.id])\n    rows = cursor.fetchall()\n    cursor.close()\n\n    return generate_topic_history_from_db_rows(rows)\n\ndef get_topic_history_for_web_public_stream(recipient: Recipient) -> List[Dict[str, Any]]:\n    cursor = connection.cursor()\n    query = '''\n    SELECT\n        \"zerver_message\".\"subject\" as topic,\n        max(\"zerver_message\".id) as max_message_id\n    FROM \"zerver_message\"\n    WHERE (\n        \"zerver_message\".\"recipient_id\" = %s\n    )\n    GROUP BY (\n        \"zerver_message\".\"subject\"\n    )\n    ORDER BY max(\"zerver_message\".id) DESC\n    '''\n    cursor.execute(query, [recipient.id])\n    rows = cursor.fetchall()\n    cursor.close()\n\n    return generate_topic_history_from_db_rows(rows)\n", "path": "zerver/lib/topic.py"}], "after_files": [{"content": "from django.db import connection\nfrom django.db.models.query import QuerySet, Q\n\nfrom sqlalchemy.sql import (\n    column,\n    literal,\n    func,\n)\n\nfrom zerver.lib.request import REQ\nfrom zerver.models import (\n    Message,\n    Recipient,\n    Stream,\n    UserMessage,\n    UserProfile,\n)\n\nfrom typing import Any, Dict, List, Optional, Tuple\n\n# Only use these constants for events.\nORIG_TOPIC = \"orig_subject\"\nTOPIC_NAME = \"subject\"\nTOPIC_LINKS = \"topic_links\"\nMATCH_TOPIC = \"match_subject\"\n\n# This constant is actually embedded into\n# the JSON data for message edit history,\n# so we'll always need to handle legacy data\n# unless we do a pretty tricky migration.\nLEGACY_PREV_TOPIC = \"prev_subject\"\n\n# This constant is pretty closely coupled to the\n# database, but it's the JSON field.\nEXPORT_TOPIC_NAME = \"subject\"\n\n'''\nThe following functions are for user-facing APIs\nwhere we'll want to support \"subject\" for a while.\n'''\n\ndef get_topic_from_message_info(message_info: Dict[str, Any]) -> str:\n    '''\n    Use this where you are getting dicts that are based off of messages\n    that may come from the outside world, especially from third party\n    APIs and bots.\n\n    We prefer 'topic' to 'subject' here.  We expect at least one field\n    to be present (or the caller must know how to handle KeyError).\n    '''\n    if 'topic' in message_info:\n        return message_info['topic']\n\n    return message_info['subject']\n\ndef REQ_topic() -> Optional[str]:\n    # REQ handlers really return a REQ, but we\n    # lie to make the rest of the type matching work.\n    return REQ(\n        whence='topic',\n        aliases=['subject'],\n        converter=lambda x: x.strip(),\n        default=None,\n    )\n\n'''\nTRY TO KEEP THIS DIVIDING LINE.\n\nBelow this line we want to make it so that functions are only\nusing \"subject\" in the DB sense, and nothing customer facing.\n\n'''\n\n# This is used in low-level message functions in\n# zerver/lib/message.py, and it's not user facing.\nDB_TOPIC_NAME = \"subject\"\nMESSAGE__TOPIC = 'message__subject'\n\ndef topic_match_sa(topic_name: str) -> Any:\n    # _sa is short for Sql Alchemy, which we use mostly for\n    # queries that search messages\n    topic_cond = func.upper(column(\"subject\")) == func.upper(literal(topic_name))\n    return topic_cond\n\ndef topic_column_sa() -> Any:\n    return column(\"subject\")\n\ndef filter_by_exact_message_topic(query: QuerySet, message: Message) -> QuerySet:\n    topic_name = message.topic_name()\n    return query.filter(subject=topic_name)\n\ndef filter_by_topic_name_via_message(query: QuerySet, topic_name: str) -> QuerySet:\n    return query.filter(message__subject__iexact=topic_name)\n\ndef messages_for_topic(stream_recipient_id: int, topic_name: str) -> QuerySet:\n    return Message.objects.filter(\n        recipient_id=stream_recipient_id,\n        subject__iexact=topic_name,\n    )\n\ndef save_message_for_edit_use_case(message: Message) -> None:\n    message.save(update_fields=[TOPIC_NAME, \"content\", \"rendered_content\",\n                                \"rendered_content_version\", \"last_edit_time\",\n                                \"edit_history\", \"has_attachment\", \"has_image\",\n                                \"has_link\", \"recipient_id\"])\n\n\ndef user_message_exists_for_topic(user_profile: UserProfile,\n                                  recipient: Recipient,\n                                  topic_name: str) -> bool:\n    return UserMessage.objects.filter(\n        user_profile=user_profile,\n        message__recipient=recipient,\n        message__subject__iexact=topic_name,\n    ).exists()\n\ndef update_messages_for_topic_edit(message: Message,\n                                   propagate_mode: str,\n                                   orig_topic_name: str,\n                                   topic_name: Optional[str],\n                                   new_stream: Optional[Stream]) -> List[Message]:\n    propagate_query = Q(recipient = message.recipient, subject = orig_topic_name)\n    if propagate_mode == 'change_all':\n        propagate_query = propagate_query & ~Q(id = message.id)\n    if propagate_mode == 'change_later':\n        propagate_query = propagate_query & Q(id__gt = message.id)\n\n    messages = Message.objects.filter(propagate_query).select_related()\n\n    update_fields = {}\n\n    # Evaluate the query before running the update\n    messages_list = list(messages)\n\n    # The cached ORM objects are not changed by the upcoming\n    # messages.update(), and the remote cache update (done by the\n    # caller) requires the new value, so we manually update the\n    # objects in addition to sending a bulk query to the database.\n    if new_stream is not None:\n        update_fields[\"recipient\"] = new_stream.recipient\n        for m in messages_list:\n            m.recipient = new_stream.recipient\n    if topic_name is not None:\n        update_fields[\"subject\"] = topic_name\n        for m in messages_list:\n            m.set_topic_name(topic_name)\n\n    messages.update(**update_fields)\n\n    return messages_list\n\ndef generate_topic_history_from_db_rows(rows: List[Tuple[str, int]]) -> List[Dict[str, Any]]:\n    canonical_topic_names = {}  # type: Dict[str, Tuple[int, str]]\n\n    # Sort rows by max_message_id so that if a topic\n    # has many different casings, we use the most\n    # recent row.\n    rows = sorted(rows, key=lambda tup: tup[1])\n\n    for (topic_name, max_message_id) in rows:\n        canonical_name = topic_name.lower()\n        canonical_topic_names[canonical_name] = (max_message_id, topic_name)\n\n    history = []\n    for canonical_topic, (max_message_id, topic_name) in canonical_topic_names.items():\n        history.append(dict(\n            name=topic_name,\n            max_id=max_message_id)\n        )\n    return sorted(history, key=lambda x: -x['max_id'])\n\ndef get_topic_history_for_stream(user_profile: UserProfile,\n                                 recipient: Recipient,\n                                 public_history: bool) -> List[Dict[str, Any]]:\n    cursor = connection.cursor()\n    if public_history:\n        query = '''\n        SELECT\n            \"zerver_message\".\"subject\" as topic,\n            max(\"zerver_message\".id) as max_message_id\n        FROM \"zerver_message\"\n        WHERE (\n            \"zerver_message\".\"recipient_id\" = %s\n        )\n        GROUP BY (\n            \"zerver_message\".\"subject\"\n        )\n        ORDER BY max(\"zerver_message\".id) DESC\n        '''\n        cursor.execute(query, [recipient.id])\n    else:\n        query = '''\n        SELECT\n            \"zerver_message\".\"subject\" as topic,\n            max(\"zerver_message\".id) as max_message_id\n        FROM \"zerver_message\"\n        INNER JOIN \"zerver_usermessage\" ON (\n            \"zerver_usermessage\".\"message_id\" = \"zerver_message\".\"id\"\n        )\n        WHERE (\n            \"zerver_usermessage\".\"user_profile_id\" = %s AND\n            \"zerver_message\".\"recipient_id\" = %s\n        )\n        GROUP BY (\n            \"zerver_message\".\"subject\"\n        )\n        ORDER BY max(\"zerver_message\".id) DESC\n        '''\n        cursor.execute(query, [user_profile.id, recipient.id])\n    rows = cursor.fetchall()\n    cursor.close()\n\n    return generate_topic_history_from_db_rows(rows)\n\ndef get_topic_history_for_web_public_stream(recipient: Recipient) -> List[Dict[str, Any]]:\n    cursor = connection.cursor()\n    query = '''\n    SELECT\n        \"zerver_message\".\"subject\" as topic,\n        max(\"zerver_message\".id) as max_message_id\n    FROM \"zerver_message\"\n    WHERE (\n        \"zerver_message\".\"recipient_id\" = %s\n    )\n    GROUP BY (\n        \"zerver_message\".\"subject\"\n    )\n    ORDER BY max(\"zerver_message\".id) DESC\n    '''\n    cursor.execute(query, [recipient.id])\n    rows = cursor.fetchall()\n    cursor.close()\n\n    return generate_topic_history_from_db_rows(rows)\n", "path": "zerver/lib/topic.py"}]}