diff --git a/zerver/lib/export.py b/zerver/lib/export.py index f237abdc53..8c0aa671b0 100644 --- a/zerver/lib/export.py +++ b/zerver/lib/export.py @@ -124,6 +124,7 @@ ALL_ZULIP_TABLES = { 'zerver_defaultstream', 'zerver_defaultstreamgroup', 'zerver_defaultstreamgroup_streams', + 'zerver_draft', 'zerver_emailchangestatus', 'zerver_huddle', 'zerver_message', @@ -237,6 +238,9 @@ NON_EXPORTED_TABLES = { # This is low priority, since users can easily just reset themselves to away. 'zerver_userstatus', + # Drafts don't need to be exported as they are supposed to be more ephemeral. + 'zerver_draft', + # For any tables listed below here, it's a bug that they are not present in the export. } diff --git a/zerver/migrations/0297_draft.py b/zerver/migrations/0297_draft.py new file mode 100644 index 0000000000..710571a0be --- /dev/null +++ b/zerver/migrations/0297_draft.py @@ -0,0 +1,26 @@ +# Generated by Django 2.2.14 on 2020-07-23 17:07 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('zerver', '0296_remove_userprofile_short_name'), + ] + + operations = [ + migrations.CreateModel( + name='Draft', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('topic', models.CharField(db_index=True, max_length=60)), + ('content', models.TextField()), + ('last_edit_time', models.DateTimeField(db_index=True)), + ('recipient', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='zerver.Recipient')), + ('user_profile', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/zerver/models.py b/zerver/models.py index 6d4c50a292..3bf4eb9a6e 100644 --- a/zerver/models.py +++ b/zerver/models.py @@ -66,7 +66,7 @@ from zerver.lib.cache import ( ) from zerver.lib.exceptions import JsonableError from zerver.lib.pysa import mark_sanitized -from zerver.lib.timestamp import datetime_to_timestamp +from zerver.lib.timestamp import datetime_to_precise_timestamp, datetime_to_timestamp from zerver.lib.types import ( DisplayRecipientT, ExtendedFieldElement, @@ -1899,6 +1899,44 @@ class ArchivedSubMessage(AbstractSubMessage): post_save.connect(flush_submessage, sender=SubMessage) +class Draft(models.Model): + """ Server-side storage model for storing drafts so that drafts can be synced across + multiple clients/devices. + """ + user_profile: UserProfile = models.ForeignKey(UserProfile, on_delete=models.CASCADE) + recipient: Optional[Recipient] = models.ForeignKey(Recipient, null=True, on_delete=models.SET_NULL) + topic: str = models.CharField(max_length=MAX_TOPIC_NAME_LENGTH, db_index=True) + content: str = models.TextField() # Length should not exceed MAX_MESSAGE_LENGTH + last_edit_time: datetime.datetime = models.DateTimeField(db_index=True) + + def __str__(self) -> str: + return f"<{self.__class__.__name__}: {self.user_profile.email} / {self.id} / {self.last_edit_time}>" + + def to_dict(self) -> Dict[str, Any]: # nocoverage # Will be added in a later commit. + if self.recipient is None: + _type = "" + to = [] + elif self.recipient.type == Recipient.STREAM: + _type = "stream" + to = [self.recipient.type_id] + else: + _type = "private" + if self.recipient.type == Recipient.PERSONAL: + to = [self.recipient.type_id] + else: + to = [] + for r in get_display_recipient(self.recipient): + assert(not isinstance(r, str)) # It will only be a string for streams + if not r["id"] == self.user_profile_id: + to.append(r["id"]) + return { + "type": _type, + "to": to, + "topic": self.topic, + "content": self.content, + "timestamp": datetime_to_precise_timestamp(self.last_edit_time), + } + class AbstractReaction(models.Model): """For emoji reactions to messages (and potentially future reaction types).