Фильтр списка администраторов, сгруппированный по первой букве и расширенной группе
У меня есть исходный код модели с внешним ключом для размещения модели. На странице списка изменений исходной модели мне бы хотелось, чтобы фильтр списка отображал все буквы алфавита, а при нажатии на "A" отображались все названия мест, начинающиеся с "A", которые можно щелкнуть, чтобы отобразить только те источники в списке, у которых есть это место в поле fk.
Упрощенные модели:
class Source(models.Model):
place = models.ForeignKey(Place, null=True, place=True, on_delete=models.SET_NULL
class Place(models.Model):
name = models.CharField(max_length = 255, primary_key=True)
На данный момент у меня есть вот это:
class PlaceFilter(admin.SimpleListFilter):
title = 'first letter of place name'
parameter_name = 'letter'
def lookups(self, request, model_admin):
qs = model_admin.get_queryset(request)
options = qs.values_list(Substr('place__name', 1, 1), Substr('place__name', 1, 1)) \
.annotate(fl=Count(Substr('place__name', 1, 1))).distinct() \
.order_by(Substr('place__name', 1, 1))
options = [(i, f'{j} ({k})') for i,j,k in options]
return options
def queryset(self, request, queryset):
if self.value():
return queryset.filter(place__name__istartswith=self.value())
В котором отображаются все буквы, но при нажатии на букву список содержит все источники с указанием места, начинающегося с этой буквы. Кроме того, я нахожусь в Нидерландах, и названия некоторых мест здесь начинаются на "с", "т", иногда с дефисом, иногда без него. Эти имена должны быть сгруппированы не по начальной одинарной кавычке, а (обычно) по 4-й букве, т.е. "s-Gravenhage" должно быть под "G".
Я не знаю, нужен ли мне здесь счетчик, я бы предпочел, чтобы он не всегда отображался в списке, но если я попытаюсь использовать встроенную функцию подсчета в фильтрах списка, я продолжу получать ошибки примерно такого типа:
ValueError at /admin/importer/source/
Column aliases cannot contain whitespace characters, quotation marks, semicolons, or SQL comments.
Я перепробовал несколько разных вариантов, даже отфильтровал имена, прежде чем перейти к методу de queryset().
ОБНОВЛЕНИЕ:
Теперь, используя приведенный ниже код, я получаю список букв алфавита, и при нажатии на одну из них появляются названия мест, начинающиеся на эту букву, и они корректно отображаются. Но я по-прежнему получаю сообщение об ошибке в отношении псевдонимов столбцов, когда нажимаю "показать количество"...
class PlaceFilter(admin.SimpleListFilter):
title = 'first letter of place name'
parameter_name = 'letter'
def lookups(self, request, model_admin):
qs = model_admin.get_queryset(request)
letters = list(string.ascii_uppercase)
options = [(letter, letter) for letter in letters]
if val := self.value():
print(val)
if len(val) == 1:
sub_options = list(qs.values_list('place__name', 'place__name').distinct() \
.filter(place__name__iregex=rf"^('s-|'s )?{val}") \
.order_by('place__name'))
val_index = options.index((val, val))
index = val_index + 1
for option in sub_options:
options.insert(index, option)
index += 1
return list(options)
def queryset(self, request, queryset):
if self.value() and len(self.value()) > 1:
return queryset.filter(place__name=self.value())
Я решил эту проблему с помощью этого кода. Теперь, даже когда я нажимаю на название места, другие названия с той же первой буквой остаются открытыми. И счетчик тоже работает :)
class PlaceFilter(admin.SimpleListFilter):
title = 'first letter of place name'
parameter_name = 'letter'
def get_sub_options(self, letter, qs):
return list(qs.values_list('place__name', 'place__name').distinct() \
.filter(place__name__iregex=rf"^('s-|'s |'t )?{letter}") \
.order_by('place__name'))
def lookups(self, request, model_admin):
qs = model_admin.get_queryset(request)
letters = list(string.ascii_uppercase)
options = [(letter, letter) for letter in letters]
if self.value():
if len(self.value()) == 1:
first_letter = self.value()
sub_options = self.get_sub_options(first_letter, qs)
val_index = options.index((self.value(), self.value()))
index = val_index + 1
for option in sub_options:
options.insert(index, option)
index += 1
elif len(self.value()) > 1:
first_letter = re.sub(fr"^('s-|'s |'t )", "",self.value())[0]
sub_options = self.get_sub_options(first_letter, qs)
val_index = options.index((first_letter, first_letter))
index = val_index + 1
for option in sub_options:
options.insert(index, option)
index += 1
return list(options)
def queryset(self, request, queryset):
if self.value():
if len(self.value()) == 1:
return queryset.filter(place__name__istartswith=self.value())
if len(self.value()) > 1:
return queryset.filter(place__name=self.value())