Поиск цепочки через набор запросов

#python #django #django-models

#python #django #django-модели

Вопрос:

У меня есть две модели: City , и его псевдоним CityAlias . CityAlias Модель содержит все имена в City , плюс псевдонимы. Я хочу, чтобы всякий City раз, когда выполняется поиск name , CityAlias модель должна запрашиваться. Это то, что я придумал:

 class CityQuerySet(models.QuerySet):
    """ If City is searched by name, search it in CityAlias """
    def _search_name_in_alias(self, args, kwargs):
        for q in args:
            if not isinstance(q, models.Q): continue
            for i, child in enumerate(q.children):
                # q.children is a list of tuples of queries:
                # [('name__iexact', 'calcutta'), ('state__icontains', 'bengal')]
                if child[0].startswith('name'):
                    q.children[i] = ('aliases__%s' % child[0], child[1])

        for filter_name in kwargs:
            if filter_name.startswith('name'):
                kwargs['aliases__%s' % filter_name] = kwargs.pop(filter_name)

    def _filter_or_exclude(self, negate, *args, **kwargs):
        # handles 'get', 'filter' and 'exclude' methods
        self._search_name_in_alias(args=args, kwargs=kwargs)
        return super(CityQuerySet, self)._filter_or_exclude(negate, *args, **kwargs)


class City(models.Model):
    name = models.CharField(max_length=255, db_index=True)
    state = models.ForeignKey(State, related_name='cities')
    objects = CityQuerySet.as_manager()

class CityAlias(models.Model):
    name = models.CharField(max_length=255, db_index=True)
    city = models.ForeignKey(City, related_name='aliases')
  

Пример: Kolkata будет иметь запись в City модели, и в модели будет две записи CityAlias : Kolkata и Calcutta . Вышесказанное QuerySet позволяет использовать поиск по name полю.
Таким образом, следующие два запроса вернут одну и ту же запись:

 City.objects.get(name='Kolkata')     # <City: Kolkata>
City.objects.get(name__iexact='calcutta')    # <City: Kolkata>
  

Пока все хорошо. Но проблема возникает, когда City is a ForeignKey в какой-то другой модели:

 class Trip(models.Model):
    destination = models.ForeignKey(City)
    # some other fields....

Trip.objects.filter(destination__name='Kolkata').count()   # some non-zero number
Trip.objects.filter(destination__name='Calcutta').count()  # always returns zero
  

Django внутренне обрабатывает эти соединения по-разному и не вызывает get_queryset метод City менеджера. Альтернативой является вызов приведенного выше запроса следующим образом:

 Trip.objects.filter(destination=City.objects.get(name='Calcutta'))
  

Мой вопрос в том, могу ли я что-то сделать, чтобы, несмотря на City поиск модели name , она всегда выполняла поиск в CityAlias таблице?
Или есть другой лучший способ реализовать требуемую мне функциональность?

Комментарии:

1. Вы просто усложняете свою проблему? Можете ли вы просто использовать Trip.destination в качестве внешнего ключа для cityalias?

2. В City модели также будут другие поля (например, геолокация).. таким образом, это означало бы использование CityAlias при фильтрации по имени и City при применении других фильтров.

3. Пробовал это: Trip.objects.filter(назначение__aliases__name=’Calcutta’).count()

Ответ №1:

Я думаю, что лучше (и более pythonic) быть явным в том, что вы запрашиваете повсюду, вместо того, чтобы пытаться творить чудеса в менеджере и, таким образом:

 City.objects.get(aliases__name__iexact='calcutta') # side note: this can return many (same in original) so you need to catch that
  

И:

 Trip.objects.filter(destination__aliases__name='Calcutta').count()
  

Комментарии:

1. Если возможно «волшебное» решение, почему бы и нет! Это решение — это то, чего я пытался избежать, но, благодаря, я вернулся к этому сейчас..

2. Почему бы и нет? Потому что это усложнит понимание и поддержку кода. Сохраняйте простоту и работайте с вашей структурой, а не искажайте ее. Хорошее программное обеспечение предназначено для решения бизнес-задач, а не для демонстрации трюков.

Ответ №2:

Я пытался использовать пользовательские запросы, но, по-видимому, вы не можете добавить таблицу в список соединений. (Ну, вы могли бы добавить дополнительный ({«таблица»: …}) в менеджере модели, но это не элегантное решение).

Итак, я бы предложил вам:

1) Всегда сохраняйте свой «основной / предпочтительный» город имени также как CityAlias. Таким образом, метаданные города будут находиться в City… но вся информация об именовании будет в CityAlias. (и, возможно, измените имена)

Таким образом, все запросы будут выполняться в этой таблице. У вас может быть логическое значение, чтобы отметить, какой экземпляр является исходным / предпочтительным.

 class City(models.Model):
    state = models.ForeignKey(State, related_name='cities')
    [...]

class CityAlias(models.Model):
    city = models.ForeignKey(City, related_name='aliases')
    name = models.CharField(max_length=255, db_index=True)
  

2) Если вы думаете о переводах… Вы думали о приложении django-modeltranslation?

В этом случае это создало бы поле для каждого языка, и это всегда было бы лучше, чем иметь соединение.

3) Или, если вы используете PostgreSQL и думаете о «разных переводах для одного и того же названия города» (и я думаю о транслитерациях с греческого или русского языка), возможно, вы могли бы использовать словари PostgreSQL, триграммы со сходствами и т. Д. Или даже в этом случае 1-й подход.

Комментарии:

1. 1) Логическое значение для предпочтительного поля является таким же (и менее эффективным), как наличие этого поля в самой модели города. Все поисковые запросы в модели CityAlias — это то, чего именно хотят достичь, и никто больше не беспокоится о таблице псевдонимов. 2) На самом деле не ищу переводы 3) взгляну на них, но не уверен, как они помогут..

Ответ №3:

Говоря о простоте. Почему бы просто не предоставить модели города поле символа ‘CityAlias’, которое содержит строку? Если я правильно понимаю ваш вопрос, это самое простое решение, если вам нужен только один псевдоним для каждого города. Мне просто кажется, что вы усложняете простую проблему.

 class City(models.Model):
    name = models.CharField(max_length=255, db_index=True)
    state = models.ForeignKey(State, related_name='cities')
    alias = models.CharField(max_length=255)

c = City.objects.get(alias='Kolkata')

>>>c.name
Calcutta
>>>c.alias
Kolkata
  

Комментарии:

1. За исключением того, что a City может иметь несколько псевдонимов… и я хочу, чтобы набор запросов возвращал тот же набор запросов, если модель ищется по любому из псевдонимов (или оригинальному имени). City.objects.get(name=x) должен возвращать тот же результат, если значение x равно Calcutta или Kolkata (или любой другой псевдоним, если он присутствует).

2. Ничего, что Город. CityAlias_set.all().filter(name__iexact=x) не подойдет?