Django: создание неопределенного объединения двух наборов запросов

Я пишу бухгалтерское приложение в django, и есть Orders, у которых есть дата создания счета и необязательная дата создания кредит-ноты.

class Order(models.Model):
    date_invoice_created = models.DateTimeField(null=True, blank=True)
    date_credit_note_created = models.DateTimeField(null=True, blank=True)

В настоящее время я разрабатываю представление для нашего бухгалтера, и она хотела бы, чтобы и счет-фактура, и кредит-нота находились в отдельных строках на панели администратора, отсортированные по соответствующим датам создания.

Итак, в основном я хотел бы показать одну и ту же модель дважды, в разных строках, отсортированных по разным полям. В SQL это будет примерно так:

SELECT id, create_date FROM (
    SELECT id, date_invoice_created AS create_date, 'invoice' AS type FROM order
        UNION
    SELECT id, date_credit_note_created AS create_date, 'creditnote' AS type FROM order
) ORDER BY create_date

Не обращайте внимания на то, что мой SQL-фу не актуален, но я думаю, вы понимаете, что я имею в виду.

Итак, я попытался заставить django сделать это за меня, переопределив дату во втором наборе запросов, потому что django не поддерживает объединение двух наборов запросов extra:

invoices = Order.objects.filter(date_invoice_created__isnull=False)
credit_notes = Order.filter_valid_orders(qs
    ).filter(
        date_credit_note_created__isnull=False
    ).extra(
        select={'date_invoice_created': 'date_credit_note_created'}
    )
return (invoices | credit_notes).order_by('date_invoice_created')

к сожалению, операция побитового ИЛИ для объединения всегда гарантирует, что идентификаторы различны, но я действительно хочу, чтобы они не были такими. Как я могу добиться объединения с повторяющимися строками?


person devsnd    schedule 17.09.2014    source источник
comment
Мне кажется, что вы занимаетесь преждевременной оптимизацией. Если это два разных списка, отображаемых независимо друг от друга, почему вы так усердно пытаетесь получить их в одном запросе?   -  person Ludwik Trammer    schedule 17.09.2014
comment
Потому что я хочу сохранить всю функциональность, предоставляемую интерфейсом contrib.admin, который использует наборы запросов, чтобы творить чудеса.   -  person devsnd    schedule 17.09.2014
comment
это может быть то, что вы хотите: stackoverflow.com/questions/431628/   -  person Fabricator    schedule 17.09.2014
comment
Спасибо, но itertools.chain не совместим с интерфейсом набора запросов и поэтому не работает в моем случае. Мне нужен набор запросов, так как я хочу переопределить метод queryset для django.contrib.admin.ModelAdmin...   -  person devsnd    schedule 18.09.2014


Ответы (1)


Теперь я нашел решение своей проблемы с помощью SQL-View.

Я создал новую миграцию (используя юг), которая содержит приведенный выше SQL-запрос, упомянутый в вопросе, как представление, которое дважды возвращает все строки, каждая с create_date и type соответственно для кредит-ноты и счета-фактуры.

accounting/migrations/00xx_create_invoice_creditnote_view.py:

class Migration(SchemaMigration):

    def forwards(self, orm):
        query = """
          CREATE VIEW invoiceoverview_invoicecreditnoteunion AS
            SELECT * FROM (
                SELECT  *, 
                        date_invoice_created AS create_date,
                        'invoice' AS type 
                    FROM accounting_order
                    WHERE date_invoice_created NOT NULL
                UNION
                SELECT  *,
                        date_credit_note_created AS date,
                        'creditnote' AS type
                    FROM accounting_order
                    WHERE date_credit_note_created NOT NULL
            );
        """
        db.execute(query)


    def backwards(self, orm):
        query = """
          DROP VIEW invoiceoverview_invoicecreditnoteunion;
        """
        db.execute(query)

    # ...
    # the rest of the migration model
    # ...

Затем я создал новую модель для этого представления, которая имеет Meta managed = False, так что django использует модель, не заботясь о ее создании. Она имеет все те же поля, что и исходная модель Order, но также включает два новых поля из SQL-View:

invoiceoverview/models.py:

class InvoiceCreditNoteUnion(models.Model):
    """ This class is a SQL-view to Order, so that the credit note and
    invoice can be displayed independently.

    """
    class Meta:
        managed = False  # do not manage the model in the DB
    # fields of the view
    date = models.DateTimeField() 
    type = models.CharField(max_length=255)

    # ...
    # all the other fields of the original Order
    # ...

Теперь я могу использовать эту модель для contrib.admin.ModelAdmin и отображать соответствующий контент, проверяя поле type. например.:

class InvoiceAdmin(admin.ModelAdmin):
    list_display = ['some_special_case']

    def some_special_case(self, obj):
        if obj.type == 'creditnote':
            return obj.credit_note_specific field
        else:
            return obj.invoice_specific_field

admin.site.register(InvoiceCreditNoteUnion, InvoiceAdmin)

Это, наконец, позволяет мне использовать все другие функции, предоставляемые панелью администратора, например. переопределение метода queryset, сортировка и т. д.

person devsnd    schedule 18.09.2014