models: Add explicit id fields for better type checking.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
This commit is contained in:
Anders Kaseorg 2020-07-01 18:13:26 -07:00 committed by Tim Abbott
parent 9b7c6828ec
commit c08ee904d8
6 changed files with 58 additions and 9 deletions

View File

@ -2872,9 +2872,11 @@ def bulk_add_subscriptions(streams: Iterable[Stream],
set(occupied_streams_after) - set(occupied_streams_before) set(occupied_streams_after) - set(occupied_streams_before)
if not stream.invite_only] if not stream.invite_only]
if new_occupied_streams and not from_stream_creation: if new_occupied_streams and not from_stream_creation:
event = dict(type="stream", op="occupy", event: Dict[str, object] = dict(
streams=[stream.to_dict() type="stream",
for stream in new_occupied_streams]) op="occupy",
streams=[stream.to_dict() for stream in new_occupied_streams],
)
send_event(realm, event, active_user_ids(realm.id)) send_event(realm, event, active_user_ids(realm.id))
# Notify all existing users on streams that users have joined # Notify all existing users on streams that users have joined

View File

@ -176,6 +176,8 @@ class Realm(models.Model):
'RemoteUser', 'AzureAD', 'SAML', 'GitLab', 'Apple'] 'RemoteUser', 'AzureAD', 'SAML', 'GitLab', 'Apple']
SUBDOMAIN_FOR_ROOT_DOMAIN = '' SUBDOMAIN_FOR_ROOT_DOMAIN = ''
id: int = models.AutoField(auto_created=True, primary_key=True, verbose_name='ID')
# User-visible display name and description used on e.g. the organization homepage # User-visible display name and description used on e.g. the organization homepage
name: Optional[str] = models.CharField(max_length=MAX_REALM_NAME_LENGTH, null=True) name: Optional[str] = models.CharField(max_length=MAX_REALM_NAME_LENGTH, null=True)
description: str = models.TextField(default="") description: str = models.TextField(default="")
@ -611,6 +613,7 @@ def avatar_changes_disabled(realm: Realm) -> bool:
class RealmDomain(models.Model): class RealmDomain(models.Model):
"""For an organization with emails_restricted_to_domains enabled, the list of """For an organization with emails_restricted_to_domains enabled, the list of
allowed domains""" allowed domains"""
id: int = models.AutoField(auto_created=True, primary_key=True, verbose_name='ID')
realm: Realm = models.ForeignKey(Realm, on_delete=CASCADE) realm: Realm = models.ForeignKey(Realm, on_delete=CASCADE)
# should always be stored lowercase # should always be stored lowercase
domain: str = models.CharField(max_length=80, db_index=True) domain: str = models.CharField(max_length=80, db_index=True)
@ -645,6 +648,7 @@ def get_realm_domains(realm: Realm) -> List[Dict[str, str]]:
return list(realm.realmdomain_set.values('domain', 'allow_subdomains')) return list(realm.realmdomain_set.values('domain', 'allow_subdomains'))
class RealmEmoji(models.Model): class RealmEmoji(models.Model):
id: int = models.AutoField(auto_created=True, primary_key=True, verbose_name='ID')
author: Optional["UserProfile"] = models.ForeignKey( author: Optional["UserProfile"] = models.ForeignKey(
"UserProfile", blank=True, null=True, on_delete=CASCADE, "UserProfile", blank=True, null=True, on_delete=CASCADE,
) )
@ -732,6 +736,7 @@ class RealmFilter(models.Model):
"""Realm-specific regular expressions to automatically linkify certain """Realm-specific regular expressions to automatically linkify certain
strings inside the markdown processor. See "Custom filters" in the settings UI. strings inside the markdown processor. See "Custom filters" in the settings UI.
""" """
id: int = models.AutoField(auto_created=True, primary_key=True, verbose_name='ID')
realm: Realm = models.ForeignKey(Realm, on_delete=CASCADE) realm: Realm = models.ForeignKey(Realm, on_delete=CASCADE)
pattern: str = models.TextField(validators=[filter_pattern_validator]) pattern: str = models.TextField(validators=[filter_pattern_validator])
url_format_string: str = models.TextField(validators=[URLValidator(), filter_format_validator]) url_format_string: str = models.TextField(validators=[URLValidator(), filter_format_validator])
@ -798,6 +803,7 @@ def flush_per_request_caches() -> None:
# (used by the Message table) to the type-specific unique id (the # (used by the Message table) to the type-specific unique id (the
# stream id, user_profile id, or huddle id). # stream id, user_profile id, or huddle id).
class Recipient(models.Model): class Recipient(models.Model):
id: int = models.AutoField(auto_created=True, primary_key=True, verbose_name='ID')
type_id: int = models.IntegerField(db_index=True) type_id: int = models.IntegerField(db_index=True)
type: int = models.PositiveSmallIntegerField(db_index=True) type: int = models.PositiveSmallIntegerField(db_index=True)
# Valid types are {personal, stream, huddle} # Valid types are {personal, stream, huddle}
@ -857,6 +863,8 @@ class UserProfile(AbstractBaseUser, PermissionsMixin):
EMBEDDED_BOT, EMBEDDED_BOT,
] ]
id: int = models.AutoField(auto_created=True, primary_key=True, verbose_name='ID')
# For historical reasons, Zulip has two email fields. The # For historical reasons, Zulip has two email fields. The
# `delivery_email` field is the user's email address, where all # `delivery_email` field is the user's email address, where all
# email notifications will be sent, and is used for all # email notifications will be sent, and is used for all
@ -1297,6 +1305,7 @@ class PasswordTooWeakError(Exception):
pass pass
class UserGroup(models.Model): class UserGroup(models.Model):
id: int = models.AutoField(auto_created=True, primary_key=True, verbose_name='ID')
name: str = models.CharField(max_length=100) name: str = models.CharField(max_length=100)
members: Manager = models.ManyToManyField(UserProfile, through='UserGroupMembership') members: Manager = models.ManyToManyField(UserProfile, through='UserGroupMembership')
realm: Realm = models.ForeignKey(Realm, on_delete=CASCADE) realm: Realm = models.ForeignKey(Realm, on_delete=CASCADE)
@ -1306,6 +1315,7 @@ class UserGroup(models.Model):
unique_together = (('realm', 'name'),) unique_together = (('realm', 'name'),)
class UserGroupMembership(models.Model): class UserGroupMembership(models.Model):
id: int = models.AutoField(auto_created=True, primary_key=True, verbose_name='ID')
user_group: UserGroup = models.ForeignKey(UserGroup, on_delete=CASCADE) user_group: UserGroup = models.ForeignKey(UserGroup, on_delete=CASCADE)
user_profile: UserProfile = models.ForeignKey(UserProfile, on_delete=CASCADE) user_profile: UserProfile = models.ForeignKey(UserProfile, on_delete=CASCADE)
@ -1348,6 +1358,7 @@ class PreregistrationUser(models.Model):
# from the authentication step and pass it to the registration # from the authentication step and pass it to the registration
# form. # form.
id: int = models.AutoField(auto_created=True, primary_key=True, verbose_name='ID')
email: str = models.EmailField() email: str = models.EmailField()
# If the pre-registration process provides a suggested full name for this user, # If the pre-registration process provides a suggested full name for this user,
@ -1390,12 +1401,14 @@ def filter_to_valid_prereg_users(query: QuerySet) -> QuerySet:
invited_at__gte=lowest_datetime) invited_at__gte=lowest_datetime)
class MultiuseInvite(models.Model): class MultiuseInvite(models.Model):
id: int = models.AutoField(auto_created=True, primary_key=True, verbose_name='ID')
referred_by: UserProfile = models.ForeignKey(UserProfile, on_delete=CASCADE) # Optional[UserProfile] referred_by: UserProfile = models.ForeignKey(UserProfile, on_delete=CASCADE) # Optional[UserProfile]
streams: Manager = models.ManyToManyField('Stream') streams: Manager = models.ManyToManyField('Stream')
realm: Realm = models.ForeignKey(Realm, on_delete=CASCADE) realm: Realm = models.ForeignKey(Realm, on_delete=CASCADE)
invited_as: int = models.PositiveSmallIntegerField(default=PreregistrationUser.INVITE_AS['MEMBER']) invited_as: int = models.PositiveSmallIntegerField(default=PreregistrationUser.INVITE_AS['MEMBER'])
class EmailChangeStatus(models.Model): class EmailChangeStatus(models.Model):
id: int = models.AutoField(auto_created=True, primary_key=True, verbose_name='ID')
new_email: str = models.EmailField() new_email: str = models.EmailField()
old_email: str = models.EmailField() old_email: str = models.EmailField()
updated_at: datetime.datetime = models.DateTimeField(auto_now=True) updated_at: datetime.datetime = models.DateTimeField(auto_now=True)
@ -1435,7 +1448,9 @@ class AbstractPushDeviceToken(models.Model):
abstract = True abstract = True
class PushDeviceToken(AbstractPushDeviceToken): class PushDeviceToken(AbstractPushDeviceToken):
# The user who's device this is id: int = models.AutoField(auto_created=True, primary_key=True, verbose_name='ID')
# The user whose device this is
user: UserProfile = models.ForeignKey(UserProfile, db_index=True, on_delete=CASCADE) user: UserProfile = models.ForeignKey(UserProfile, db_index=True, on_delete=CASCADE)
class Meta: class Meta:
@ -1448,6 +1463,7 @@ class Stream(models.Model):
MAX_NAME_LENGTH = 60 MAX_NAME_LENGTH = 60
MAX_DESCRIPTION_LENGTH = 1024 MAX_DESCRIPTION_LENGTH = 1024
id: int = models.AutoField(auto_created=True, primary_key=True, verbose_name='ID')
name: str = models.CharField(max_length=MAX_NAME_LENGTH, db_index=True) name: str = models.CharField(max_length=MAX_NAME_LENGTH, db_index=True)
realm: Realm = models.ForeignKey(Realm, db_index=True, on_delete=CASCADE) realm: Realm = models.ForeignKey(Realm, db_index=True, on_delete=CASCADE)
date_created: datetime.datetime = models.DateTimeField(default=timezone_now) date_created: datetime.datetime = models.DateTimeField(default=timezone_now)
@ -1566,6 +1582,7 @@ post_save.connect(flush_stream, sender=Stream)
post_delete.connect(flush_stream, sender=Stream) post_delete.connect(flush_stream, sender=Stream)
class MutedTopic(models.Model): class MutedTopic(models.Model):
id: int = models.AutoField(auto_created=True, primary_key=True, verbose_name='ID')
user_profile: UserProfile = models.ForeignKey(UserProfile, on_delete=CASCADE) user_profile: UserProfile = models.ForeignKey(UserProfile, on_delete=CASCADE)
stream: Stream = models.ForeignKey(Stream, on_delete=CASCADE) stream: Stream = models.ForeignKey(Stream, on_delete=CASCADE)
recipient: Recipient = models.ForeignKey(Recipient, on_delete=CASCADE) recipient: Recipient = models.ForeignKey(Recipient, on_delete=CASCADE)
@ -1582,6 +1599,7 @@ class MutedTopic(models.Model):
return (f"<MutedTopic: ({self.user_profile.email}, {self.stream.name}, {self.topic_name}, {self.date_muted})>") return (f"<MutedTopic: ({self.user_profile.email}, {self.stream.name}, {self.topic_name}, {self.date_muted})>")
class Client(models.Model): class Client(models.Model):
id: int = models.AutoField(auto_created=True, primary_key=True, verbose_name='ID')
name: str = models.CharField(max_length=30, db_index=True, unique=True) name: str = models.CharField(max_length=30, db_index=True, unique=True)
def __str__(self) -> str: def __str__(self) -> str:
@ -1739,6 +1757,7 @@ class AbstractMessage(models.Model):
return f"<{self.__class__.__name__}: {display_recipient} / {self.subject} / {self.sender}>" return f"<{self.__class__.__name__}: {display_recipient} / {self.subject} / {self.sender}>"
class ArchiveTransaction(models.Model): class ArchiveTransaction(models.Model):
id: int = models.AutoField(auto_created=True, primary_key=True, verbose_name='ID')
timestamp: datetime.datetime = models.DateTimeField(default=timezone_now, db_index=True) timestamp: datetime.datetime = models.DateTimeField(default=timezone_now, db_index=True)
# Marks if the data archived in this transaction has been restored: # Marks if the data archived in this transaction has been restored:
restored: bool = models.BooleanField(default=False, db_index=True) restored: bool = models.BooleanField(default=False, db_index=True)
@ -1765,9 +1784,11 @@ class ArchivedMessage(AbstractMessage):
are permanently deleted. This is an important part of a robust are permanently deleted. This is an important part of a robust
'message retention' feature. 'message retention' feature.
""" """
id: int = models.AutoField(auto_created=True, primary_key=True, verbose_name='ID')
archive_transaction: ArchiveTransaction = models.ForeignKey(ArchiveTransaction, on_delete=CASCADE) archive_transaction: ArchiveTransaction = models.ForeignKey(ArchiveTransaction, on_delete=CASCADE)
class Message(AbstractMessage): class Message(AbstractMessage):
id: int = models.AutoField(auto_created=True, primary_key=True, verbose_name='ID')
def topic_name(self) -> str: def topic_name(self) -> str:
""" """
@ -1869,6 +1890,7 @@ class AbstractSubMessage(models.Model):
abstract = True abstract = True
class SubMessage(AbstractSubMessage): class SubMessage(AbstractSubMessage):
id: int = models.AutoField(auto_created=True, primary_key=True, verbose_name='ID')
message: Message = models.ForeignKey(Message, on_delete=CASCADE) message: Message = models.ForeignKey(Message, on_delete=CASCADE)
@staticmethod @staticmethod
@ -1879,6 +1901,7 @@ class SubMessage(AbstractSubMessage):
return list(query) return list(query)
class ArchivedSubMessage(AbstractSubMessage): class ArchivedSubMessage(AbstractSubMessage):
id: int = models.AutoField(auto_created=True, primary_key=True, verbose_name='ID')
message: ArchivedMessage = models.ForeignKey(ArchivedMessage, on_delete=CASCADE) message: ArchivedMessage = models.ForeignKey(ArchivedMessage, on_delete=CASCADE)
post_save.connect(flush_submessage, sender=SubMessage) post_save.connect(flush_submessage, sender=SubMessage)
@ -1926,6 +1949,7 @@ class AbstractReaction(models.Model):
("user_profile", "message", "reaction_type", "emoji_code")) ("user_profile", "message", "reaction_type", "emoji_code"))
class Reaction(AbstractReaction): class Reaction(AbstractReaction):
id: int = models.AutoField(auto_created=True, primary_key=True, verbose_name='ID')
message: Message = models.ForeignKey(Message, on_delete=CASCADE) message: Message = models.ForeignKey(Message, on_delete=CASCADE)
@staticmethod @staticmethod
@ -1938,6 +1962,7 @@ class Reaction(AbstractReaction):
return f"{self.user_profile.email} / {self.message.id} / {self.emoji_name}" return f"{self.user_profile.email} / {self.message.id} / {self.emoji_name}"
class ArchivedReaction(AbstractReaction): class ArchivedReaction(AbstractReaction):
id: int = models.AutoField(auto_created=True, primary_key=True, verbose_name='ID')
message: ArchivedMessage = models.ForeignKey(ArchivedMessage, on_delete=CASCADE) message: ArchivedMessage = models.ForeignKey(ArchivedMessage, on_delete=CASCADE)
# Whenever a message is sent, for each user subscribed to the # Whenever a message is sent, for each user subscribed to the
@ -2104,9 +2129,11 @@ class ArchivedAttachment(AbstractAttachment):
before they are permanently deleted. This is an important part of before they are permanently deleted. This is an important part of
a robust 'message retention' feature. a robust 'message retention' feature.
""" """
id: int = models.AutoField(auto_created=True, primary_key=True, verbose_name='ID')
messages: Manager = models.ManyToManyField(ArchivedMessage) messages: Manager = models.ManyToManyField(ArchivedMessage)
class Attachment(AbstractAttachment): class Attachment(AbstractAttachment):
id: int = models.AutoField(auto_created=True, primary_key=True, verbose_name='ID')
messages: Manager = models.ManyToManyField(Message) messages: Manager = models.ManyToManyField(Message)
def is_claimed(self) -> bool: def is_claimed(self) -> bool:
@ -2174,6 +2201,7 @@ def get_old_unclaimed_attachments(weeks_ago: int) -> Sequence[Attachment]:
return old_attachments return old_attachments
class Subscription(models.Model): class Subscription(models.Model):
id: int = models.AutoField(auto_created=True, primary_key=True, verbose_name='ID')
user_profile: UserProfile = models.ForeignKey(UserProfile, on_delete=CASCADE) user_profile: UserProfile = models.ForeignKey(UserProfile, on_delete=CASCADE)
recipient: Recipient = models.ForeignKey(Recipient, on_delete=CASCADE) recipient: Recipient = models.ForeignKey(Recipient, on_delete=CASCADE)
@ -2393,6 +2421,7 @@ def is_cross_realm_bot_email(email: str) -> bool:
# below, to support efficiently mapping from a set of users to the # below, to support efficiently mapping from a set of users to the
# corresponding Huddle object. # corresponding Huddle object.
class Huddle(models.Model): class Huddle(models.Model):
id: int = models.AutoField(auto_created=True, primary_key=True, verbose_name='ID')
# TODO: We should consider whether using # TODO: We should consider whether using
# CommaSeparatedIntegerField would be better. # CommaSeparatedIntegerField would be better.
huddle_hash: str = models.CharField(max_length=40, db_index=True, unique=True) huddle_hash: str = models.CharField(max_length=40, db_index=True, unique=True)
@ -2427,6 +2456,7 @@ def get_huddle_backend(huddle_hash: str, id_list: List[int]) -> Huddle:
return huddle return huddle
class UserActivity(models.Model): class UserActivity(models.Model):
id: int = models.AutoField(auto_created=True, primary_key=True, verbose_name='ID')
user_profile: UserProfile = models.ForeignKey(UserProfile, on_delete=CASCADE) user_profile: UserProfile = models.ForeignKey(UserProfile, on_delete=CASCADE)
client: Client = models.ForeignKey(Client, on_delete=CASCADE) client: Client = models.ForeignKey(Client, on_delete=CASCADE)
query: str = models.CharField(max_length=50, db_index=True) query: str = models.CharField(max_length=50, db_index=True)
@ -2440,6 +2470,7 @@ class UserActivity(models.Model):
class UserActivityInterval(models.Model): class UserActivityInterval(models.Model):
MIN_INTERVAL_LENGTH = datetime.timedelta(minutes=15) MIN_INTERVAL_LENGTH = datetime.timedelta(minutes=15)
id: int = models.AutoField(auto_created=True, primary_key=True, verbose_name='ID')
user_profile: UserProfile = models.ForeignKey(UserProfile, on_delete=CASCADE) user_profile: UserProfile = models.ForeignKey(UserProfile, on_delete=CASCADE)
start: datetime.datetime = models.DateTimeField('start time', db_index=True) start: datetime.datetime = models.DateTimeField('start time', db_index=True)
end: datetime.datetime = models.DateTimeField('end time', db_index=True) end: datetime.datetime = models.DateTimeField('end time', db_index=True)
@ -2457,6 +2488,7 @@ class UserPresence(models.Model):
("realm", "timestamp"), ("realm", "timestamp"),
] ]
id: int = models.AutoField(auto_created=True, primary_key=True, verbose_name='ID')
user_profile: UserProfile = models.ForeignKey(UserProfile, on_delete=CASCADE) user_profile: UserProfile = models.ForeignKey(UserProfile, on_delete=CASCADE)
realm: Realm = models.ForeignKey(Realm, on_delete=CASCADE) realm: Realm = models.ForeignKey(Realm, on_delete=CASCADE)
client: Client = models.ForeignKey(Client, on_delete=CASCADE) client: Client = models.ForeignKey(Client, on_delete=CASCADE)
@ -2523,6 +2555,7 @@ class UserPresence(models.Model):
return status_val return status_val
class UserStatus(models.Model): class UserStatus(models.Model):
id: int = models.AutoField(auto_created=True, primary_key=True, verbose_name='ID')
user_profile: UserProfile = models.OneToOneField(UserProfile, on_delete=CASCADE) user_profile: UserProfile = models.OneToOneField(UserProfile, on_delete=CASCADE)
timestamp: datetime.datetime = models.DateTimeField() timestamp: datetime.datetime = models.DateTimeField()
@ -2535,6 +2568,7 @@ class UserStatus(models.Model):
status_text: str = models.CharField(max_length=255, default='') status_text: str = models.CharField(max_length=255, default='')
class DefaultStream(models.Model): class DefaultStream(models.Model):
id: int = models.AutoField(auto_created=True, primary_key=True, verbose_name='ID')
realm: Realm = models.ForeignKey(Realm, on_delete=CASCADE) realm: Realm = models.ForeignKey(Realm, on_delete=CASCADE)
stream: Stream = models.ForeignKey(Stream, on_delete=CASCADE) stream: Stream = models.ForeignKey(Stream, on_delete=CASCADE)
@ -2543,6 +2577,8 @@ class DefaultStream(models.Model):
class DefaultStreamGroup(models.Model): class DefaultStreamGroup(models.Model):
MAX_NAME_LENGTH = 60 MAX_NAME_LENGTH = 60
id: int = models.AutoField(auto_created=True, primary_key=True, verbose_name='ID')
name: str = models.CharField(max_length=MAX_NAME_LENGTH, db_index=True) name: str = models.CharField(max_length=MAX_NAME_LENGTH, db_index=True)
realm: Realm = models.ForeignKey(Realm, on_delete=CASCADE) realm: Realm = models.ForeignKey(Realm, on_delete=CASCADE)
streams: Manager = models.ManyToManyField('Stream') streams: Manager = models.ManyToManyField('Stream')
@ -2575,6 +2611,7 @@ class ScheduledEmail(AbstractScheduledJob):
# ScheduledEmails for use in clear_scheduled_emails; the # ScheduledEmails for use in clear_scheduled_emails; the
# recipients used for actually sending messages are stored in the # recipients used for actually sending messages are stored in the
# data field of AbstractScheduledJob. # data field of AbstractScheduledJob.
id: int = models.AutoField(auto_created=True, primary_key=True, verbose_name='ID')
users: Manager = models.ManyToManyField(UserProfile) users: Manager = models.ManyToManyField(UserProfile)
# Just the address part of a full "name <address>" email address # Just the address part of a full "name <address>" email address
address: Optional[str] = models.EmailField(null=True, db_index=True) address: Optional[str] = models.EmailField(null=True, db_index=True)
@ -2592,6 +2629,7 @@ class MissedMessageEmailAddress(models.Model):
EXPIRY_SECONDS = 60 * 60 * 24 * 5 EXPIRY_SECONDS = 60 * 60 * 24 * 5
ALLOWED_USES = 1 ALLOWED_USES = 1
id: int = models.AutoField(auto_created=True, primary_key=True, verbose_name='ID')
message: Message = models.ForeignKey(Message, on_delete=CASCADE) message: Message = models.ForeignKey(Message, on_delete=CASCADE)
user_profile: UserProfile = models.ForeignKey(UserProfile, on_delete=CASCADE) user_profile: UserProfile = models.ForeignKey(UserProfile, on_delete=CASCADE)
email_token: str = models.CharField(max_length=34, unique=True, db_index=True) email_token: str = models.CharField(max_length=34, unique=True, db_index=True)
@ -2614,6 +2652,7 @@ class MissedMessageEmailAddress(models.Model):
self.save(update_fields=["times_used"]) self.save(update_fields=["times_used"])
class ScheduledMessage(models.Model): class ScheduledMessage(models.Model):
id: int = models.AutoField(auto_created=True, primary_key=True, verbose_name='ID')
sender: UserProfile = models.ForeignKey(UserProfile, on_delete=CASCADE) sender: UserProfile = models.ForeignKey(UserProfile, on_delete=CASCADE)
recipient: Recipient = models.ForeignKey(Recipient, on_delete=CASCADE) recipient: Recipient = models.ForeignKey(Recipient, on_delete=CASCADE)
subject: str = models.CharField(max_length=MAX_TOPIC_NAME_LENGTH) subject: str = models.CharField(max_length=MAX_TOPIC_NAME_LENGTH)
@ -2739,6 +2778,7 @@ class RealmAuditLog(AbstractRealmAuditLog):
acting_user is that administrator and both modified_user and acting_user is that administrator and both modified_user and
modified_stream will be None. modified_stream will be None.
""" """
id: int = models.AutoField(auto_created=True, primary_key=True, verbose_name='ID')
realm: Realm = models.ForeignKey(Realm, on_delete=CASCADE) realm: Realm = models.ForeignKey(Realm, on_delete=CASCADE)
acting_user: Optional[UserProfile] = models.ForeignKey( acting_user: Optional[UserProfile] = models.ForeignKey(
UserProfile, null=True, related_name="+", on_delete=CASCADE, UserProfile, null=True, related_name="+", on_delete=CASCADE,
@ -2759,6 +2799,7 @@ class RealmAuditLog(AbstractRealmAuditLog):
return f"<RealmAuditLog: {self.realm} {self.event_type} {self.event_time} {self.id}>" return f"<RealmAuditLog: {self.realm} {self.event_type} {self.event_time} {self.id}>"
class UserHotspot(models.Model): class UserHotspot(models.Model):
id: int = models.AutoField(auto_created=True, primary_key=True, verbose_name='ID')
user: UserProfile = models.ForeignKey(UserProfile, on_delete=CASCADE) user: UserProfile = models.ForeignKey(UserProfile, on_delete=CASCADE)
hotspot: str = models.CharField(max_length=30) hotspot: str = models.CharField(max_length=30)
timestamp: datetime.datetime = models.DateTimeField(default=timezone_now) timestamp: datetime.datetime = models.DateTimeField(default=timezone_now)
@ -2798,6 +2839,7 @@ class CustomProfileField(models.Model):
HINT_MAX_LENGTH = 80 HINT_MAX_LENGTH = 80
NAME_MAX_LENGTH = 40 NAME_MAX_LENGTH = 40
id: int = models.AutoField(auto_created=True, primary_key=True, verbose_name='ID')
realm: Realm = models.ForeignKey(Realm, on_delete=CASCADE) realm: Realm = models.ForeignKey(Realm, on_delete=CASCADE)
name: str = models.CharField(max_length=NAME_MAX_LENGTH) name: str = models.CharField(max_length=NAME_MAX_LENGTH)
hint: Optional[str] = models.CharField(max_length=HINT_MAX_LENGTH, default='', null=True) hint: Optional[str] = models.CharField(max_length=HINT_MAX_LENGTH, default='', null=True)
@ -2886,6 +2928,7 @@ def custom_profile_fields_for_realm(realm_id: int) -> List[CustomProfileField]:
return CustomProfileField.objects.filter(realm=realm_id).order_by('order') return CustomProfileField.objects.filter(realm=realm_id).order_by('order')
class CustomProfileFieldValue(models.Model): class CustomProfileFieldValue(models.Model):
id: int = models.AutoField(auto_created=True, primary_key=True, verbose_name='ID')
user_profile: UserProfile = models.ForeignKey(UserProfile, on_delete=CASCADE) user_profile: UserProfile = models.ForeignKey(UserProfile, on_delete=CASCADE)
field: CustomProfileField = models.ForeignKey(CustomProfileField, on_delete=CASCADE) field: CustomProfileField = models.ForeignKey(CustomProfileField, on_delete=CASCADE)
value: str = models.TextField() value: str = models.TextField()
@ -2918,6 +2961,7 @@ SLACK_INTERFACE = 'SlackOutgoingWebhookService'
# embedded bots with the same name will run the same code # embedded bots with the same name will run the same code
# - base_url and token are currently unused # - base_url and token are currently unused
class Service(models.Model): class Service(models.Model):
id: int = models.AutoField(auto_created=True, primary_key=True, verbose_name='ID')
name: str = models.CharField(max_length=UserProfile.MAX_NAME_LENGTH) name: str = models.CharField(max_length=UserProfile.MAX_NAME_LENGTH)
# Bot user corresponding to the Service. The bot_type of this user # Bot user corresponding to the Service. The bot_type of this user
# deterines the type of service. If non-bot services are added later, # deterines the type of service. If non-bot services are added later,
@ -2955,6 +2999,7 @@ def get_service_profile(user_profile_id: int, service_name: str) -> Service:
class BotStorageData(models.Model): class BotStorageData(models.Model):
id: int = models.AutoField(auto_created=True, primary_key=True, verbose_name='ID')
bot_profile: UserProfile = models.ForeignKey(UserProfile, on_delete=CASCADE) bot_profile: UserProfile = models.ForeignKey(UserProfile, on_delete=CASCADE)
key: str = models.TextField(db_index=True) key: str = models.TextField(db_index=True)
value: str = models.TextField() value: str = models.TextField()
@ -2963,6 +3008,7 @@ class BotStorageData(models.Model):
unique_together = ("bot_profile", "key") unique_together = ("bot_profile", "key")
class BotConfigData(models.Model): class BotConfigData(models.Model):
id: int = models.AutoField(auto_created=True, primary_key=True, verbose_name='ID')
bot_profile: UserProfile = models.ForeignKey(UserProfile, on_delete=CASCADE) bot_profile: UserProfile = models.ForeignKey(UserProfile, on_delete=CASCADE)
key: str = models.TextField(db_index=True) key: str = models.TextField(db_index=True)
value: str = models.TextField() value: str = models.TextField()
@ -2987,6 +3033,7 @@ class AlertWord(models.Model):
# never move to another realm, so it's static, and having Realm # never move to another realm, so it's static, and having Realm
# here optimizes the main query on this table, which is fetching # here optimizes the main query on this table, which is fetching
# all the alert words in a realm. # all the alert words in a realm.
id: int = models.AutoField(auto_created=True, primary_key=True, verbose_name='ID')
realm: Realm = models.ForeignKey(Realm, db_index=True, on_delete=CASCADE) realm: Realm = models.ForeignKey(Realm, db_index=True, on_delete=CASCADE)
user_profile: UserProfile = models.ForeignKey(UserProfile, on_delete=CASCADE) user_profile: UserProfile = models.ForeignKey(UserProfile, on_delete=CASCADE)
# Case-insensitive name for the alert word. # Case-insensitive name for the alert word.

View File

@ -890,7 +890,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
self.login('hamlet') self.login('hamlet')
othello = self.example_user('othello') othello = self.example_user('othello')
bot_info = { bot_info: Dict[str, object] = {
'full_name': 'The Bot of Hamlet', 'full_name': 'The Bot of Hamlet',
'short_name': 'hambot', 'short_name': 'hambot',
} }
@ -983,7 +983,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
self.create_bot() self.create_bot()
self.assert_num_bots_equal(1) self.assert_num_bots_equal(1)
bot_info = { bot_info: Dict[str, object] = {
'full_name': 'Another Bot of Hamlet', 'full_name': 'Another Bot of Hamlet',
'short_name': 'hamelbot', 'short_name': 'hamelbot',
} }

View File

@ -596,7 +596,7 @@ class UpdateCustomProfileFieldTest(CustomProfileFieldTestCase):
self.assertIsNone(value) self.assertIsNone(value)
self.assertIsNone(rendered_value) self.assertIsNone(rendered_value)
update_dict = { update_dict: Dict[str, Union[int, str, List[int]]] = {
"id": quote.id, "id": quote.id,
"value": "***beware*** of jealousy...", "value": "***beware*** of jealousy...",
} }

View File

@ -2674,7 +2674,7 @@ class GetOldMessagesTest(ZulipTestCase):
# If nothing relevant is muted, then exclude_muting_conditions() # If nothing relevant is muted, then exclude_muting_conditions()
# should return an empty list. # should return an empty list.
narrow = [ narrow: List[Dict[str, object]] = [
dict(operator='stream', operand='Scotland'), dict(operator='stream', operand='Scotland'),
] ]
muting_conditions = exclude_muting_conditions(user_profile, narrow) muting_conditions = exclude_muting_conditions(user_profile, narrow)

View File

@ -1150,7 +1150,7 @@ class StreamAdminTest(ZulipTestCase):
self.make_stream(stream_name, invite_only=invite_only) self.make_stream(stream_name, invite_only=invite_only)
# Set up the principal to be unsubscribed. # Set up the principal to be unsubscribed.
principals = [] principals: List[Union[str, int]] = []
for user in target_users: for user in target_users:
if using_legacy_emails: if using_legacy_emails:
principals.append(user.email) principals.append(user.email)