Django:我如何为不同的用户提供相同的ManyToManyField

时间:2017-03-29 13:41:36

标签: django django-admin

问题:

如何为管理员中的同一ManyToManyField提供不同用户的不同选项子集,并且每个用户的选择仅影响他们的选项?

场景:

我正在构建一个交互式地图Web应用程序。可以有很多地图。可以有许多MapItems。 MapItems可以在很多地图上,而地图可以有很多地图项目。因此,ManyToMany关系。

每个地图都有一个所有者,我只希望地图的所有者能够在他/她的地图中添加或删除MapItems。

以下是我的模型的相关部分:

class Map(models.Model):
    # Omit
    owner = models.ForeignKey(User, default=1)

class MapItem(models.Model):
    # Omit
    map = models.ManyToManyField(Map, default=None, blank=True)

MapItem.map由MapItem管理界面上的<select>表示:

<select multiple="multiple" id="id_map" name="map">
  <option value="1">Bob's Map</option>
  <option value="2">Sally's Map</option>
</select>

我希望鲍勃只有选择/取消选择鲍勃的地图的选项,甚至不能看到莎莉的地图作为选项。

我已经能够通过覆盖ModelAdmin上的get_form()来过滤admin <select>小部件中显示的选项。

def get_form(self, request, obj=None, **kwargs):
    kwargs['form'] = MapItemAdminForm
    form = super(MapItemAdmin, self).get_form(request, obj, **kwargs)
    form.base_fields['map'].queryset = form.base_fields['map'].queryset.filter(Q(owner=request.user) | Q(groups__in=request.user.groups.all()))
    return form

到目前为止一切顺利。这给了我一个只有所需选项的选择:

<select multiple="multiple" id="id_map" name="map">
  <option value="1">Bob's Map</option>
</select>

鲍勃保存时会出现问题。当Bob保存时,它会清除Sally Map的任何保存值。因此,如果选择了Sally的地图,当Bob保存时(即使Bob看不到它),Sally的地图也会被取消选择。

有没有更好的方法解决这个问题?有没有办法确保保存时保持未更改的值(如Sally的Map被选中)?

2 个答案:

答案 0 :(得分:0)

尝试覆盖ModelAdmin的save_model方法。

在其中,您需要删除属于当前用户的所有关系(而不是删除当前行为的所有关系)并保存所有提交的关系(您知道属于当前用户,因为您筛选了选项,但您可能应该在上一步中重新验证这一点,因为HTTP请求可以再次被篡改。

答案 1 :(得分:0)

@ lufte的答案很接近,但不是我需要的。它确实让我闻到了正确的气味。这是我的解决方案:

save_model非常适合调整非ManyToMany字段的值。但是,ManyToMany字段 无论在will be cleared prior to being saved进行了哪些更改,都无法在此方法中受到影响。

另一方面,

save_related正是我所需要的。我能够确定我的用户应该在get_form中影响哪些地图,并将其他任何地图传递给save_relatedDOCS.

这里有一些代码供参考:

def get_form(self, request, obj=None, **kwargs):
      form = super(MapItemAdmin, self).get_form(request, obj, **kwargs)

      # Filter already selected maps
      # and create a list of map current user can't touch
      self.existing_maps = [m.id for m in obj.map.all() \
      if not m.owner == request.user and not \
      m.groups.filter(id__in=request.user.groups.all())]

      # Filter the options that will be displayed in the admin <select>
      form.base_fields['map'].queryset = \
      form.base_fields['map'].queryset.filter(Q(owner=request.user) \
      | Q(groups__in=request.user.groups.all()))
      return form

  def save_related(self, request, form, formsets, change):
      super(MapItemAdmin, self).save_related(request, form, formsets, change)

      # Re-add any previously selected maps that the current user can't touch
      for em in self.existing_maps:
          form.instance.map.add(em)