开发者

Django: make ModelChoiceField evaluate queryset at run-time

I've overridden the default manager of my models in order to show only allowed items, according to the logged user (a sort of object-specific permission):

class User_manager(models.Manager):
    def get_query_set(self):
        """ Filter results according to logged user """
        #Compose a filter dictionary with current user (stored in a middleware method)
        user_filter = middleware.get_user_filt开发者_如何转开发er() 
        return super(User_manager, self).get_query_set().filter(**user_filter)

class Foo(models.Model):
    objects = User_manager()
    ...

In this way, whenever I use Foo.objects, the current user is retrieved and a filter is applied to default queryset in order to show allowed records only.

Then, I have a model with a ForeignKey to Foo:

class Bar(models.Model):
    foo = models.ForeignKey(Foo)

class BarForm(form.ModelForm):
    class Meta:
        model = Bar

When I compose BarForm I'm expecting to see only the filteres Foo instances but the filter is not applied. I think it is because the queryset is evaluated and cached on Django start-up, when no user is logged and no filter is applied.

Is there a method to make Django evalutate the ModelChoice queryset at run-time, without having to make it explicit in the form definition? (despite of all performance issues...)

EDIT I've found where the queryset is evaluated (django\db\models\fields\related.py: 887):

def formfield(self, **kwargs):
    db = kwargs.pop('using', None)
    defaults = {
        'form_class': forms.ModelChoiceField,
        'queryset': self.rel.to._default_manager.using(db).complex_filter(self.rel.limit_choices_to),
        'to_field_name': self.rel.field_name,
    }
    defaults.update(kwargs)
    return super(ForeignKey, self).formfield(**defaults)

Any hint?


Had exactly this problem -- needed to populate select form with user objects from a group, but fun_vit's answer is incorrect (at least for django 1.5)

Firstly, you don't want to overwrite the field['somefield'].choices object -- it is a ModelChoiceIterator object, not a queryset. Secondly, a comment in django.forms.BaseForm warns you against overriding base_fields:

    # The base_fields class attribute is the *class-wide* definition of
    # fields. Because a particular *instance* of the class might want to
    # alter self.fields, we create self.fields here by copying base_fields.
    # Instances should always modify self.fields; they should not modify
    # self.base_fields.

This worked for me (django 1.5):

class MyForm(ModelForm):
    users = ModelMultipleChoiceField(queryset=User.objects.none())

    def __init__(self, *args, **kwargs):
        super(MyForm, self).__init__(*args,**kwargs)

        site = Site.objects.get_current()
        self.fields['users'].queryset = site.user_group.user_set.all()

    class Meta:
        model = MyModel


i use init of custom form:

class BT_Form(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super(BT_Form, self).__init__(*args, **kwargs)

        #prepare new values
        cities = [(u'',u'------')] #default value
        cities.extend([
            (
                c.pk, 
                c.__unicode__()
            ) for c in City.objects.filter(enabled=True).all()
        ])

        self.fields['fly_from_city'].choices = cities #renew values


No way: I had to rewrite queryset definition (which is evaluated at startup)

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜