可在字段集中使用的有序 ManyToManyField

2023-11-20

我一直在研究一个有序的 ManyToManyField 小部件,并且它的前端方面工作得很好:

alt text

不幸的是,我在让后端工作时遇到了很多麻烦。连接后端的明显方法是使用through表关闭模型ForeignKeys 到关系双方并覆盖 save 方法。这会很好用,除了由于内容的特殊性,绝对要求将此小部件放置在字段集中(使用 ModelAdminfieldsets属性),即显然不可能.

我没主意了。有什么建议么?

Thanks!


关于如何设置模型,您是对的,带有“订单”列的贯穿表是表示它的理想方式。你也是对的,Django 不会让你在字段集中引用该关系。解决这个问题的技巧是记住您在“fieldsets”或“fields”中指定的字段名称ModelAdmin实际上并不引用的字段Model,但是对于字段ModelForm,我们可以随心所欲地随意覆盖它。对于many2many字段,这会变得很棘手,但请耐心等待:

假设您试图代表比赛和参加比赛的参赛者,比赛和参赛者之间有一个有序的多对多,其中顺序代表参赛者在该比赛中的排名。你的models.py然后看起来像这样:

from django.db import models

class Contest(models.Model):
    name = models.CharField(max_length=50)
    # More fields here, if you like.
    contestants = models.ManyToManyField('Contestant', through='ContestResults')

class Contestant(models.Model):
    name = models.CharField(max_length=50)

class ContestResults(models.Model):
    contest = models.ForeignKey(Contest)
    contestant = models.ForeignKey(Contestant)
    rank = models.IntegerField()

希望这与您正在处理的情况类似。现在,对于管理员来说。我写了一个例子admin.py有大量评论来解释正在发生的事情,但这里有一个总结可以帮助您:

由于我没有您编写的有序 m2m 小部件的代码,因此我使用了一个占位符虚拟小部件,它只是继承自TextInput。输入包含以逗号分隔的参赛者 ID 列表(不带空格),他们在字符串中出现的顺序决定了他们在“排名”列中的值ContestResults model.

发生的情况是我们覆盖默认值ModelFormfor Contest 与我们自己的,然后在其中定义一个“结果”字段(我们不能将该字段称为“参赛者”,因为会与模型中的 m2m 字段发生名称冲突)。然后我们重写__init__(),当表单在管理中显示时调用,因此我们可以获取可能已为竞赛定义的任何 ContestResults,并使用它们来填充小部件。我们还覆盖save(),这样我们就可以从小部件中获取数据并创建所需的 ContestResults。

请注意,为了简单起见,此示例省略了诸如验证小部件中的数据之类的内容,因此如果您尝试在文本输入中输入任何意外的内容,事情就会中断。此外,用于创建 ContestResults 的代码非常简单,并且可以大大改进。

我还应该补充一点,我实际上已经运行了这段代码并验证了它的工作原理。

from django import forms
from django.contrib import admin
from models import Contest, Contestant, ContestResults

# Generates a function that sequentially calls the two functions that were
# passed to it
def func_concat(old_func, new_func):
    def function():
        old_func()
        new_func()
    return function

# A dummy widget to be replaced with your own.
class OrderedManyToManyWidget(forms.widgets.TextInput):
    pass

# A simple CharField that shows a comma-separated list of contestant IDs.
class ResultsField(forms.CharField):
    widget = OrderedManyToManyWidget()

class ContestAdminForm(forms.models.ModelForm):
    # Any fields declared here can be referred to in the "fieldsets" or
    # "fields" of the ModelAdmin. It is crucial that our custom field does not
    # use the same name as the m2m field field in the model ("contestants" in
    # our example).
    results = ResultsField()

    # Be sure to specify your model here.
    class Meta:
        model = Contest

    # Override init so we can populate the form field with the existing data.
    def __init__(self, *args, **kwargs):
        instance = kwargs.get('instance', None)
        # See if we are editing an existing Contest. If not, there is nothing
        # to be done.
        if instance and instance.pk:
            # Get a list of all the IDs of the contestants already specified
            # for this contest.
            contestants = ContestResults.objects.filter(contest=instance).order_by('rank').values_list('contestant_id', flat=True)
            # Make them into a comma-separated string, and put them in our
            # custom field.
            self.base_fields['results'].initial = ','.join(map(str, contestants))
            # Depending on how you've written your widget, you can pass things
            # like a list of available contestants to it here, if necessary.
        super(ContestAdminForm, self).__init__(*args, **kwargs)

    def save(self, *args, **kwargs):
        # This "commit" business complicates things somewhat. When true, it 
        # means that the model instance will actually be saved and all is
        # good. When false, save() returns an unsaved instance of the model.
        # When save() calls are made by the Django admin, commit is pretty
        # much invariably false, though I'm not sure why. This is a problem
        # because when creating a new Contest instance, it needs to have been
        # saved in the DB and have a PK, before we can create ContestResults.
        # Fortunately, all models have a built-in method called save_m2m()
        # which will always be executed after save(), and we can append our
        # ContestResults-creating code to the existing same_m2m() method.
        commit = kwargs.get('commit', True)
        # Save the Contest and get an instance of the saved model
        instance = super(ContestAdminForm, self).save(*args, **kwargs)
        # This is known as a lexical closure, which means that if we store
        # this function and execute it later on, it will execute in the same
        # context (i.e. it will have access to the current instance and self).
        def save_m2m():
            # This is really naive code and should be improved upon,
            # especially in terms of validation, but the basic gist is to make
            # the needed ContestResults. For now, we'll just delete any
            # existing ContestResults for this Contest and create them anew.
            ContestResults.objects.filter(contest=instance).delete()
            # Make a list of (rank, contestant ID) tuples from the comma-
            # -separated list of contestant IDs we get from the results field.
            formdata = enumerate(map(int, self.cleaned_data['results'].split(',')), 1)
            for rank, contestant in formdata:
                ContestResults.objects.create(contest=instance, contestant_id=contestant, rank=rank)
        if commit:
            # If we're committing (fat chance), simply run the closure.
            save_m2m()
        else:
            # Using a function concatenator, ensure our save_m2m closure is
            # called after the existing save_m2m function (which will be
            # called later on if commit is False).
            self.save_m2m = func_concat(self.save_m2m, save_m2m)
        # Return the instance like a good save() method.
        return instance

class ContestAdmin(admin.ModelAdmin):
    # The precious fieldsets.
    fieldsets = (
        ('Basic Info', {
            'fields': ('name', 'results',)
        }),)
    # Here's where we override our form
    form = ContestAdminForm

admin.site.register(Contest, ContestAdmin)

如果您想知道,我自己在我一直在从事的一个项目中遇到了这个问题,所以大部分代码都来自该项目。希望对你有帮助。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

可在字段集中使用的有序 ManyToManyField 的相关文章

随机推荐

  • 如何反汇编原始 16 位 x86 机器代码?

    我想反汇编我拥有的可启动 x86 磁盘的 MBR 前 512 字节 我已使用以下命令将 MBR 复制到文件中 dd if dev my device of mbr bs 512 count 1 对可以反汇编该文件的 Linux 实用程序的任
  • 在android中上传多个图像到服务器的最快方法

    我有多个图像要在服务器中上传 并且我有一种将单个图像上传到服务器的方法 现在我使用此方法通过为每个图像创建循环来发送多个图像 有没有最快的方法将多个图像发送到服务器 提前致谢 public int imageUpload GroupInfo
  • Xlib:XGetWindowAttributes 始终返回 1x1?

    我想要当前聚焦窗口的宽度和高度 窗口的选择就像一个魅力 而高度和宽度是always返回 1 include
  • 在虚拟列表达式中连接数字会引发 ORA-12899: 值对于列来说太大

    当我给出这个时answer昨天的一个问题 我建议使用虚拟栏目用于计算值而不是手动更新它 我自己做了一个测试 发现了虚拟列表达式在执行时所需的数据大小的问题连接 two NUMBER类型列 不过 连接两个字符时没有问题 数据库版本 SQL g
  • Ruby 中的块和过程

    我已经开始学习 Ruby 读了一些教程 甚至买了一本书 Programming Ruby 1 9 The Pragmatic Programmers Guide 我遇到了一些我以前在任何课程中都没有见过的新东西 我知道的其他语言 我是一名
  • Android从多页webview创建pdf文档

    我使用 Android 的 PdfDocument 框架 link 从我的 webview 内容创建一个 pdf 文档 pdf 创建得很好 但它只是一页文档 当网络视图内容很大时 我需要创建一个多页文档 我所需要的只是将网页内容拆分为多个页
  • 触发 $XCS_PRODUCT 后持续集成 Xcode Server 未设置

    我有一个在 Xcode 6 4 下运行良好的机器人 后触发脚本使用以下路径自动上传 IPA XCS OUTPUT DIR XCS PRODUCT 然而 即使从头开始重做机器人之后 似乎 XCS PRODUCT 始终为空 XCS OUTPUT
  • 多列上的 LINQ COUNT

    如果我有一个包含标题列和包含 1 或 NULL 的 3 位列 f1 f2 f3 的表 我将如何编写 LINQ 以返回标题以及包含 1 的每个位列的计数 我正在寻找与此 SQL 查询等效的内容 SELECT title COUNT f1 CO
  • Rstudio 编织为 PDF

    新版本的 Rstudio 0 98 932 有许多新选项 包括编织为 PDF 一篇描述新版本的文章有一个戴夫的评论说的是 安装 rstudio 0 98 932 后 我没有看到小下拉菜单 用于编辑 Rmd 文件时的 knit pdf 或 w
  • 如何在 OS X 上静态链接

    我正在尝试链接到 OS X 上的静态库 我使用了 static在 gcc 命令中标记 但我收到以下错误消息 ld classic can t locate file for lcrt0 o collect2 ld returned 1 ex
  • 如何在 Cython 中指定 `.pxd` 文件的路径

    我的项目有以下目录结构 Makefile pxd pyx Landscaping pyx Shrubbing pxd Shrubbing pyx setup py 然而 如果我移动Shrubbing pxd比如说 在其他任何地方pxd 我收
  • CG_INLINE 的作用是什么?

    我正在研究诸如此类的定义CGPoint有关如何创建我自己的函数的提示 但我不知道其目的CG INLINE 这里的幕后发生了什么 CG INLINE CGPoint CGPointMake CGFloat x CGFloat y CGPoin
  • 将 div 宽度设置为 100% 减去一定量的 px

    过去几个小时一直在网上搜索以找到解决方案 但找不到 所以我在这里 我需要的宽度div to be 100 减去宽度left div 所以这样div它的左侧保持相同的宽度 390px 但另一个div根据分辨率调整其大小 我找到了固定宽度的解决
  • Chrome 开发工具 - 修改 JavaScript 并重新加载

    是否可以修改页面的 JavaScript 然后重新加载页面 而不重新加载修改后的 JavaScript 文件 从而丢失修改 好消息 该修复将于 2018 年 3 月发布 请参阅此链接 https developers google com
  • 如何将我的数据集以“mnist.pkl.gz”中使用的确切格式和数据结构放入 .pkl 文件中?

    我正在尝试使用 python 中的 Theano 库对深度信念网络进行一些实验 我使用这个地址中的代码 DBN完整代码 该代码使用MNIST 手写数据库 该文件已经是 pickle 格式 它在以下位置未被选中 动车组 有效集 test se
  • java.lang.ClassCastException:android.widget.TextView无法转换

    12 01 00 36 28 058 E AndroidRuntime 5062 Caused by java lang ClassCastException android widget TextView cannot be cast t
  • 使用样式组件在 Material-UI 中进行媒体查询

    Material UI 有一组很好的内置媒体查询 https material ui com customization breakpoints css media queries Material UI 还允许我们将 Styled Com
  • 写入.txt 文件?

    如何将一小段文字写入 txt文件 我已经用谷歌搜索了超过 3 4 个小时 但不知道如何做 fwrite 有这么多参数 我不知道如何使用它 当您只想向其中写入名称和几个数字时 最简单的函数是什么 txt file char name int
  • 如何独占锁定mysql数据库?

    我有多个 mysql 数据库 我想在特定数据库上执行一些管理任务 如何确保任务正在进行时没有其他人可以连接到该数据库 显然 您可以使用 FLUSH 命令来执行此操作 gt 使用读锁刷新表 and then gt 解锁桌子 再次解锁数据库 不
  • 可在字段集中使用的有序 ManyToManyField

    我一直在研究一个有序的 ManyToManyField 小部件 并且它的前端方面工作得很好 不幸的是 我在让后端工作时遇到了很多麻烦 连接后端的明显方法是使用through表关闭模型ForeignKeys 到关系双方并覆盖 save 方法