Django ModelAdmin 使用自定义查询

Django ModelAdmin use custom query

您好,目前我在不使用 models.py

中的任何已定义模型的情况下向应用管理端添加自定义 ModelAdmin 部分时遇到问题

例如,我有 3 个模型(充值、取款、转账),我想添加一个单独的 ModelAdmin 事务部分,它是这 3 个模型的组合,因为我喜欢它的分页、更改列表和详细信息视图。

我的模特:

#TOPUP
class TopUp(SafeDeleteModel):
    class Meta:
        db_table = "topups"
        verbose_name = 'TopUp Request'
        verbose_name_plural = 'TopUp Requests'

    user = models.ForeignKey("backend.User", null=True, blank=True, related_name='user_toptup', on_delete=models.CASCADE)
    currency = models.ForeignKey("backend.Currency", null=True, blank=True, related_name='user_topup_currency', on_delete=models.SET_NULL)

    TOPUP_METHOD_CHOICES = [
        (1, 'method 1'),
        (2, 'method 2')
    ]
    method = models.PositiveSmallIntegerField("Method", choices=TOPUP_METHOD_CHOICES)

    amount = models.DecimalField("Amount", max_digits=65, decimal_places=0, default=0)
    fee = models.DecimalField("Fee", max_digits=65, decimal_places=0, default=0)

    TOPUP_STATUS_CHOICES = [
        (0, 'Pending'),
        (1, 'Success'),
        (2, 'Failed'),
    ]
    status = models.PositiveSmallIntegerField("Status", choices=TOPUP_STATUS_CHOICES, default=0)
    created = models.DateTimeField(auto_now_add=True)
    received = models.DateTimeField(null=True, blank=True)

# WITHDRAWALS
class Withdrawals(SafeDeleteModel):
    class Meta:
        db_table = "withdrawals"
        verbose_name = 'Withdraw Request'
        verbose_name_plural = 'Withdraw Requests'

    user = models.ForeignKey("backend.User", null=True, blank=True, related_name='user_withdrawal', on_delete=models.CASCADE)
    currency = models.ForeignKey("backend.Currency", null=True, blank=True, related_name='user_withdrawal_currency', on_delete=models.SET_NULL)

    WITHDRAWAL_METHOD_CHOICES = [
        (1, 'method 1'),
        (2, 'method 2')
    ]
    method = models.PositiveSmallIntegerField("Method", choices=WITHDRAWAL_METHOD_CHOICES)
    to_bank = models.ForeignKey("backend.UserBank", null=True, blank=True, related_name='user_withdrawal_userbank', on_delete=models.SET_NULL, db_column='to_bank')
    to_address = models.CharField("To address", max_length=255, null=True, blank=True, db_column='to_address')
    to_card = models.ForeignKey("backend.CardBinding", null=True, blank=True, related_name='user_withdrawal_to_card', on_delete=models.SET_NULL, db_column='to_card')
    amount = models.DecimalField("Amount", max_digits=65, decimal_places=0, default=0)
    fee = models.DecimalField("Fee", max_digits=65, decimal_places=0, default=0)

    WITHDRAWAL_STATUS_CHOICES = [
        (0, 'Pending'),
        (1, 'success'),
        (2, 'failed')
    ]
    status = models.PositiveSmallIntegerField("Status", choices=WITHDRAWAL_STATUS_CHOICES, default=0)
    created = models.DateTimeField(auto_now_add=True)
    submitted = models.DateTimeField(null=True, blank=True)
    confirmed = models.DateTimeField(null=True, blank=True)

# TRANSFERS

class Transfers(SafeDeleteModel):
    class Meta:
        db_table = "transfers"
        verbose_name = 'Transfer'
        verbose_name_plural = 'Transfers'

    user = models.ForeignKey("backend.User", null=True, blank=True, related_name='user_transfer', on_delete=models.CASCADE)
    currency = models.ForeignKey("backend.Currency", null=True, blank=True, related_name='user_transfer_currency', on_delete=models.SET_NULL)

    TRANSFER_METHOD_CHOICES = [
        (2, 'method 1'),
        (3, 'method 2')
    ]
    method = models.PositiveSmallIntegerField("Method", choices=TRANSFER_METHOD_CHOICES)

    to_address = models.CharField("To Address", max_length=255, null=True, blank=True, db_column='to_address')
    to_account = models.ForeignKey("backend.User", null=True, blank=True, related_name='user_transfer_to_account', on_delete=models.SET_NULL, db_column='to_account')
    amount = models.DecimalField("Amount", max_digits=65, decimal_places=0, default=0)
    fee = models.DecimalField("Fee", max_digits=65, decimal_places=0, default=0)

    TRANSFER_STATUS_CHOICES = [
        (0, 'Pending'),
        (1, 'Success'),
        (2, 'Failed')
    ]
    status = models.PositiveSmallIntegerField("Status", choices=TRANSFER_STATUS_CHOICES, default=0)
    created = models.DateTimeField(auto_now_add=True)
    submitted = models.DateTimeField(null=True, blank=True)
    confirmed = models.DateTimeField(null=True, blank=True)

所以如果我有这样的查询:

        cursor = connection.cursor()
        cursor.execute('''
            SELECT * FROM 
                (SELECT id,
                    user_id, 
                    'top up'  AS transaction_type, 
                    method, 
                    NULL     AS to_bank, 
                    NULL     AS to_address, 
                    user_id  AS to_account, 
                    NULL     AS to_card, 
                    currency_id, 
                    amount, 
                    fee,
                    status, 
                    created AS created, 
                    received AS confirmed 
                FROM   topups 
                WHERE deleted IS NULL
                UNION ALL 
                SELECT id,
                    user_id, 
                    'transfer' AS transaction_type, 
                    method, 
                    NULL       AS to_bank, 
                    to_address, 
                    to_account, 
                    NULL       AS to_card, 
                    currency_id, 
                    amount, 
                    fee,
                    status, 
                    created AS created, 
                    confirmed  AS confirmed 
                FROM   transfers 
                WHERE deleted IS NULL
                UNION ALL 
                SELECT id,
                    user_id, 
                    'withdrawal' AS transaction_type, 
                    method, 
                    to_bank, 
                    to_address, 
                    NULL         AS to_account, 
                    to_card, 
                    currency_id, 
                    amount, 
                    fee,
                    status, 
                    created AS created, 
                    confirmed    AS confirmed 
                FROM   withdrawals
                WHERE deleted IS NULL
                ) AS T
            ORDER BY created DESC'''
        )

        row = namedtuplefetchall(cursor)

它 return 3 个表的 UNION 和像这样的列:

  {
    "user_id": 120,
    "transaction_type": "transfer",
    "method": 3,
    "to_bank" null,
    "to_card" null,
    "to_address" null,
    "to_account": 170,
    "currency_id": 1,
    "amount": "-10000",
    "fee": "100000000",
    "status": 2,
    "created": 1582272307,
    "confirmed": 1582272307
  },

如何让 ModelAdmin 使用此查询?我还没有找到任何仅使用原始查询而不是模型的管理部分的解决方案

为了在 ModelAdmin 中获得最佳体验(过滤、正确的类型检测)需要 Model 分配。

使用必填字段创建 Model。告诉 Django 不要管理模型 - 不要为它创建 db table - 所以它会假设 db table 因为它已经存在。

class Transaction(models.Model):
    # all fields of the result of UNION
    user = models.ForeignKey(User)
    transaction_type = models.CharField(max_length=128)
    method = models.IntegerField()
    ...

    class Meta:
        managed = False  # django will not create db table
        db_table = "myapp_transaction_view"  # if accessing database view

现在,您可以创建 database view - 虚构 table 在数据库中仅在访问时生成 - 自定义 SELECT.

的快捷方式

您可以创建 django 数据库迁移并在其中创建视图:

...
    migrations.RunSQL(
        """
        CREATE VIEW myapp_transaction_view AS
            SELECT * FROM .....; /* your UNION SELECT */
        """,
        """
        DROP VIEW IF EXISTS myapp_transaction_view;
        """,
    )
...

现在,Transaction 模型自动链接到此视图和 select 来自它的 运行 自定义联合 select。这个模型可以像往常一样传递给 ModelAdmin


或者,您可以避免在数据库中创建视图 - 相反,在 ModelAdmin 上重新定义 get_queryset() 方法并在其中提供查询 - 这样它可能更可定制,或者您可以使用 Django ORM构建查询。

进一步扩展 - 自定义 sql 可以放置在自定义模型管理器中,作为 sql / 查询集比 ModelAdmin 更合适的位置。