Features: 1) Introduced ChatThread and ChatMessage models for messaging functionality; 2) Added ThreadStatus and SenderType choices for structured messaging states; 3) Integrated Django migrations with indexing for new models;
Fixes: 1) Corrected `admin.py` imports for consistency and model alignment; Extra: Refactored `choices.py` for reusable enums; restructured `models.py` for clarity and maintainability.
This commit is contained in:
parent
52a62f0f6f
commit
6109643acb
5 changed files with 250 additions and 77 deletions
|
|
@ -29,8 +29,15 @@ from engine.core.admin import ActivationActionsMixin
|
|||
from engine.core.models import Order
|
||||
from engine.payments.models import Balance
|
||||
from engine.vibes_auth.forms import UserForm
|
||||
from engine.vibes_auth.messaging.models import ChatMessage, ChatThread, ThreadStatus
|
||||
from engine.vibes_auth.models import BlacklistedToken, Group, OutstandingToken, User
|
||||
from engine.vibes_auth.models import (
|
||||
BlacklistedToken,
|
||||
Group,
|
||||
OutstandingToken,
|
||||
User,
|
||||
ChatMessage,
|
||||
ChatThread,
|
||||
ThreadStatus,
|
||||
)
|
||||
|
||||
|
||||
class BalanceInline(admin.TabularInline): # type: ignore [type-arg]
|
||||
|
|
|
|||
13
engine/vibes_auth/choices.py
Normal file
13
engine/vibes_auth/choices.py
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
from django.db.models import TextChoices
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
class ThreadStatus(TextChoices):
|
||||
OPEN = "open", _("Open")
|
||||
CLOSED = "closed", _("Closed")
|
||||
|
||||
|
||||
class SenderType(TextChoices):
|
||||
USER = "user", _("User")
|
||||
STAFF = "staff", _("Staff")
|
||||
SYSTEM = "system", _("System")
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db.models import (
|
||||
CharField,
|
||||
DateTimeField,
|
||||
EmailField,
|
||||
ForeignKey,
|
||||
Index,
|
||||
JSONField,
|
||||
TextChoices,
|
||||
TextField,
|
||||
SET_NULL,
|
||||
CASCADE,
|
||||
)
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from engine.core.abstract import NiceModel
|
||||
from engine.vibes_auth.models import User
|
||||
|
||||
|
||||
class ThreadStatus(TextChoices):
|
||||
OPEN = "open", _("Open")
|
||||
CLOSED = "closed", _("Closed")
|
||||
|
||||
|
||||
class SenderType(TextChoices):
|
||||
USER = "user", _("User")
|
||||
STAFF = "staff", _("Staff")
|
||||
SYSTEM = "system", _("System")
|
||||
|
||||
|
||||
class ChatThread(NiceModel):
|
||||
user = ForeignKey(User, null=True, blank=True, on_delete=SET_NULL, related_name="chat_threads")
|
||||
email = EmailField(blank=True, default="", help_text=_("For anonymous threads"))
|
||||
assigned_to = ForeignKey(User, null=True, blank=True, on_delete=SET_NULL, related_name="assigned_chat_threads")
|
||||
status = CharField(max_length=16, choices=ThreadStatus.choices, default=ThreadStatus.OPEN)
|
||||
last_message_at = DateTimeField(default=timezone.now)
|
||||
attributes = JSONField(default=dict, blank=True)
|
||||
|
||||
class Meta:
|
||||
indexes = [
|
||||
Index(fields=["status", "modified"], name="chatthread_status_mod_idx"),
|
||||
Index(fields=["assigned_to", "status"], name="chatthread_assigned_status_idx"),
|
||||
Index(fields=["user"], name="chatthread_user_idx"),
|
||||
Index(fields=["email"], name="chatthread_email_idx"),
|
||||
]
|
||||
ordering = ("-modified",)
|
||||
verbose_name = _("Chat thread")
|
||||
verbose_name_plural = _("Chat threads")
|
||||
|
||||
def clean(self) -> None:
|
||||
super().clean()
|
||||
if not self.user and not self.email:
|
||||
raise ValidationError({"email": _("Provide user or email for anonymous thread.")})
|
||||
if self.assigned_to and not self.assigned_to.is_staff:
|
||||
raise ValidationError({"assigned_to": _("Assignee must be a staff user.")})
|
||||
|
||||
|
||||
class ChatMessage(NiceModel):
|
||||
thread = ForeignKey(ChatThread, on_delete=CASCADE, related_name="messages")
|
||||
sender_type = CharField(max_length=16, choices=SenderType.choices)
|
||||
sender_user = ForeignKey(User, null=True, blank=True, on_delete=SET_NULL, related_name="chat_messages")
|
||||
text = TextField()
|
||||
sent_at = DateTimeField(default=timezone.now)
|
||||
attributes = JSONField(default=dict, blank=True)
|
||||
|
||||
class Meta:
|
||||
indexes = [
|
||||
Index(fields=["thread", "sent_at"], name="chatmessage_thread_sent_idx"),
|
||||
]
|
||||
ordering = ("sent_at", "uuid")
|
||||
verbose_name = _("Chat message")
|
||||
verbose_name_plural = _("Chat messages")
|
||||
|
|
@ -0,0 +1,175 @@
|
|||
# Generated by Django 5.2.8 on 2025-11-11 15:28
|
||||
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
import django_extensions.db.fields
|
||||
import uuid
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("vibes_auth", "0005_alter_user_groups"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="ChatThread",
|
||||
fields=[
|
||||
(
|
||||
"uuid",
|
||||
models.UUIDField(
|
||||
default=uuid.uuid4,
|
||||
editable=False,
|
||||
help_text="unique id is used to surely identify any database object",
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="unique id",
|
||||
),
|
||||
),
|
||||
(
|
||||
"is_active",
|
||||
models.BooleanField(
|
||||
default=True,
|
||||
help_text="if set to false, this object can't be seen by users without needed permission",
|
||||
verbose_name="is active",
|
||||
),
|
||||
),
|
||||
(
|
||||
"created",
|
||||
django_extensions.db.fields.CreationDateTimeField(
|
||||
auto_now_add=True,
|
||||
help_text="when the object first appeared on the database",
|
||||
verbose_name="created",
|
||||
),
|
||||
),
|
||||
(
|
||||
"modified",
|
||||
django_extensions.db.fields.ModificationDateTimeField(
|
||||
auto_now=True, help_text="when the object was last modified", verbose_name="modified"
|
||||
),
|
||||
),
|
||||
("email", models.EmailField(blank=True, default="", help_text="For anonymous threads", max_length=254)),
|
||||
(
|
||||
"status",
|
||||
models.CharField(choices=[("open", "Open"), ("closed", "Closed")], default="open", max_length=16),
|
||||
),
|
||||
("last_message_at", models.DateTimeField(default=django.utils.timezone.now)),
|
||||
("attributes", models.JSONField(blank=True, default=dict)),
|
||||
(
|
||||
"assigned_to",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="assigned_chat_threads",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
(
|
||||
"user",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="chat_threads",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "Chat thread",
|
||||
"verbose_name_plural": "Chat threads",
|
||||
"ordering": ("-modified",),
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="ChatMessage",
|
||||
fields=[
|
||||
(
|
||||
"uuid",
|
||||
models.UUIDField(
|
||||
default=uuid.uuid4,
|
||||
editable=False,
|
||||
help_text="unique id is used to surely identify any database object",
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="unique id",
|
||||
),
|
||||
),
|
||||
(
|
||||
"is_active",
|
||||
models.BooleanField(
|
||||
default=True,
|
||||
help_text="if set to false, this object can't be seen by users without needed permission",
|
||||
verbose_name="is active",
|
||||
),
|
||||
),
|
||||
(
|
||||
"created",
|
||||
django_extensions.db.fields.CreationDateTimeField(
|
||||
auto_now_add=True,
|
||||
help_text="when the object first appeared on the database",
|
||||
verbose_name="created",
|
||||
),
|
||||
),
|
||||
(
|
||||
"modified",
|
||||
django_extensions.db.fields.ModificationDateTimeField(
|
||||
auto_now=True, help_text="when the object was last modified", verbose_name="modified"
|
||||
),
|
||||
),
|
||||
(
|
||||
"sender_type",
|
||||
models.CharField(
|
||||
choices=[("user", "User"), ("staff", "Staff"), ("system", "System")], max_length=16
|
||||
),
|
||||
),
|
||||
("text", models.TextField()),
|
||||
("sent_at", models.DateTimeField(default=django.utils.timezone.now)),
|
||||
("attributes", models.JSONField(blank=True, default=dict)),
|
||||
(
|
||||
"sender_user",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="chat_messages",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
(
|
||||
"thread",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE, related_name="messages", to="vibes_auth.chatthread"
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "Chat message",
|
||||
"verbose_name_plural": "Chat messages",
|
||||
"ordering": ("sent_at", "uuid"),
|
||||
},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="chatthread",
|
||||
index=models.Index(fields=["status", "modified"], name="chatthread_status_mod_idx"),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="chatthread",
|
||||
index=models.Index(fields=["assigned_to", "status"], name="chatthread_assigned_status_idx"),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="chatthread",
|
||||
index=models.Index(fields=["user"], name="chatthread_user_idx"),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="chatthread",
|
||||
index=models.Index(fields=["email"], name="chatthread_email_idx"),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="chatmessage",
|
||||
index=models.Index(fields=["thread", "sent_at"], name="chatmessage_thread_sent_idx"),
|
||||
),
|
||||
]
|
||||
|
|
@ -4,6 +4,7 @@ from django.conf import settings
|
|||
from django.contrib.auth.models import AbstractUser
|
||||
from django.contrib.auth.models import Group as BaseGroup
|
||||
from django.core.cache import cache
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db.models import (
|
||||
BooleanField,
|
||||
CharField,
|
||||
|
|
@ -11,7 +12,14 @@ from django.db.models import (
|
|||
ImageField,
|
||||
JSONField,
|
||||
UUIDField,
|
||||
ForeignKey,
|
||||
TextField,
|
||||
DateTimeField,
|
||||
Index,
|
||||
CASCADE,
|
||||
SET_NULL,
|
||||
)
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from rest_framework_simplejwt.token_blacklist.models import (
|
||||
BlacklistedToken as BaseBlacklistedToken,
|
||||
|
|
@ -21,6 +29,7 @@ from rest_framework_simplejwt.token_blacklist.models import (
|
|||
)
|
||||
|
||||
from engine.core.abstract import NiceModel
|
||||
from engine.vibes_auth.choices import SenderType, ThreadStatus
|
||||
from engine.vibes_auth.managers import UserManager
|
||||
from engine.vibes_auth.validators import validate_phone_number
|
||||
from engine.payments.models import Balance
|
||||
|
|
@ -116,6 +125,50 @@ class User(AbstractUser, NiceModel): # type: ignore [django-manager-missing]
|
|||
verbose_name_plural = _("users")
|
||||
|
||||
|
||||
class ChatThread(NiceModel):
|
||||
user = ForeignKey(User, null=True, blank=True, on_delete=SET_NULL, related_name="chat_threads")
|
||||
email = EmailField(blank=True, default="", help_text=_("For anonymous threads"))
|
||||
assigned_to = ForeignKey(User, null=True, blank=True, on_delete=SET_NULL, related_name="assigned_chat_threads")
|
||||
status = CharField(max_length=16, choices=ThreadStatus.choices, default=ThreadStatus.OPEN)
|
||||
last_message_at = DateTimeField(default=timezone.now)
|
||||
attributes = JSONField(default=dict, blank=True)
|
||||
|
||||
class Meta:
|
||||
indexes = [
|
||||
Index(fields=["status", "modified"], name="chatthread_status_mod_idx"),
|
||||
Index(fields=["assigned_to", "status"], name="chatthread_assigned_status_idx"),
|
||||
Index(fields=["user"], name="chatthread_user_idx"),
|
||||
Index(fields=["email"], name="chatthread_email_idx"),
|
||||
]
|
||||
ordering = ("-modified",)
|
||||
verbose_name = _("Chat thread")
|
||||
verbose_name_plural = _("Chat threads")
|
||||
|
||||
def clean(self) -> None:
|
||||
super().clean()
|
||||
if not self.user and not self.email:
|
||||
raise ValidationError({"email": _("provide user or email for anonymous thread.")})
|
||||
if self.assigned_to and not self.assigned_to.is_staff:
|
||||
raise ValidationError({"assigned_to": _("assignee must be a staff user.")})
|
||||
|
||||
|
||||
class ChatMessage(NiceModel):
|
||||
thread = ForeignKey(ChatThread, on_delete=CASCADE, related_name="messages")
|
||||
sender_type = CharField(max_length=16, choices=SenderType.choices)
|
||||
sender_user = ForeignKey(User, null=True, blank=True, on_delete=SET_NULL, related_name="chat_messages")
|
||||
text = TextField()
|
||||
sent_at = DateTimeField(default=timezone.now)
|
||||
attributes = JSONField(default=dict, blank=True)
|
||||
|
||||
class Meta:
|
||||
indexes = [
|
||||
Index(fields=["thread", "sent_at"], name="chatmessage_thread_sent_idx"),
|
||||
]
|
||||
ordering = ("sent_at", "uuid")
|
||||
verbose_name = _("Chat message")
|
||||
verbose_name_plural = _("Chat messages")
|
||||
|
||||
|
||||
class Group(BaseGroup):
|
||||
class Meta:
|
||||
proxy = True
|
||||
|
|
|
|||
Loading…
Reference in a new issue