#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) не подойдет?