【问题标题】:In Django, how to find rows which would violate a unique together constraint?在 Django 中,如何找到违反唯一一起约束的行?
【发布时间】:2018-12-31 10:29:33
【问题描述】:

对于以下 API 端点,

import json

from django.contrib.auth.decorators import login_required
from django.http import JsonResponse, HttpResponseBadRequest
from django.views.decorators.http import require_POST

from lucy_web.models import UserApn


@login_required
@require_POST
def save_apn(request, version):
    player_id = json.loads(request.body).get('player_id')
    if player_id:
        UserApn.objects.get_or_create(user=request.user, player_id=player_id)
        return JsonResponse({'status': 'success'})
    else:
        return HttpResponseBadRequest()

这是底层模型:

from django.contrib.auth.models import User
from django.db import models
from .timestamped_model import TimeStampedModel


class UserApn(TimeStampedModel):
    user = models.ForeignKey(User)
    player_id = models.CharField(max_length=255)

get_or_create() 的调用引发了一些MultipleObjectsReturned 错误。为了解决这个问题,我想对userplayer_id 施加unique_together 约束。但是,首先,我必须编写一个数据迁移,以消除违反此唯一共同约束的行。

我如何编写一个选择这些的查询?目前已提出以下建议:

def remove_duplicate_apns(apps, schema_editor):
    UserApn = apps.get_model('lucy_web', 'UserApn')

    previous_user_id = None
    previous_player_id = None
    for apn in UserApn.objects.all().order_by('user_id', 'player_id'):
        if apn.user_id == previous_user_id and apn.player_id == previous_player_id:
            print(f'deleting {apn} (id: {apn.id})')
            apn.delete()
        else:
            previous_user_id = apn.user_id
            previous_player_id = apn.player_id

不过,这似乎也可以在单个查询中完成。

更新

我发现可以将userplayer_id 这两个字段传递给.values(),然后使用.distinct() 检查重复项。例如,以下测试通过:

from django.test import TestCase

from django.contrib.auth.models import User
from myapp.models import UserApn


class UserApnTest(TestCase):
    def test_1(self):
        user = User.objects.create_user(username='jayz')
        apn1 = UserApn.objects.create(user=user, player_id='foo')
        apn2 = UserApn.objects.create(user=user, player_id='foo')
        apn3 = UserApn.objects.create(user=user, player_id='bar')

        self.assertEqual(
            len(UserApn.objects.values('user', 'player_id')) -
            len(UserApn.objects.values('user', 'player_id').distinct()), 1)

但是,问题仍然存在,此输出是带有user_idplayer_id 的字典,但原始id 丢失了,因此我不能随后get() 重复对象并删除它们。我怎样才能做类似的事情但保留对重复对象的引用?

【问题讨论】:

    标签: python django database migration unique-constraint


    【解决方案1】:

    我设法将重复的UserApns 分组到以下查询集中:

    UserApn.objects.all().difference(UserApn.objects.distinct('user', 'player_id'))
    

    请注意,将多个参数传递给 distinct() 仅适用于 PostgreSQL。

    【讨论】:

    • “仅适用于 PostgreSQL”自从我开始使用它以来,它一直是一个非常令人满意(并且经常出现)的短语。
    猜你喜欢
    • 2012-05-03
    • 2020-04-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-12
    • 2016-11-27
    • 2018-03-14
    • 2012-12-31
    相关资源
    最近更新 更多