2016-05-05 1 views
1

Есть ли простой способ запроса фильтрации обратного отношения по значению, которое является значением по умолчанию, если объект еще не существует?Связанные объекты или по умолчанию

Позвольте мне подробнее остановиться на примере.

У меня есть следующие основные модели модели:

class Delegation(models.Model): 
    name = models.CharField(unique=True,max_length=4) 
    country = models.CharField(unique=True,max_length=100) 
    members = models.ManyToManyField(User, blank=True) 

class Exam(models.Model): 
    name = models.CharField(max_length=100, unique=True) 
    active = models.BooleanField(default=True) 

Теперь мне нужно хранить информацию о каких-то действий, в основном, если действие еще продолжается или завершена (представлен). По этой причине я использую модель, которая ссылается на обе модели.

class ExamAction(models.Model): 
    OPEN = 'O' 
    SUBMITTED = 'S' 
    STATUS_CHOICES = ((OPEN, 'In progress'), (SUBMITTED, 'Submitted')) 
    TRANSLATION = 'T' 
    POINTS = 'P' 
    ACTION_CHOICES = ((TRANSLATION, 'Translation submission'), (POINTS, 'Points submission')) 
    exam  = models.ForeignKey(Exam) 
    delegation = models.ForeignKey(Delegation) 
    action  = models.CharField(max_length=2, choices=ACTION_CHOICES) 
    status  = models.CharField(max_length=1, choices=STATUS_CHOICES, default=OPEN) 
    timestamp = models.DateTimeField(auto_now=True) 
    class Meta: 
     unique_together = (('exam', 'delegation', 'action'),) 

Моя проблема заключается в том, что запросы становятся в настоящее время довольно сложный, потому что (на данный момент) я предполагаю, что действие еще не могли быть перечислены в БД, для которой я принял бы значение по умолчанию ExamAction.OPEN.

Для конкретного примера, это то, что я использую в данный момент запросить список экзаменов, которые все еще открыты для делегации:

exams_open = Exam.objects.filter((Q(examaction__delegation=delegation) & Q(examaction__action=ExamAction.TRANSLATION) & Q(examaction__status=ExamAction.OPEN)) 
    | Q(examaction__isnull=True), active=True,) 

Я считаю, что выше запрос тоже неправильно, потому что он должен вернуть объект Exam, если нет ExamAction с action=TRANSLATION и delegation=delegation, но это не будет, если другое действие уже сохранено (с любым значением).

Я уверен, что я не первый, столкнувшийся с этой проблемой дизайна, и я думаю, что должна существовать гораздо более простая реализация. Но что?

То, что я в настоящее время рассматриваю это:

  1. ExamAction.objects.get_or_create()
    • Задача 1: это не работает на нескольких матчах, то есть запрос прибудет должен быть уникальным
    • Задача 2: это не нравится создавать объекты в запросе GET.
  2. Сгенерировать все возможные ExamAction при создании нового Exam объекта. Это означало бы зацикливание, хотя все делегации.
    • Что делать, если делегация добавляется после сдачи экзамена?
  3. Есть ли отношения ManyToMany с through=ExamAction help в этой ситуации? Я так не думаю, потому что я все равно должен запрашивать у ExamAction желаемое (по умолчанию) значение или еще не существующее.
  4. Полная редизайн? Я открыт для возможных идей!

Большое спасибо.

ответ

0

Если я правильно понял ваш нужный запрос правильно, я бы поставил его, как это (здесь в качестве пользовательского метода на Manager):

# UPDATE: Bugfix thanks to Mic D. 
class ExamManager(models.Manager): 
    def open_exams(delegation, action): 
     return Exam.objects.exclude(
      examaction__in=ExamAction.objects.filter(
       delegation=delegation, 
       action=action, 
       status=ExamAction.SUBMITTED, 
      ) 
     ).distinct() 

Так что это не так уж плохо.

кратко Глядя на предложенные ваши решения:

  1. Как вы отмечаете это работает только на одной строке за один раз.

  2. Для большинства таких вопросов это то, что я предлагаю. Но в вашем случае это будет квадратично увеличивать размер вашей базы данных, и это осложнит жизнь при добавлении новой делегации.

  3. A ManyToManyField - всего лишь синтаксический сахар, для вас здесь ничего не будет сделано.

  4. Это может быть хорошей идеей, но информации здесь недостаточно, чтобы действительно делать какие-либо предложения.

Так что я думаю, что ваш подход на самом деле довольно разумный, и вам просто нужно выяснить правильные запросы.

+0

Спасибо за отзыв. Запрос в Менеджере действительно намного более изящный, и он не настолько уродлив, чтобы поддерживать, как я, в начале. –

+0

Я попытался преследовать бит 2. Поскольку в конечном итоге у меня будет большая квадратичная база данных, я могу просто создать все записи в начале и избежать создания в запросах GET, я использовал сигналы post_save для обеих делегаций и экзамен. –

+0

Обратите внимание, что я нашел проблему в запрошенном вами запросе. Я опубликовал, как я исправил и какую-то многословную отладку. –

0

Запрос, который я в конечном итоге, используя это:

exams_open = Exam.objects.exclude(
    examaction__in=ExamAction.objects.filter(
     delegation=delegation, 
     action=ExamAction.TRANSLATION, 
     status=ExamAction.SUBMITTED) 
).distinct() 

Это был вдохновлен Kevin Christopher Henry, однако я нашел ошибку в нем. Чтобы отладить его, мне пришлось распечатать сгенерированный запрос с помощью print QuerySet.query. Разница довольно тонкая, но в моем случае это имело огромное значение.

Неправильный запрос:

print Exam.objects.exclude(
      examaction__delegation=delegation, 
      examaction__action=action, 
      examaction__status=ExamAction.SUBMITTED, 
     ).query 

Выход:

SELECT "app_exam"."id", 
     "app_exam"."name" 
FROM "app_exam" 
WHERE NOT ("app_exam"."id" IN (SELECT U1."exam_id" AS Col1 
           FROM "app_examaction" U1 
           WHERE U1."action" = t) 
      AND "app_exam"."id" IN (SELECT U1."exam_id" AS Col1 
            FROM "app_examaction" U1 
            WHERE U1."delegation_id" = 1) 
      AND "app_exam"."id" IN (SELECT U1."exam_id" AS Col1 
            FROM "app_examaction" U1 
            WHERE U1."status" = s)) 

Здесь любой экзамен, который соответствует любому из exclude() заявления получает удалены.

Для правильного запроса:

print Exam.objects.exclude(
    examaction__in=ExamAction.objects.filter(
     delegation=delegation, 
     action=ExamAction.TRANSLATION, 
     status=ExamAction.SUBMITTED) 
).query 

Выход:

SELECT "app_exam"."id", 
     "app_exam"."name" 
FROM "app_exam" 
WHERE NOT ("app_exam"."id" IN (SELECT V1."exam_id" AS Col1 
           FROM "app_examaction" V1 
           WHERE V1."id" IN (SELECT U0."id" 
                FROM "app_examaction" U0 
                WHERE (U0."delegation_id" 
                  = 1 
                  AND U0."action" = t 
                  AND U0."status" = s 
                  )))) 

Здесь мы правильно исключить экзамены только один раз.

 Смежные вопросы

  • Нет связанных вопросов^_^