【问题标题】:Django ModelAdmin use custom queryDjango ModelAdmin 使用自定义查询
【发布时间】:2020-06-28 03:00:18
【问题描述】:

您好,目前我在将自定义 ModelAdmin 部分添加到应用程序管理端时遇到问题,而无需在 models.py 中使用任何定义的模型

例如,我有 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)

它返回 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 使用此查询?我还没有找到只使用原始查询而不是模型的管理部分的任何解决方案

【问题讨论】:

  • 你尝试过覆盖get_queryset吗?
  • 我尝试制作 TopUp 的代理模型并使用自定义管理器返回查询,但我在管理列表页面上收到 user 的关键错误,我不知道 get_queryset 是否接受RawQuery,但我回家后会尝试
  • 我认为您不能使用 RawQuery。但是您也许可以使用ModelA.objects.all().union(ModelB.objects.all())
  • 这是我在另一个问题中尝试过的一种方式,但由于后处理试图解析字符串值stackoverflow.com/questions/60732879/…,我遇到了字符串值注释的问题

标签: python django django-forms django-templates django-admin


【解决方案1】:

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

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

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 - 仅在访问时生成的数据库中的虚构表 - 自定义 SELECT 的快捷方式。

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

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

现在,Transaction 模型链接到此视图并从中选择自动运行自定义联合选择。这个模型可以像往常一样直接传递给ModelAdmin


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

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

【讨论】:

  • 很好,我可以从查询中创建一个 MySQL 视图,并与 managed=false 的假模型一起使用,现在我可以将它作为模型,感谢您的建议。但是因为视图没有自动递增 id 管理员需要一个 id 主键字段,所以我会在另一个问题中问这个
猜你喜欢
  • 1970-01-01
  • 2011-11-08
  • 2015-03-20
  • 2011-06-18
  • 1970-01-01
  • 1970-01-01
  • 2011-03-04
  • 2011-03-11
  • 1970-01-01
相关资源
最近更新 更多