Django中交换数据库中两个对象的表单

1 投票
1 回答
1145 浏览
提问于 2025-04-17 10:37

我刚开始学习Django,过去一个月一直在开发一个简单的应用,但遇到了一个问题,有些事情我没能搞定。

我有一个简单的模型叫做WeeklyPlaylist(在我的models.py里):

class WeeklyPlaylist(models.Model):
    total_num_entries_in_playlist = 8
    get_pos_choices = get_integer_choices(1, total_num_entries_in_playlist)
    sched = models.ForeignKey(Schedule)
    week = models.IntegerField(choices=get_integer_choices(1, 52))
    position = models.IntegerField(choices=get_pos_choices)

这里的'position'表示视频在播放列表中的位置。

我想给管理员一个功能,让他们能够在播放列表中交换一个视频的位置和另一个视频的位置,这个功能是在上面的模型的更改/更新表单中实现的(在我的admin.py里):

class WeeklyPlaylistAdmin(admin.ModelAdmin):
    (...)
    readonly_fields = ('position',)
    form = WeeklyPlaylistAdminForm

    def get_changelist_form(self, request, obj=None, **kwargs):
        return WeeklyPlaylistAdminForm

我还为这个对象定义了自己的表单(同样在admin.py里):

class WeeklyPlaylistAdminForm(ModelForm):
    class Meta:
        model = WeeklyPlaylist
        fields = ('position',)

    swap_with_position = forms.IntegerField(widget=forms.Select(choices=WeeklyPlaylist.get_pos_choices))

    def clean_swap_with_position(self):
        swap_with_position = self.cleaned_data['swap_with_position']
        instance = getattr(self, 'instance', None)
        if instance and instance.id and swap_with_position == self.instance.position:
            raise forms.ValidationError("You must specify a different position than the actual one.")

        # select the database obj to swap position with
        other = WeeklyPlaylist.objects.filter(sched__screen__name=self.instance.sched.screen.name, sched__year__exact=self.instance.sched.year, week=self.instance.week, position=swap_with_position)
        if other.count() != 1:
            raise forms.ValidationError("The desired position does not correspond to any existing WeeklyPlaylist entry.")

        return swap_with_position

我想的基本上是,在WeeklyPlaylist模型的更改/更新表单中,给管理员提供一个额外的'select' HTML标签,让他们可以输入当前视频在播放列表中的新位置,并在相关的clean_方法中进行必要的检查,以确保输入的位置是有效的。

到目前为止都还不错。现在,我的问题是:当管理员点击'保存'按钮时,如何同时保存被修改的对象和它要交换位置的那个对象?我试着在表单的save()方法中做到这一点,使用了以下代码:

def save(self, commit=True, *args, **kwargs):
    m = super(WeeklyPlaylistAdminForm, self).save(commit=False, *args, **kwargs)
    cleaned_data = self.cleaned_data
    swap_with_position = cleaned_data.get("swap_with_position")
    if commit:
        # select the database obj to swap position with
        other = WeeklyPlaylist.objects.get(sched__screen__name=m.sched.screen.name, sched__year__exact=m.sched.year, week=m.week, position=swap_with_position)
        m.position, other.position = other.position, m.position
        m.save()
        other.save()
    return m

这看起来没问题,但出于某种原因,commit总是为false,尽管'm'在操作完成后被保存了,这让我很困惑。结果是,other.save()从未被调用,如果我去掉检查commit值的if语句,我就无法对WeeklyPlaylistAdminForm对象进行save(commit=False),这会很麻烦……

所以,有什么建议可以帮我吗?非常感谢!

祝好!

Adrien

1 个回答

1

在Django中,我做过类似的事情,但我没有专门实现那些与其他类别交换的模型,而是重新排序列表。比如说,把顺序为5的元素移动到顺序为0的位置,这样就会把之前顺序在0到4之间的所有元素往上挪动。不过,和一个特定顺序的元素交换应该简单得多。

我会在任何修改发生之前,先保存你模型的原始位置,然后在保存方法中检查这个位置是否发生了变化。如果位置变了,我会先保存当前的模型,然后查找那个原本在这个位置但现在不在这个位置的模型,接着更新那个模型以纠正它的位置。希望下面的代码能帮助你理解我的意思:

class WeeklyPlaylist(models.Model):
  def __init__(self, *args, **kwargs):
    super(WeeklyPlaylist, self).__init__(*args, **kwargs)
    self._position = int(self.position)

  def save(self, *args, **kwargs):
    super(WeeklyPlaylist, self).save(*args, **kwargs)

    # position has changed, so change the position of the element that held the new position now held by this element
    if int(self.position) != self._position:
      WeeklyPlaylist.objects.exclude(pk=self.pk).filter(
        position=self.position
      ).update(position=self._position)

撰写回答