tags:

views:

44

answers:

1

Let's say I've defined this model:

class Identifier(models.Model):
    user = models.ForeignKey(User)
    key = models.CharField(max_length=64)
    value = models.CharField(max_length=255)

Each user will have multiple identifiers, each with a key and a value. I am 100% sure I want to keep the design like this, there are external reasons why I'm doing it that I won't go through here, so I'm not interested in changing this.

I'd like to develop a function of this sort:

def get_users_by_identifiers(**kwargs):
    # something goes here
    return users

The function will return all users that have one of the key=value pairs specified in **kwargs. Here's an example usage:

get_users_by_identifiers(a=1, b=2)

This should return all users for whom a=1 or b=2. I've noticed that the way I've set this up, this amounts to a disjunctive normal form...the SQL query would be something like:

SELECT DISTINCT(user_id) FROM app_identifier 
    WHERE (key = "a" AND value = "1") OR (key = "b" AND value = "2") ...

I feel like there's got to be some elegant way to take the **kwargs input and do a Django filter on it, in just 1-2 lines, to produce this result. I'm new to Django though, so I'm just not sure how to do it. Here's my function now, and I'm completely sure it's not the best way to do it :)

def get_users_by_identifiers(**identifiers):
    users = []
    for key, value in identifiers.items():
        for identifier in Identifier.objects.filter(key=key, value=value):
            if not identifier.user in users:
                users.append(identifier.user)

    return users

Any ideas? :)

Thanks!

A: 
def get_users_by_identifiers(**kwargs):
    q = reduce(operator.or_, Q(identifier__key=k, identifier__value=v)
        for (k, v) in kwargs.iteritems())
    return User.objects.filter(q)
Ignacio Vazquez-Abrams
Thanks! Great way to do it. I had to slightly tweak it though. As you wrote it, for me it resulted in "SyntaxError: Generator expression must be parenthesized if not sole argument". I changed the sceond line to read "q = reduce(operator.or_, (Q(identifier__key=k, identifier__value=v) for (k, v) in kwargs.iteritems()))" and it worked perfectly.
Mike
Actually, one more small modification required to make it run exactly the way I specified above. You need to change the last line to read "return User.objects.filter(q).distinct()"
Mike