Извлечение обратного внешнего ключа из обратного внешнего ключа в django, в то же время используя prefetch_related - PullRequest
0 голосов
/ 01 ноября 2018

У меня есть следующие модели (они значительно упрощены):

class Job(models.Model):
    name = models.CharField(max_length=255)

    def get_date_details(self):
        pass
        #return a list of all jobdate --> details


class JobDate(models.Model):
    job = models.ForeignKey(Job, on_delete=models.CASCADE, related_name='dates')
    date = models.DateField()
    # ... and a bunch of other fields


class JobDateDetail(models.Model):
    job_date = models.ForeignKey(JobDate, on_delete=models.CASCADE, related_name='details')
    detail = models.CharField(max_length=255)
    # ... and a bunch of other fields

И регулярно нужно обрабатывать JobDateDetails для всех заданий в базе данных (несколько тысяч). Я знаю, что могу предварительно выбрать все данные с помощью запроса:

q = Job.objects.all().prefetch_related('dates', 'dates__details')

Однако я не уверен, как наилучшим образом использовать предварительно выбранные данные в Job.get_date_details(). Одним из вариантов будет сделать что-то вроде:

class Job(models.Model):
    def get_date_details(self):
        details = []
        for job_date in self.dates.all():
            details += [detail for detail in job_date.details.all()]
        return details

Тем не менее, я подозреваю, что может быть способ запросить все эти JobDateDetail объекты напрямую. Следует отметить одну вещь - как уже было сказано, я понимаю, что могу пойти в другом направлении, чтобы собрать все детали за один проход, однако в этом случае я использую Django Rest Framework и мне нужно собрать детали в виде поля в сериализаторе Job, требуя от меня идти в этом направлении. Мысли? * * 1013

1 Ответ

0 голосов
/ 02 ноября 2018

Я ввел несколько случайных данных для демонстрации запроса. Вот два варианта:

  • Первый находится на уровне таблицы, 1db hit :

    In [23]: Job.objects.all().values_list('id', 'dates__details__detail')                                                                                                                                             
    Out[23]: <QuerySet [(1, '132'), (1, '4324gg'), (1, 'hrthrthrth'), (1, 'erhehrgnfgnmfgghmn'), (1, 'herhehrnfn'), (1, 'erg eg cvb dfg vb'), (1, 'greg egr erg erg erg'), (1, 'ewrg erg db cvb'), (2, None), (3, None)]>
    
  • Второй находится на уровне строки Хиты в 2 дБ (один для извлечения объекта, а другой для извлечения деталей даты):

    In [36]: Job.objects.all().first().dates.all().values_list('details__detail')                                                                                                                                      
    Out[36]: <QuerySet [('132',), ('4324gg',), ('hrthrthrth',), ('erhehrgnfgnmfgghmn',), ('herhehrnfn',), ('erg eg cvb dfg vb',), ('greg egr erg erg erg',), ('ewrg erg db cvb',)]>
    

Второй, эквивалентный self.dates.all().values_list('details__detail'), если вызывается из метода модели get_date_details и может принести следующие результаты с 1db hit :

In [30]: from itertools import chain
In [31]: list(chain.from_iterable( 
    ...:     Job.objects.all().first().dates.all().values_list('details__detail') 
    ...: ))        
Out[31]: 
['132',
 '4324gg',
 'hrthrthrth',
 'erhehrgnfgnmfgghmn',
 'herhehrnfn',
 'erg eg cvb dfg vb',
 'greg egr erg erg erg',
 'ewrg erg db cvb']

Поэтому метод вашей модели может стать:

class Job(models.Model):
    def get_date_details(self):
        return (
            list(chain.from_iterable(
                self.dates.all().values_list('details__detail')
            ))                                                                                                           
        )
...