В Django, как найти строки, которые нарушали бы уникальное ограничение вместе?

Для следующей конечной точки 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 ошибки. Чтобы это исправить, я бы хотел навязать unique_together ограничение на user а также player_id, Во-первых, однако, я должен написать миграцию данных, которая исключает строки, которые нарушают это уникальное ограничение вместе.

Как я могу написать запрос, который выбирает эти? Прямо сейчас было предложено следующее:

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

Кажется, однако, что это также может быть сделано в одном запросе.

Обновить

Я обнаружил, что можно пройти два поля, user а также player_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_id а также player_id, но оригинал id потерян, поэтому я не могу впоследствии get() дубликаты объектов и их удаление. Как я могу сделать что-то подобное, но сохранить ссылки на дубликаты объектов?

1 ответ

Решение

Мне удалось сгруппировать дубликаты UserApn s в следующий набор запросов:

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

Обратите внимание, что передача нескольких аргументов distinct() работает только на PostgreSQL.

Другие вопросы по тегам