147 lines
5.8 KiB
Python
147 lines
5.8 KiB
Python
# -*- coding: utf-8 -*-
|
||
from django import forms
|
||
from django.contrib import admin
|
||
from django.db import models
|
||
from django.forms import TextInput, Textarea
|
||
from django.urls import reverse
|
||
from django_select2.forms import Select2TagWidget
|
||
from web.models import TbContent
|
||
from web.add_function import safe_html_special_symbols
|
||
from cadpoint import settings
|
||
|
||
|
||
class AjaxCommaSeparatedSelect2TagWidget(Select2TagWidget):
|
||
"""
|
||
Select2-виджет для `taggit`.
|
||
|
||
Select2 в браузере работает с массивом значений, а `taggit` ждёт строку
|
||
с тегами через запятую. Поэтому здесь есть конвертация туда и обратно.
|
||
"""
|
||
|
||
def value_from_datadict(self, data, files, name):
|
||
# Select2 присылает список значений, а `taggit` ожидает строку вида
|
||
# "tag-one,tag two,tag-three".
|
||
values = super().value_from_datadict(data, files, name)
|
||
if isinstance(values, (list, tuple)):
|
||
return ",".join(values)
|
||
return values
|
||
|
||
def optgroups(self, name, value, attrs=None):
|
||
# При редактировании объекта нужно показать уже выбранные теги.
|
||
# При этом не тащим ВСЕ теги из базы — только те, что уже сохранены.
|
||
if isinstance(value, (list, tuple)):
|
||
raw_values = []
|
||
for item in value:
|
||
if not item:
|
||
continue
|
||
raw_values.extend(str(item).split(","))
|
||
else:
|
||
raw_values = str(value or "").split(",")
|
||
|
||
values = [item for item in raw_values if item]
|
||
selected = set(values)
|
||
subgroup = [
|
||
self.create_option(name, v, v, v in selected, i)
|
||
for i, v in enumerate(values)
|
||
]
|
||
return [(None, subgroup, 0)]
|
||
|
||
|
||
class AdminContentForm(forms.ModelForm):
|
||
class Meta:
|
||
model = TbContent
|
||
fields = '__all__'
|
||
|
||
class Media:
|
||
css = {
|
||
'all': ('css/admin-select2-theme.css',),
|
||
}
|
||
|
||
def __init__(self, *args, **kwargs):
|
||
super().__init__(*args, **kwargs)
|
||
# AJAX-виджет подгружает список тегов лениво, а здесь мы оставляем
|
||
# только уже выбранные значения, чтобы не тащить все теги из базы при
|
||
# открытии формы и не провоцировать лишние запросы к SQLite.
|
||
if self.is_bound:
|
||
if hasattr(self.data, 'getlist'):
|
||
tag_values = self.data.getlist('tags')
|
||
else:
|
||
raw_values = self.data.get('tags', [])
|
||
tag_values = raw_values if isinstance(raw_values, list) else [raw_values]
|
||
tag_choices = [(value, value) for value in tag_values if value]
|
||
elif self.instance.pk:
|
||
tag_choices = [
|
||
(name, name)
|
||
for name in self.instance.tags.order_by('name').values_list('name', flat=True)
|
||
]
|
||
else:
|
||
tag_choices = []
|
||
|
||
self.fields['tags'].widget = AjaxCommaSeparatedSelect2TagWidget(
|
||
attrs={
|
||
'data-ajax--url': reverse('web_tag_autocomplete'),
|
||
'data-ajax--cache': 'true',
|
||
'data-ajax--data-type': 'json',
|
||
'data-ajax--delay': settings.SELECT2_AJAX_DELAY_MS,
|
||
'data-token-separators': settings.SELECT2_TOKEN_SEPARATORS,
|
||
'data-minimum-input-length': settings.SELECT2_MINIMUM_INPUT_LENGTH,
|
||
},
|
||
choices=tag_choices,
|
||
)
|
||
|
||
|
||
# Register your models here.
|
||
class AdminContent(admin.ModelAdmin):
|
||
form = AdminContentForm
|
||
search_fields = ['szContentHead', 'szContentIntro', 'szContentBody',
|
||
'szContentKeywords', 'szContentDescription']
|
||
list_display = ('id', 'ContentHeadSafe', 'tag_list', 'bContentPublish', 'tdContentPublishUp')
|
||
list_display_links = ('id', 'ContentHeadSafe')
|
||
list_filter = ('bContentPublish', )
|
||
list_editable = ('bContentPublish', )
|
||
# настройка длины поля TextInput в админке
|
||
formfield_overrides = {
|
||
models.CharField: {'widget': TextInput(attrs={'size': '100%'})},
|
||
models.TextField: {'widget': Textarea(attrs={'rows': 14, 'cols': 120})},
|
||
}
|
||
# Настройка страницы редактирования
|
||
fieldsets = [
|
||
(None, {
|
||
'fields': ('bContentPublish', 'tdContentPublishUp')
|
||
}),
|
||
('Окончание публикации', {
|
||
'fields': ('tdContentPublishDown',),
|
||
'classes': ('collapse',),
|
||
}),
|
||
(None, {
|
||
'fields': ('tags', 'szContentHead', 'imgContentPreview', 'szContentIntro', 'szContentBody')
|
||
}),
|
||
('Типограф', {
|
||
'fields': ('bTypograf', ),
|
||
'classes': ('collapse',),
|
||
}),
|
||
('Поля для SEO', {
|
||
'fields': ('szContentSlug', 'szContentKeywords', 'szContentDescription', 'iContentHits'),
|
||
'classes': ('collapse', ),
|
||
}),
|
||
]
|
||
# exclude = ('', '', )
|
||
empty_value_display = u"<b style='color:red;'>—//—</b>"
|
||
actions_on_top = False
|
||
actions_on_bottom = False
|
||
|
||
def ContentHeadSafe(self, obj) -> str:
|
||
return safe_html_special_symbols(obj.szContentHead)
|
||
|
||
def get_queryset(self, request):
|
||
queryset = super().get_queryset(request)
|
||
if request.resolver_match and request.resolver_match.url_name == 'web_tbcontent_changelist':
|
||
return queryset.prefetch_related('tags')
|
||
return queryset
|
||
|
||
def tag_list(self, obj):
|
||
return u", ".join(o.name for o in obj.tags.all())
|
||
|
||
|
||
admin.site.register(TbContent, AdminContent)
|