rozzy
rozzy

Reputation: 2948

Django many to many filter by ALL in list

I have the following models:

class TradeList(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL, unique=True)
    text = models.TextField(null=True, blank=True)
    modified = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ['-modified']

    def __str__(self):
        return "{}'s trade list".format(self.user.username)

    def get_absolute_url(self):
        return reverse('tradelist_detail', kwargs={'username': self.user.username})

class TradeItem(models.Model):
    tradelist = models.ForeignKey('TradeList')
    card = models.ForeignKey('cards.Card')
    quantity = models.PositiveSmallIntegerField()

    class Meta:
        abstract = True

class ForTrade(TradeItem):
    class Meta:
        verbose_name_plural = 'for trade'
        ordering = ['card']

class LookingFor(TradeItem):
    class Meta:
        verbose_name_plural = 'looking for'
        ordering = ['card']

I want to be able to search these models from my site, so I created a form like so:

class TradeListSearchForm(forms.Form):
    for_trade = forms.ModelMultipleChoiceField(queryset=Card.objects.all())

I am using the generic FormView to handle the form, so I have overridden its form_valid method to customise the behaviour when the form has been validated successfully:

class TradeListSearchView(FormView):
    form_class = TradeListSearchForm
    template_name = 'trades/tradelist_search.html'

    def form_valid(self, form):
        search_results = TradeList.objects.filter(fortrade__card__in=form.cleaned_data['for_trade'])

        return self.render_to_response(self.get_context_data(form=form,
                                                             search_results=search_results))

This works great except for one thing. At the moment, the user can select multiple cards to search for and the view searches for all tradelists that have any of those particular cards for trade. However I would like to be able to perform the search to that it looks for only those tradelists that have ALL the cards selected for trade, but I cannot think how to do this.

Upvotes: 0

Views: 69

Answers (1)

vlad-ardelean
vlad-ardelean

Reputation: 7622

I think you can use a little magic:

This line is what kills your boom: search_results = TradeList.objects.filter(fortrade__card__in=form.cleaned_data['for_trade'])

Instead of it, use this: (notice i'm explicitly filtering down the TradeList instances many times)

for_trade = form.cleaned_data['for_trade']
search_results = Tradelist.objects.all()
for card in for_trade:
    search_results = search_results.filter(fortrade__card__id=card.id)

This way, you'll filter your objects on all for all of the cards. Previously you were returning (as you already said) every tradelist that had a card among your selection.

Upvotes: 2

Related Questions