python - 派生查询上的 Django 内部联接
问题描述
我有两个模型如下所示。我希望能够通过 django ORM 执行这个查询,基本上给了我 CustomUser 类以及两个派生字段:max(message.sent_at) 和 max(case when read_at is null then 1 else 0 end)。这两个字段使我能够按用户名和最新活动对消息线程进行排序。
这是我的课程:
class CustomUser(AbstractBaseUser, PermissionsMixin):
username_validator = UnicodeUsernameValidator()
username = models.CharField(_('username'), max_length=150, unique=True, help_text=_('Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.'), validators=[username_validator], error_messages={'unique': _('A user with that username already exists.'),},)
email = models.EmailField(_('email address'), blank=True)
first_name = models.CharField(_('first name'), max_length=30, blank=True)
last_name = models.CharField(_('last name'), max_length=150, blank=True)
is_staff = models.BooleanField(_('staff status'), default=False, help_text=_('Designates whether the user can log into this admin site.'),)
is_active = models.BooleanField(_('active'), default=True, help_text=_('Designates whether this user should be treated as active. Unselect this instead of deleting accounts.'),)
date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
bio = models.TextField(max_length=500, null=True, blank=True)
location = models.CharField(max_length=30, null=True, blank=True)
birth_date = models.DateField(null=True, blank=True)
phone_number = PhoneNumberField(default='+10000000000')
gender = models.CharField(max_length=32, choices=[(tag.name, tag.value) for tag in GenderChoice], default=GenderChoice.UNSPECIFIED.value)
objects = UserManager()
EMAIL_FIELD = 'email'
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ['email']
class Meta:
ordering = ['username']
verbose_name = _('user')
verbose_name_plural = _('users')
和
class Message(AbstractIP):
subject = models.CharField(_('Subject'), max_length=120, blank=True)
body = models.TextField(_('Body')) # Do we want to cap length or enforce non-blank?
sender = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='sender_messages', verbose_name=_('Sender'), on_delete=models.CASCADE)
recipient = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='receiver_messages', verbose_name=_('Recipient'), blank=True, on_delete=models.CASCADE)
parent_msg = models.ForeignKey('self', related_name='next_messages', null=True, blank=True, verbose_name=_('Parent message'), on_delete=models.CASCADE)
sent_at = models.DateTimeField(_('sent at'), null=True, blank=True)
read_at = models.DateTimeField(_('read at'), null=True, blank=True)
replied_at = models.DateTimeField(_('replied at'), null=True, blank=True)
sender_deleted_at = models.DateTimeField(_('Sender deleted at'), null=True, blank=True)
recipient_deleted_at = models.DateTimeField(_('Recipient deleted at'), null=True, blank=True)
ip = models.GenericIPAddressField(verbose_name=_('IP'), null=True, blank=True)
user_agent = models.CharField(verbose_name=_('User Agent'), blank=True, max_length=255)
objects = MessageManager() # Manager for Message queries
def new(self):
"""Returns whether the recipient has read the message or not"""
if self.read_at is not None:
return False
return True
def replied(self):
"""Returns whether the recipient has written a reply to this message"""
if self.replied_at is not None:
return True
return False
def __str__(self):
if self.subject is not None:
return self.subject
if self.body is not None:
return self.body[:40]
return None
def get_absolute_url(self):
return reverse('messages_detail', args=[self.id])
def save(self, **kwargs):
if not self.id:
self.sent_at = timezone.now()
super(Message, self).save(**kwargs)
class Meta:
ordering = ['-sent_at']
verbose_name = _('Message')
verbose_name_plural = _('Messages')
我希望能够执行的查询等同于此,但我无法弄清楚如何在 ORM 中执行此操作,其中 %s 是给定用户的 CustomUser.id (pk) 字段的占位符。
SELECT webrtc_customuser.*
,MAX(webrtc_message.sent_at) AS sent_at
,MAX(CASE WHEN webrtc_message.read_at IS NULL AND webrtc_customuser.id <> webrtc_message.sender_id THEN 1 ELSE 0 END) AS has_unread
FROM webrtc_customuser
INNER JOIN webrtc_message
ON (
webrtc_customuser.id = webrtc_message.sender_id
AND webrtc_message.sender_id = %s
AND webrtc_message.sender_deleted_at IS NULL
) OR (
webrtc_customuser.id = webrtc_message.recipient_id
AND webrtc_message.recipient_id = %s
AND webrtc_message.recipient_deleted_at IS NULL
)
我设法通过以下查询获得了正确的 user_id 和派生字段,但无法弄清楚如何让 CustomUser 属性与它们一起加入。
messages = self.values(
user_fk=Case(When(sender=user, then='recipient'), default='sender', output_field=models.IntegerField())
).exclude(
sender=user, recipient=user
).filter(
Q(sender=user, sender_deleted_at__isnull=True) |
Q(recipient=user, recipient_deleted_at__isnull=True)
).annotate(
max_sent_at=Max('sent_at'),
has_unread=Max(Case(When(~Q(sender=user) & Q(read_at__isnull=True), then=1), default=0, output_field=models.IntegerField()))
).order_by()
提前感谢您的宝贵时间!
编辑:更新的 ORM 查询
解决方案
您需要单独指定所需的用户属性:
messages = self.values(
user_email=Case(When(sender=user, then='recipient__email'), default='sender__email'),
user_username=Case(When(sender=user, then='recipient__username'), default='sender__username'),
)
不是很漂亮,特别是因为您必须为每一列重复该CASE
语句,甚至可能需要为每一列指定output_field
一个。
为了解决这个问题,即。要获取所有用户属性而不一一选择它们,您需要 a) 选择CustomUser.object
(弄清楚如何选择相关用户并获取相关注释),或 b) 选择完整的消息对象而不仅仅是values()
字典. 然后您可以通过message.sender
和访问完整的用户对象message.recipient
。但在这里,挑战将是如何messages
使用子查询过滤和注释查询集,因为只是省略values()
会破坏注释中的聚合,因为每个消息对象都是唯一的。
推荐阅读
- docker - 允许 Docker 容器仅与主机通信
- javascript - 如何使用 JS 在 Mac 上模拟 Command + shift + esc?
- javascript - React TypeError:无法读取未定义的属性“文本”
- azure-devops - 在 azure-devops 中的发布管道期间创建标签
- google-cloud-platform - 将 GCP 项目迁移到另一个电子邮件/帐户
- algorithm - 给定一个数学链接的二维数组 (NxM),可以创建 log(N)log(M) 搜索算法吗
- javascript - HTML 表单验证 - 检查是否至少选择了一个下拉菜单
- sorting - Elasticsearch 7 - 对多字段属性的自定义字段进行排序
- python - 在类之外定义的函数的范围是什么?
- python - 如何在while循环中记录数据值?