简体   繁体   中英

Dynamically limit choices for Foreignkey in Django models based on another foreign key in the same model

I have these models:

class UserProfile(models.Model):
    name = models.CharField(max_length=100)

class Dialog(models.Model):
    belong_to = models.ManyToManyField(UserProfile)

class Message(models.Model):
    # Dialog to which this message belongs
    part_of = models.ForeignKey(Dialog)

    # User who sends message
    sender = models.ForeignKey(UserProfile, related_name='sender')
    # User who receives message 
    receiver = models.ForeignKey(UserProfile, related_name='receiver')

What I want to do is limit the choices for the sender and receiver fields so that they can only be the users to which the whole dialog belongs. I tried this:

sender = models.ForeignKey(UserProfile,
                           related_name='sender',
                           limit_choices_to={'dialog':1})

which limits the choices but only for members of dialog with id=1. I'm wondering if this can be done dynamically?

I don't believe there is any way to dynamically filter like you want using limit_choices_to, as you won't have access to the needed objects to form such a query there.

Instead you should probably create your own model form for message and set the queryset for those fields there. Something like below...

class MessageForm(forms.ModelForm):
    class Meta:
        model = Message

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

        if self.instance.part_of and self.instance.part_of.id:
            users = self.instance.part_of.belong_to.all()
            self.fields['sender'].queryset = users
            self.fields['receiver'].queryset = users

Further, why limit_choices_to works for your example, but is not dynamically useful.

Django simply processes the limit_choices_to expression as an extra filter to be applied to the ModelForm fields queryset. Your expression {dialog: 1} is semantically no different than if you were to assign the result of UserProfile.objects.filter(dialog=1) to the queryset in my example.

Django does not know if a dialog with that id exists as a relation on the UserProfile, it just applies the filter. In this case a dialog with id 1 exists and so it works out. If you stick an invalid dialog id in your example instead..it would evaluate to an empty queryset and you would have get 0 choices in your form.

It cannot be dynamic because in limit_choices_to you can only create a filter expression for the UserProfile model. You have no access to the Message instance the field belongs to, nor the Dialog model the message belongs to...hence you cannot create a filter to dynamically limit to those.

Creating your own ModelForm and limiting the queryset for the field there, where you have the information you need, is the proper way to do it.

If instances of Message all belong to a Dialog , why not create a field messages on the Dialog model? Then, you could attach a sender and receiver to each Dialog . In short, something along these lines:

class Dialog(models.Model):
    messages = models.ManyToManyField(Message)
    sender = models.ForeignKey(UserProfile)
    receiver = models.ForeignKey(UserProfile)

class Message(models.Model):
    # Other fields

Sender and receiver of the Message are then always those to which the Dialog belongs.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM