views.py 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. from django.contrib import messages
  2. from django.db.models import ObjectDoesNotExist
  3. from django.shortcuts import redirect
  4. from django.urls import reverse
  5. from django.utils.translation import gettext, gettext_lazy as _
  6. from ...admin.views import generic
  7. from ..models import Theme, Css
  8. from .css import move_css_down, move_css_up
  9. from .forms import CssEditorForm, ThemeForm, UploadCssForm, UploadMediaForm
  10. class ThemeAdmin(generic.AdminBaseMixin):
  11. root_link = "misago:admin:appearance:themes:index"
  12. model = Theme
  13. form = ThemeForm
  14. templates_dir = "misago/admin/themes"
  15. message_404 = _("Requested theme does not exist.")
  16. class ThemesList(ThemeAdmin, generic.ListView):
  17. pass
  18. class NewTheme(ThemeAdmin, generic.ModelFormView):
  19. message_submit = _('New theme "%(name)s" has been saved.')
  20. def initialize_form(self, form, request, _):
  21. if request.method == "POST":
  22. return form(request.POST, request.FILES)
  23. try:
  24. initial = {"parent": int(request.GET.get("parent"))}
  25. except (TypeError, ValueError):
  26. initial = {}
  27. return form(initial=initial)
  28. class EditTheme(ThemeAdmin, generic.ModelFormView):
  29. message_submit = _('Theme "%(name)s" has been updated.')
  30. def check_permissions(self, request, target):
  31. if target.is_default:
  32. return gettext("Default theme can't be edited.")
  33. class DeleteTheme(ThemeAdmin, generic.ModelFormView):
  34. message_submit = _('Theme "%(name)s" has been deleted.')
  35. def check_permissions(self, request, target):
  36. if target.is_default:
  37. return gettext("Default theme can't be deleted.")
  38. class ActivateTheme(ThemeAdmin, generic.ButtonView):
  39. def button_action(self, request, target):
  40. set_theme_as_active(request, target)
  41. message = gettext('Active theme has been changed to "%(name)s".')
  42. messages.success(request, message % {"name": target})
  43. def set_theme_as_active(request, theme):
  44. Theme.objects.update(is_active=False)
  45. Theme.objects.filter(pk=theme.pk).update(is_active=True)
  46. class ThemeAssetsAdmin(ThemeAdmin):
  47. def check_permissions(self, request, theme):
  48. if theme.is_default:
  49. return gettext("Default theme assets can't be edited.")
  50. def redirect_to_theme_assets(self, theme):
  51. return redirect("misago:admin:appearance:themes:assets", pk=theme.pk)
  52. class ThemeAssets(ThemeAssetsAdmin, generic.TargetedView):
  53. template = "assets/list.html"
  54. def real_dispatch(self, request, theme):
  55. return self.render(request, {"theme": theme})
  56. class ThemeAssetsActionAdmin(ThemeAssetsAdmin):
  57. def real_dispatch(self, request, theme):
  58. if request.method == "POST":
  59. self.action(request, theme)
  60. return self.redirect_to_theme_assets(theme)
  61. def action(self, request, theme):
  62. raise NotImplementedError(
  63. "action method must be implemented in inheriting class"
  64. )
  65. class UploadThemeAssets(ThemeAssetsActionAdmin, generic.TargetedView):
  66. message_partial_success = _(
  67. "Some css files could not have been added to the theme."
  68. )
  69. message_submit = None
  70. form = None
  71. def action(self, request, theme):
  72. form = self.form( # pylint: disable=not-callable
  73. request.POST, request.FILES, instance=theme
  74. )
  75. if not form.is_valid():
  76. if form.cleaned_data.get("assets"):
  77. messages.info(request, self.message_partial_success)
  78. for error in form.errors["assets"]:
  79. messages.error(request, error)
  80. if form.cleaned_data.get("assets"):
  81. form.save()
  82. messages.success(request, self.message_success)
  83. class UploadThemeCss(UploadThemeAssets):
  84. message_success = _("New CSS files have been added to the theme.")
  85. form = UploadCssForm
  86. class UploadThemeMedia(UploadThemeAssets):
  87. message_success = _("New media files have been added to the theme.")
  88. form = UploadMediaForm
  89. class DeleteThemeAssets(ThemeAssetsActionAdmin, generic.TargetedView):
  90. message_submit = None
  91. queryset_attr = None
  92. def action(self, request, theme):
  93. items = self.clean_items_list(request)
  94. if items:
  95. queryset = getattr(theme, self.queryset_attr)
  96. for item in items:
  97. self.delete_asset(queryset, item)
  98. messages.success(request, self.message_submit)
  99. def clean_items_list(self, request):
  100. try:
  101. return {int(i) for i in request.POST.getlist("item")[:20]}
  102. except (ValueError, TypeError):
  103. pass
  104. def delete_asset(self, queryset, item):
  105. try:
  106. queryset.get(pk=item).delete()
  107. except ObjectDoesNotExist:
  108. pass
  109. class DeleteThemeCss(DeleteThemeAssets):
  110. message_submit = _("Selected CSS files have been deleted.")
  111. queryset_attr = "css"
  112. class DeleteThemeMedia(DeleteThemeAssets):
  113. message_submit = _("Selected media have been deleted.")
  114. queryset_attr = "media"
  115. class ThemeCssAdmin(ThemeAssetsAdmin, generic.TargetedView):
  116. def wrapped_dispatch(self, request, pk, css_pk=None):
  117. theme = self.get_target_or_none(request, {"pk": pk})
  118. if not theme:
  119. messages.error(request, self.message_404)
  120. return redirect(self.root_link)
  121. error = self.check_permissions( # pylint: disable=assignment-from-no-return
  122. request, theme
  123. )
  124. if error:
  125. messages.error(request, error)
  126. return redirect(self.root_link)
  127. css = self.get_theme_css_or_none(theme, css_pk)
  128. if css_pk and not css:
  129. css_error = gettext("Requested CSS could not be found in the theme.")
  130. messages.error(request, css_error)
  131. return self.redirect_to_theme_assets(theme)
  132. return self.real_dispatch(request, theme, css)
  133. def get_theme_css_or_none(self, theme, css_pk):
  134. if not css_pk:
  135. return None
  136. try:
  137. return theme.css.select_for_update().get(pk=css_pk)
  138. except ObjectDoesNotExist:
  139. return None
  140. class MoveThemeCssUp(ThemeCssAdmin):
  141. def real_dispatch(self, request, theme, css):
  142. if request.method == "POST" and move_css_up(theme, css):
  143. messages.success(request, gettext('"%s" was moved up.') % css)
  144. return self.redirect_to_theme_assets(theme)
  145. class MoveThemeCssDown(ThemeCssAdmin):
  146. def real_dispatch(self, request, theme, css):
  147. if request.method == "POST" and move_css_down(theme, css):
  148. messages.success(request, gettext('"%s" was moved down.') % css)
  149. return self.redirect_to_theme_assets(theme)
  150. class ThemeCssFormAdmin(ThemeCssAdmin, generic.ModelFormView):
  151. def real_dispatch(self, request, theme, css=None):
  152. form = self.initialize_form(self.form, request, theme, css)
  153. if request.method == "POST" and form.is_valid():
  154. response = self.handle_form( # pylint: disable=assignment-from-no-return
  155. form, request, theme, css
  156. )
  157. if response:
  158. return response
  159. if "stay" in request.POST:
  160. return self.redirect_to_edit_form(theme, form.instance)
  161. return self.redirect_to_theme_assets(theme)
  162. return self.render(request, {"form": form, "theme": theme, "target": css})
  163. def handle_form(self, form, request, theme, css):
  164. form.save()
  165. messages.success(request, self.message_submit % {"name": css.name})
  166. def redirect_to_edit_form(self, theme, css):
  167. return redirect(
  168. "misago:admin:appearance:themes:edit-css", pk=theme.pk, css_pk=css.pk
  169. )
  170. class NewThemeCss(ThemeCssFormAdmin):
  171. message_submit = _('New CSS "%(name)s" has been saved.')
  172. form = CssEditorForm
  173. template = "assets/css-editor.html"
  174. def get_theme_css_or_none(self, theme, _):
  175. return Css(theme=theme)
  176. def initialize_form(self, form, request, theme, css):
  177. if request.method == "POST":
  178. return form(request.POST, instance=css)
  179. return form(instance=css)
  180. class EditThemeCss(NewThemeCss):
  181. message_submit = _('CSS "%(name)s" has been updated.')
  182. def get_theme_css_or_none(self, theme, css_pk):
  183. try:
  184. return theme.css.get(pk=css_pk, url__isnull=True)
  185. except ObjectDoesNotExist:
  186. return None
  187. def initialize_form(self, form, request, theme, css):
  188. if request.method == "POST":
  189. return form(request.POST, instance=css)
  190. initial_data = {"source": css.source_file.read()}
  191. return form(instance=css, initial=initial_data)