Django中交换数据库中两个对象的表单
我刚开始学习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 个回答
在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)