基于选定的外键限制Django ManyToMany查询集

13

我有一个模型,长这个样子:

class Invite(models.Model):
    user = models.ForeignKey(User)
    event = models.ForeignKey(Event)
    roles = models.ManyToManyField(Role, blank=True, null=True)
    sent =  models.BooleanField("Invite Sent", default=False, editable=False)
    created = models.DateTimeField(auto_now_add=True)

    def __unicode__(self):
        return u"%s" % self.user

    class Meta:
        unique_together =(('user','event'),)


class Role(models.Model):
    """
    This associates a user's role to an event
    """
    event = models.ForeignKey(Event, related_name="roles")
    roletype = models.ForeignKey(RoleType)
    profiles = models.ManyToManyField(Profile, related_name="roles",
            blank=True, null=True)
    modified = models.DateTimeField(auto_now=True)
    created = models.DateTimeField(auto_now_add=True)

每当创建一个新事件时,一堆角色就会随之创建。在邀请模型中,我如何只显示与我在Django管理界面中选择的事件相关联的角色,而不是显示角色模型中的所有条目?

4个回答

11

你可能想要类似于以下的内容:

class InviteAdmin(admin.ModelAdmin):
    def get_form(self, request, obj=None, **kwargs):
        self.instance = obj # Capture instance before the form gets generated   
        return super(InviteAdmin, self).get_form(request, obj=obj, **kwargs)

    def formfield_for_manytomany(self, db_field, request=None, **kwargs):
        if db_field.name == 'role' and self.instance:
            # restrict role queryset to those related to this instance:         
            kwargs['queryset'] = self.instance.event.roles.all()
        return super(InviteAdmin, self).formfield_for_manytomany(
            db_field, request=request, **kwargs)

Django文档中关于formfield_for_manytomany的说明


2
问题明确地表示:“我如何只显示与我在更改表单中选择的事件相关联的角色”。 这绝对是通过动态方式(ajax)完成的。您的解决方案是静态的。 - Pannu
啊,我错过了那个细节,谢谢。我不会把这称为静态解决方案,因为它根据加载的对象和在数据库中选择的事件而变化,但我认为你是对的,这并没有实际解决所提出的问题 :) - DrMeers

9
你希望能够动态过滤“角色”选择器的选项,因此需要使用ajax执行此任务。
以下是如何使其工作的步骤。
1: 在事件OnChange中,通过ajax将event_id发送到自定义视图。
2: 从Roles模型中根据ajax请求获取的event_id筛选,并通过序列化转换为JSON返回筛选后的角色。
3:清除现有的角色,并通过解析JSON响应来重新填充。
例如:这是一个jquery getJSON示例
javascript:
$("#event").change(function (){         
 var event_id = $(this).val();              
    $.getJSON("/my-app/my-roles-filter-view/"+ event_id +"/",function(data){
        var roles_dd = $("#roles");
        $('#event >option').remove();
        $.each(data, function(index,value) {
        roles_dd.append($("<option />").val(value).text(value));
    });                 
})(django.jquery);

网址:

('^/my-app/my-roles-filter-view/(?P<event_id>\d+)/$','my_view'),

视图:

def my_view(request,event_id):
    qs = Role.objects.filter(event=event_id).values_list('id')
    return HttpResponse(simplejson.dumps(qs),mimetype='application/javascript')

这只是使用 jquery 的一个示例,您可以使用任何类型的 ajax 来实现这一点。


解决方案需要使用 JavaScript,因为当用户在表单中选择“事件”时,角色必须被动态地筛选(无需按按钮、提交整个表单,然后返回筛选后的选项),因此使用“ajax”是可行的方式。 - Pannu
是的,如果你希望在小部件中更改所选事件时在浏览器中进行更改,那么当然需要使用Javascript。抱歉,我看错了。 - DrMeers

1

您需要在您的模型管理类中提供一个自定义formfield_for_foreignkey方法。

以下是来自我所链接文档的示例,可以帮助您入门:

class MyModelAdmin(admin.ModelAdmin):
    def formfield_for_foreignkey(self, db_field, request, **kwargs):
        if db_field.name == "car":
            kwargs["queryset"] = Car.objects.filter(owner=request.user)
        return super(MyModelAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)

1
这将在加载时完成,而不是动态的。我认为“choices”必须动态地进行“过滤”。这似乎就是问题所在。 - Pannu
@Pannu:不,这实际上接近正确的解决方案,但要解决这个特定的问题,您需要访问相关的邀请实例。 - DrMeers

1

我认为有两种解决方法:

  1. 使用@Pannu描述的ajax方法

  2. 非ajax方法可以通过将event字段移出更改表单来实现(这意味着会有另一个表单来更改event),并根据当前event过滤roles。最近我也遇到了类似的问题,需要根据对象属于特定站点的情况限制可用选项。如果您感兴趣,这里是描述和代码:http://source.mihelac.org/2011/09/8/django-sites-ext/


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接