admin.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. from django.contrib import admin
  2. from django.utils.html import format_html
  3. from landing.models import (
  4. FAQmodel,
  5. LandingMainPageSettings,
  6. LandingPage,
  7. LandingTeamMember,
  8. )
  9. from landing.utils import create_pages_clean_admin
  10. @admin.register(LandingPage)
  11. class LandingPageAdmin(admin.ModelAdmin):
  12. from landing.admin_rich_text import LandingPageAdminForm
  13. form = LandingPageAdminForm
  14. list_display = ('title', 'slug')
  15. actions = [create_pages_clean_admin]
  16. @admin.register(FAQmodel)
  17. class FAQAdmin(admin.ModelAdmin):
  18. list_display = ('question',)
  19. @admin.register(LandingTeamMember)
  20. class LandingTeamMemberAdmin(admin.ModelAdmin):
  21. list_display = (
  22. 'display_name',
  23. 'caption',
  24. 'sort_order',
  25. 'is_active',
  26. )
  27. list_filter = ('is_active',)
  28. autocomplete_fields = ('user',)
  29. fields = (
  30. 'user',
  31. 'avatar_preview',
  32. 'caption',
  33. 'sort_order',
  34. 'is_active',
  35. )
  36. readonly_fields = ('avatar_preview',)
  37. def avatar_preview(self, obj):
  38. if not obj or not obj.avatar_src:
  39. return 'Аватар не задан'
  40. return format_html(
  41. '<img src="{}" style="width: 48px; height: 48px; object-fit: cover; border-radius: 50%;" />',
  42. obj.avatar_src,
  43. )
  44. avatar_preview.short_description = 'Аватар'
  45. @admin.register(LandingMainPageSettings)
  46. class LandingMainPageSettingsAdmin(admin.ModelAdmin):
  47. list_display = (
  48. 'id',
  49. 'title',
  50. 'hero_title',
  51. 'is_active',
  52. 'team_count',
  53. 'updated_at',
  54. )
  55. list_filter = ('is_active',)
  56. fieldsets = (
  57. ('Основные настройки', {
  58. 'fields': (
  59. 'title',
  60. 'is_active',
  61. )
  62. }),
  63. ('Шапка лендинга', {
  64. 'fields': (
  65. 'hero_title',
  66. 'hero_text',
  67. 'hero_image',
  68. 'hero_image_preview',
  69. )
  70. }),
  71. ('Раздел услуг', {
  72. 'fields': (
  73. 'services_title',
  74. 'services_text',
  75. )
  76. }),
  77. ('Раздел "О компании"', {
  78. 'fields': (
  79. 'about_title',
  80. 'about_text',
  81. 'video_url',
  82. )
  83. }),
  84. ('Дополнительный раздел с изображением', {
  85. 'fields': (
  86. 'extra_section_title',
  87. 'image_block_title',
  88. 'image_block_text',
  89. 'image_block_image',
  90. 'image_block_image_preview',
  91. )
  92. }),
  93. ('Раздел FAQ', {
  94. 'fields': (
  95. 'faq_csv',
  96. 'faq_csv_help',
  97. )
  98. }),
  99. ('Раздел команды', {
  100. 'fields': (
  101. 'team_title',
  102. 'team_text',
  103. )
  104. }),
  105. )
  106. readonly_fields = (
  107. 'hero_image_preview',
  108. 'image_block_image_preview',
  109. 'faq_csv_help',
  110. )
  111. def hero_image_preview(self, obj):
  112. if not obj or not obj.hero_image:
  113. return 'Изображение не выбрано'
  114. return format_html(
  115. '<img src="{}" style="max-width: 360px; max-height: 180px; object-fit: cover; border-radius: 8px;" />',
  116. obj.hero_image.url,
  117. )
  118. hero_image_preview.short_description = 'Предпросмотр изображения шапки'
  119. def image_block_image_preview(self, obj):
  120. if not obj or not obj.image_block_image:
  121. return 'Изображение не выбрано'
  122. return format_html(
  123. '<img src="{}" style="max-width: 360px; max-height: 180px; object-fit: cover; border-radius: 8px;" />',
  124. obj.image_block_image.url,
  125. )
  126. image_block_image_preview.short_description = 'Предпросмотр изображения блока'
  127. def faq_csv_help(self, obj):
  128. return format_html(
  129. '<div style="max-width: 720px; line-height: 1.55;">'
  130. '<strong>Формат CSV:</strong> <code>question;answer;keywords</code><br>'
  131. 'Первая строка может быть заголовком. Поле <code>keywords</code> необязательно. '
  132. 'В нем можно перечислить слова для поиска ответа через запятую или точку с запятой. '
  133. 'Если файл не загружен или подходящий ответ не найден, раздел FAQ обратится к нейросети.'
  134. '</div>'
  135. )
  136. faq_csv_help.short_description = 'Справка по CSV для FAQ'
  137. def has_add_permission(self, request):
  138. if LandingMainPageSettings.objects.exists():
  139. return False
  140. return super().has_add_permission(request)
  141. def team_count(self, obj):
  142. return LandingTeamMember.objects.filter(is_active=True).count()
  143. team_count.short_description = 'Активных участников команды'