2011-01-21 5 views
10

Je me demandais s'il y avait un moyen d'utiliser filter() de Django sur les ensembles de requêtes en utilisant une propriété python générée dynamiquement en utilisant property(). J'ai first_name et last_name de chaque utilisateur, et je veux filtrer en fonction de leur nom concaténé first_name last_name. (La raison derrière cela est que quand je fais saisie semi-automatique, je recherche pour voir si la requête correspond le prénom, le nom, ou une partie de l'enchaînement. Je veux John S correspondre John Smith, par exemple.Requête django basée sur la propriété dynamique()

J'ai créé une propriété de name:.

def _get_name(self): 
    return self.first_name + " " + self.last_name 
    name = property(_get_name) 

de cette façon, je peux appeler user.name pour obtenir le nom concaténés

Cependant, si j'essaie de faire User.objects.filter(name__istartswith=query) je reçois l'erreur Cannot resolve keyword 'name' into field.

Des idées sur comment faire cela? Dois-je créer un autre champ dans la base de données pour stocker le nom complet?

Répondre

9

filter() fonctionne au niveau de la base de données (il écrit réellement SQL), donc il ne sera pas possible de l'utiliser pour des requêtes basées sur votre code python (dynamic property in your question).

Ceci est une réponse mis en place de nombreuses autres réponses dans ce département:)

+0

Intéressant, non vous ne pouvez pas filtrer sur les propriétés, mais vous pouvez grouper les champs existants et filtrer par le groupe [répondre ci-dessous] (http://stackoverflow.com/a/19040011/3999748) – Persijn

0

pense pas qu'il soit possible django pour filtrer sur les propriétés qui ne présente pas comme une base de données déposée, mais ce que vous pouvez faire pour faire de la recherche autocomplete fraîche est quelque chose comme ceci:

if ' ' in query: 
    query = query.split() 
    search_results = list(chain(User.objects.filter(first_name__icontains=query[0],last_name__icontains=query[1]), 
            User.objects.filter(first_name__icontains=query[1],last_name__icontains=query[0]))) 
else: 
    search_results = User.objects.filter(Q(first_name__icontains=query)| Q(last_name__icontains=query)) 

Ce code donne à l'utilisateur de votre système une flexibilité pour commencer à taper le prénom ou le nom de famille et l'utilisateur vous sera reconnaissant de l'avoir autorisé.

+0

pensée à ce sujet, mais il y a des problèmes. Certaines personnes peuvent avoir des espaces dans leur prénom ou leur nom de famille, et donc le diviser par l'espace peut être brisé. Sinon, cependant, je l'ai déjà chercher soit le prénom ou le nom de famille. J'ai mis en place un signal pour remplir le champ de nom lors de la création, donc, cela fonctionne maintenant. Merci. – munchybunch

12

La réponse acceptée est pas tout à fait vrai.

Pour de nombreux cas, vous pouvez remplacer get() dans le gestionnaire de modèle à pop propriétés dynamiques des arguments de mots clés, puis ajoutez les attributs réels que vous souhaitez interroger contre dans le dictionnaire des arguments de mot-clé kwargs. Veillez à renvoyer un super afin que tous les appels get() réguliers renvoient le résultat attendu.

Je ne colle que ma propre solution, mais pour le __startswith et d'autres requêtes conditionnelles, vous pouvez ajouter une certaine logique à split le trait de soulignement double et gérer de manière appropriée.

Voici était mon travail autour pour permettre l'interrogation par une propriété dynamique:

class BorrowerManager(models.Manager): 
    def get(self, *args, **kwargs): 
     full_name = kwargs.pop('full_name', None) 
     # Override #1) Query by dynamic property 'full_name' 
     if full_name: 
      names = full_name_to_dict(full_name) 
      kwargs = dict(kwargs.items() + names.items()) 
     return super(BorrowerManager, self).get(*args, **kwargs) 

En models.py:

class Borrower(models.Model): 
    objects = BorrowerManager() 

    first_name = models.CharField(null=False, max_length=30) 
    middle_name = models.CharField(null=True, max_length=30) 
    last_name = models.CharField(null=False, max_length=30) 
    created = models.DateField(auto_now_add=True) 

En utils.py (pour la contexte):

def full_name_to_dict(full_name): 
    ret = dict() 
    values = full_name.split(' ') 
    if len(values) == 1: 
     raise NotImplementedError("Not enough names to unpack from full_name") 
    elif len(values) == 2: 
     ret['first_name'] = values[0] 
     ret['middle_name'] = None 
     ret['last_name'] = values[1] 
     return ret 
    elif len(values) >= 3: 
     ret['first_name'] = values[0] 
     ret['middle_name'] = values[1:len(values)-1] 
     ret['last_name'] = values[len(values)-1] 
     return ret 
    raise NotImplementedError("Error unpacking full_name to first, middle, last names") 
+2

Bon! btw vous pouvez utiliser quelque chose comme 'isinstance (getattr (self.model, attr), propriété)' pour vérifier si l'argument est une propriété ou non. Je fais quelque chose de la même manière .. –

+1

'kwargs = dict (kwargs.items() + names.items())' peut être écrit comme 'kwargs.update (noms)' – thebjorn

+1

cela semble ne plus fonctionner dans Django 1,9 –

3

J'ai eu un problème similaire et cherchait une solution.En prenant pour acquis qu'un moteur de recherche serait la meilleure option (par exemple django-haystack avec Elasticsearch), voilà comment je mettre en œuvre un code pour vos besoins en utilisant uniquement le Django ORM (vous pouvez remplacer icontains avec istartswith):

from django.db.models import Value 
from django.db.models.functions import Concat 

queryset = User.objects.annotate(full_name=Concat('first_name', Value(' '), 'last_name') 
return queryset.filter(full_name__icontains=value) 

Dans mon cas, je ne savais pas si l'utilisateur allait insérer 'first_namelast_name' ou viceversa, donc j'ai utilisé le code suivant.

from django.db.models import Q, Value 
from django.db.models.functions import Concat 

queryset = User.objects.annotate(first_last=Concat('first_name', Value(' '), 'last_name'), last_first=Concat('last_name', Value(' '), 'first_name')) 
return queryset.filter(Q(first_last__icontains=value) | Q(last_first__icontains=value)) 

Avec Django < 1.8, vous auriez probablement besoin de recourir à extra avec la fonction SQL CONCAT, quelque chose comme ce qui suit:

queryset.extra(where=['UPPER(CONCAT("auth_user"."last_name", \' \', "auth_user"."first_name")) LIKE UPPER(%s) OR UPPER(CONCAT("auth_user"."first_name", \' \', "auth_user"."last_name")) LIKE UPPER(%s)'], params=['%'+value+'%', '%'+value+'%']) 
Questions connexes