models: Extract zerver.models.user_topics.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
This commit is contained in:
Anders Kaseorg 2023-12-14 19:23:33 -08:00 committed by Tim Abbott
parent 4aa2d76bea
commit ea2ee61b4c
2 changed files with 79 additions and 67 deletions

View File

@ -3,7 +3,7 @@
import hashlib import hashlib
import time import time
from datetime import datetime, timedelta, timezone from datetime import timedelta
from typing import Any, Callable, Dict, List, Optional, Tuple, TypedDict, TypeVar, Union from typing import Any, Callable, Dict, List, Optional, Tuple, TypedDict, TypeVar, Union
import orjson import orjson
@ -18,7 +18,7 @@ from django.core.serializers.json import DjangoJSONEncoder
from django.db import models from django.db import models
from django.db.backends.base.base import BaseDatabaseWrapper from django.db.backends.base.base import BaseDatabaseWrapper
from django.db.models import CASCADE, Exists, F, OuterRef, Q, QuerySet from django.db.models import CASCADE, Exists, F, OuterRef, Q, QuerySet
from django.db.models.functions import Lower, Upper from django.db.models.functions import Upper
from django.db.models.signals import post_delete, post_save from django.db.models.signals import post_delete, post_save
from django.db.models.sql.compiler import SQLCompiler from django.db.models.sql.compiler import SQLCompiler
from django.utils.timezone import now as timezone_now from django.utils.timezone import now as timezone_now
@ -83,6 +83,7 @@ from zerver.models.streams import DefaultStream as DefaultStream
from zerver.models.streams import DefaultStreamGroup as DefaultStreamGroup from zerver.models.streams import DefaultStreamGroup as DefaultStreamGroup
from zerver.models.streams import Stream as Stream from zerver.models.streams import Stream as Stream
from zerver.models.streams import Subscription as Subscription from zerver.models.streams import Subscription as Subscription
from zerver.models.user_topics import UserTopic as UserTopic
from zerver.models.users import RealmUserDefault as RealmUserDefault from zerver.models.users import RealmUserDefault as RealmUserDefault
from zerver.models.users import UserBaseSettings as UserBaseSettings from zerver.models.users import UserBaseSettings as UserBaseSettings
from zerver.models.users import UserProfile as UserProfile from zerver.models.users import UserProfile as UserProfile
@ -142,71 +143,6 @@ def query_for_ids(
return query return query
class UserTopic(models.Model):
user_profile = models.ForeignKey(UserProfile, on_delete=CASCADE)
stream = models.ForeignKey(Stream, on_delete=CASCADE)
recipient = models.ForeignKey(Recipient, on_delete=CASCADE)
topic_name = models.CharField(max_length=MAX_TOPIC_NAME_LENGTH)
# The default value for last_updated is a few weeks before tracking
# of when topics were muted was first introduced. It's designed
# to be obviously incorrect so that one can tell it's backfilled data.
last_updated = models.DateTimeField(default=datetime(2020, 1, 1, 0, 0, tzinfo=timezone.utc))
class VisibilityPolicy(models.IntegerChoices):
# A normal muted topic. No notifications and unreads hidden.
MUTED = 1, "Muted topic"
# This topic will behave like an unmuted topic in an unmuted stream even if it
# belongs to a muted stream.
UNMUTED = 2, "Unmuted topic in muted stream"
# This topic will behave like `UNMUTED`, plus some additional
# display and/or notifications priority that is TBD and likely to
# be configurable; see #6027. Not yet implemented.
FOLLOWED = 3, "Followed topic"
# Implicitly, if a UserTopic does not exist, the (user, topic)
# pair should have normal behavior for that (user, stream) pair.
# We use this in our code to represent the condition in the comment above.
INHERIT = 0, "User's default policy for the stream."
visibility_policy = models.SmallIntegerField(
choices=VisibilityPolicy.choices, default=VisibilityPolicy.MUTED
)
class Meta:
constraints = [
models.UniqueConstraint(
"user_profile",
"stream",
Lower("topic_name"),
name="usertopic_case_insensitive_topic_uniq",
),
]
indexes = [
models.Index("stream", Upper("topic_name"), name="zerver_mutedtopic_stream_topic"),
# This index is designed to optimize queries fetching the
# set of users who have special policy for a stream,
# e.g. for the send-message code paths.
models.Index(
fields=("stream", "topic_name", "visibility_policy", "user_profile"),
name="zerver_usertopic_stream_topic_user_visibility_idx",
),
# This index is useful for handling API requests fetching the
# muted topics for a given user or user/stream pair.
models.Index(
fields=("user_profile", "visibility_policy", "stream", "topic_name"),
name="zerver_usertopic_user_visibility_idx",
),
]
@override
def __str__(self) -> str:
return f"({self.user_profile.email}, {self.stream.name}, {self.topic_name}, {self.last_updated})"
class MutedUser(models.Model): class MutedUser(models.Model):
user_profile = models.ForeignKey(UserProfile, related_name="muter", on_delete=CASCADE) user_profile = models.ForeignKey(UserProfile, related_name="muter", on_delete=CASCADE)
muted_user = models.ForeignKey(UserProfile, related_name="muted", on_delete=CASCADE) muted_user = models.ForeignKey(UserProfile, related_name="muted", on_delete=CASCADE)

View File

@ -0,0 +1,76 @@
from datetime import datetime, timezone
from django.db import models
from django.db.models import CASCADE
from django.db.models.functions import Lower, Upper
from typing_extensions import override
from zerver.models.constants import MAX_TOPIC_NAME_LENGTH
from zerver.models.recipients import Recipient
from zerver.models.streams import Stream
from zerver.models.users import UserProfile
class UserTopic(models.Model):
user_profile = models.ForeignKey(UserProfile, on_delete=CASCADE)
stream = models.ForeignKey(Stream, on_delete=CASCADE)
recipient = models.ForeignKey(Recipient, on_delete=CASCADE)
topic_name = models.CharField(max_length=MAX_TOPIC_NAME_LENGTH)
# The default value for last_updated is a few weeks before tracking
# of when topics were muted was first introduced. It's designed
# to be obviously incorrect so that one can tell it's backfilled data.
last_updated = models.DateTimeField(default=datetime(2020, 1, 1, 0, 0, tzinfo=timezone.utc))
class VisibilityPolicy(models.IntegerChoices):
# A normal muted topic. No notifications and unreads hidden.
MUTED = 1, "Muted topic"
# This topic will behave like an unmuted topic in an unmuted stream even if it
# belongs to a muted stream.
UNMUTED = 2, "Unmuted topic in muted stream"
# This topic will behave like `UNMUTED`, plus some additional
# display and/or notifications priority that is TBD and likely to
# be configurable; see #6027. Not yet implemented.
FOLLOWED = 3, "Followed topic"
# Implicitly, if a UserTopic does not exist, the (user, topic)
# pair should have normal behavior for that (user, stream) pair.
# We use this in our code to represent the condition in the comment above.
INHERIT = 0, "User's default policy for the stream."
visibility_policy = models.SmallIntegerField(
choices=VisibilityPolicy.choices, default=VisibilityPolicy.MUTED
)
class Meta:
constraints = [
models.UniqueConstraint(
"user_profile",
"stream",
Lower("topic_name"),
name="usertopic_case_insensitive_topic_uniq",
),
]
indexes = [
models.Index("stream", Upper("topic_name"), name="zerver_mutedtopic_stream_topic"),
# This index is designed to optimize queries fetching the
# set of users who have special policy for a stream,
# e.g. for the send-message code paths.
models.Index(
fields=("stream", "topic_name", "visibility_policy", "user_profile"),
name="zerver_usertopic_stream_topic_user_visibility_idx",
),
# This index is useful for handling API requests fetching the
# muted topics for a given user or user/stream pair.
models.Index(
fields=("user_profile", "visibility_policy", "stream", "topic_name"),
name="zerver_usertopic_user_visibility_idx",
),
]
@override
def __str__(self) -> str:
return f"({self.user_profile.email}, {self.stream.name}, {self.topic_name}, {self.last_updated})"