DjangoGuy DjangoGuy - 12 days ago 6
Python Question

filter objects using other model's objects

I have a Parent, Name, and Kid Models :

models.py

class Parent(models.Model):
title = models.CharField(max_length=250)
address = models.CharField(max_length=250)


class Name(models.Model):
title = models.CharField(max_length=250)


class Kid(models.Model):
family = models.ForeignKey(Parent)
name = models.ForeignKey(Name)
age = models.IntegerField()
city = models.CharField(max_length=250)


and I have a view function to display Parent objects only if Kid objects related to the Parent are all in the dictionary:

views.py

def index(request):
patterns = [
{'name__title': 'samy', 'age__lt': 15, 'city': 'paris'},
{'name__title': 'sally', 'age__gt': 20, 'city': 'london'}
]
filter_q = reduce(operator.or_, map(lambda p: Q(**p), patterns))
qs = Kid.objects.filter(filter_q).values_list('id', 'family_id')
family_ids = set()
child_ids = list()
for child_id, family_id in qs:
family_ids.add(family_id)
child_ids.append(child_id)
incomplete_family_ids = set(Kid.objects.exclude(id__in=child_ids).filter(family_id__in=family_ids).values_list('family_id', flat=True).distinct())
complete_family_ids = family_ids - incomplete_family_ids
parents = Parent.objects.filter(id__in=complete_family_ids)
template = 'index.html'
context = {'parents': parents}
return render(request, template, context)


What if I want to replace the pattern dictionary with models objects, like:

class Pattern(models.Model):
title = models.CharField(max_length=250)


class PatternItems(models.Model):
name = models.ForeignKey(Name)
age = models.integer()
city = models.CharField(max_length=250)
pattern = models.ForeignKey(Pattern)


so instead of the dictionary. I wonder if its possible to select a pattern from Pattern models to display Parent objects if all it's related Kid objects are in PatternItems using a Pattern object.

the whole idea is to make the filter dynamic and allow users to make their own filters .

Help?

Answer

You can replace this piece of code:

patterns = [
    {'name__title': 'samy', 'age__lt': 15, 'city': 'paris'},
    {'name__title': 'sally', 'age__gt': 20, 'city': 'london'}
]
filter_q = reduce(operator.or_, map(lambda p: Q(**p), patterns))

with a loop in which you add as many filters as you like:

# init patterns as a list of PatternItems
patterns = [ PatternItem(name=Name(title='samy'), age=15, city='paris'),
             PatternItem(name=Name(title='sally'), age=20, city='london'),
           ]
# iterate through the list normally
filters = []
for pt_item in patterns:
    filters.append(Q(name__title=pt_item.name.title) & 
                    Q(age__lt=pt_item.age) & Q(city=pt_item.city))

filter_q = filters[0]
for f in filters[1:]:
    filter_q |= f

Another way of getting patterns could be evaluating a QuerySet, e.g. using filter():

# any_column__gte and value here are just examples
patterns = PatternItem.objects.filter(any_column__gte=value)
# iterate through the patterns using .all()
filters = []
for pt_item in patterns.all():
    ...

The only problem with this approach is that you have to use the same comparison (i.e. age__lt) for each PatternItem in the list.

Please note: I'm using loops instead of the reduce() function because it doesn't exist in Python 3.

Comments