123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364 |
- from django.contrib import messages
- from django.db.models import ObjectDoesNotExist
- from django.shortcuts import redirect
- from django.utils.translation import gettext, gettext_lazy as _
- from ...admin.views import generic
- from ..cache import clear_theme_cache
- from ..models import Theme, Css
- from .css import move_css_down, move_css_up
- from .forms import CssEditorForm, CssLinkForm, ThemeForm, UploadCssForm, UploadMediaForm
- from .tasks import build_single_theme_css, build_theme_css, update_remote_css_size
- class ThemeAdmin(generic.AdminBaseMixin):
- root_link = "misago:admin:appearance:themes:index"
- model = Theme
- form = ThemeForm
- templates_dir = "misago/admin/themes"
- message_404 = _("Requested theme does not exist.")
- class ThemesList(ThemeAdmin, generic.ListView):
- pass
- class NewTheme(ThemeAdmin, generic.ModelFormView):
- message_submit = _('New theme "%(name)s" has been saved.')
- def initialize_form(self, form, request, _):
- if request.method == "POST":
- return form(request.POST, request.FILES)
- try:
- initial = {"parent": int(request.GET.get("parent"))}
- except (TypeError, ValueError):
- initial = {}
- return form(initial=initial)
- class EditTheme(ThemeAdmin, generic.ModelFormView):
- message_submit = _('Theme "%(name)s" has been updated.')
- def check_permissions(self, request, target):
- if target.is_default:
- return gettext("Default theme can't be edited.")
- def handle_form(self, form, request, target):
- super().handle_form(form, request, target)
- if "parent" in form.changed_data:
- clear_theme_cache()
- class DeleteTheme(ThemeAdmin, generic.ButtonView):
- message_submit = _('Theme "%(name)s" has been deleted.')
- def check_permissions(self, request, target):
- if target.is_default:
- return gettext("Default theme can't be deleted.")
- if target.is_active:
- return gettext("Active theme can't be deleted.")
- if target.get_descendants().filter(is_active=True).exists():
- message = gettext(
- 'Theme "%(name)s" can\'t be deleted '
- "because one of its child themes is set as active."
- )
- return message % {"name": target}
- def button_action(self, request, target):
- for theme in reversed(target.get_descendants(include_self=True)):
- theme.delete()
- clear_theme_cache()
- messages.success(request, self.message_submit % {"name": target})
- class ActivateTheme(ThemeAdmin, generic.ButtonView):
- def button_action(self, request, target):
- set_theme_as_active(request, target)
- message = gettext('Active theme has been changed to "%(name)s".')
- messages.success(request, message % {"name": target})
- def set_theme_as_active(request, theme):
- Theme.objects.update(is_active=False)
- Theme.objects.filter(pk=theme.pk).update(is_active=True)
- clear_theme_cache()
- class ThemeAssetsAdmin(ThemeAdmin):
- def check_permissions(self, request, theme):
- if theme.is_default:
- return gettext("Default theme assets can't be edited.")
- def redirect_to_theme_assets(self, theme):
- return redirect("misago:admin:appearance:themes:assets", pk=theme.pk)
- class ThemeAssets(ThemeAssetsAdmin, generic.TargetedView):
- template = "assets/list.html"
- def real_dispatch(self, request, theme):
- return self.render(request, {"theme": theme})
- class ThemeAssetsActionAdmin(ThemeAssetsAdmin):
- def real_dispatch(self, request, theme):
- if request.method == "POST":
- self.action(request, theme)
- return self.redirect_to_theme_assets(theme)
- def action(self, request, theme):
- raise NotImplementedError(
- "action method must be implemented in inheriting class"
- )
- class UploadThemeAssets(ThemeAssetsActionAdmin, generic.TargetedView):
- message_partial_success = _(
- "Some css files could not have been added to the theme."
- )
- message_submit = None
- form = None
- def action(self, request, theme):
- form = self.form( # pylint: disable=not-callable
- request.POST, request.FILES, instance=theme
- )
- if not form.is_valid():
- if form.cleaned_data.get("assets"):
- messages.info(request, self.message_partial_success)
- for error in form.errors["assets"]:
- messages.error(request, error)
- if form.cleaned_data.get("assets"):
- form.save()
- build_theme_css.delay(theme.pk)
- messages.success(request, self.message_success)
- class UploadThemeCss(UploadThemeAssets):
- message_success = _("New CSS files have been added to the theme.")
- form = UploadCssForm
- class UploadThemeMedia(UploadThemeAssets):
- message_success = _("New media files have been added to the theme.")
- form = UploadMediaForm
- class DeleteThemeAssets(ThemeAssetsActionAdmin, generic.TargetedView):
- message_submit = None
- queryset_attr = None
- def action(self, request, theme):
- items = self.clean_items_list(request)
- if items:
- queryset = getattr(theme, self.queryset_attr)
- for item in items:
- self.delete_asset(queryset, item)
- messages.success(request, self.message_submit)
- def clean_items_list(self, request):
- try:
- return {int(i) for i in request.POST.getlist("item")[:20]}
- except (ValueError, TypeError):
- pass
- def delete_asset(self, queryset, item):
- try:
- queryset.get(pk=item).delete()
- except ObjectDoesNotExist:
- pass
- class DeleteThemeCss(DeleteThemeAssets):
- message_submit = _("Selected CSS files have been deleted.")
- queryset_attr = "css"
- def action(self, request, theme):
- super().action(request, theme)
- clear_theme_cache()
- class DeleteThemeMedia(DeleteThemeAssets):
- message_submit = _("Selected media have been deleted.")
- queryset_attr = "media"
- class ThemeCssAdmin(ThemeAssetsAdmin, generic.TargetedView):
- def wrapped_dispatch(self, request, pk, css_pk=None):
- theme = self.get_target_or_none(request, {"pk": pk})
- if not theme:
- messages.error(request, self.message_404)
- return redirect(self.root_link)
- error = self.check_permissions( # pylint: disable=assignment-from-no-return
- request, theme
- )
- if error:
- messages.error(request, error)
- return redirect(self.root_link)
- css = self.get_theme_css_or_none(theme, css_pk)
- if css_pk and not css:
- css_error = gettext("Requested CSS could not be found in the theme.")
- messages.error(request, css_error)
- return self.redirect_to_theme_assets(theme)
- return self.real_dispatch(request, theme, css)
- def get_theme_css_or_none(self, theme, css_pk):
- if not css_pk:
- return None
- try:
- return theme.css.select_for_update().get(pk=css_pk)
- except ObjectDoesNotExist:
- return None
- def real_dispatch(self, request, theme, css):
- raise NotImplementedError(
- "Admin views extending the ThemeCssAdmin"
- "should define real_dispatch(request, theme, css)"
- )
- class MoveThemeCssUp(ThemeCssAdmin):
- def real_dispatch(self, request, theme, css):
- if request.method == "POST" and move_css_up(theme, css):
- clear_theme_cache()
- messages.success(request, gettext('"%s" was moved up.') % css)
- return self.redirect_to_theme_assets(theme)
- class MoveThemeCssDown(ThemeCssAdmin):
- def real_dispatch(self, request, theme, css):
- if request.method == "POST" and move_css_down(theme, css):
- clear_theme_cache()
- messages.success(request, gettext('"%s" was moved down.') % css)
- return self.redirect_to_theme_assets(theme)
- class ThemeCssFormAdmin(ThemeCssAdmin, generic.ModelFormView):
- def real_dispatch(self, request, theme, css=None):
- form = self.initialize_form(self.form, request, theme, css)
- if request.method == "POST" and form.is_valid():
- response = self.handle_form( # pylint: disable=assignment-from-no-return
- form, request, theme, css
- )
- if response:
- return response
- if "stay" in request.POST:
- return self.redirect_to_edit_form(theme, form.instance)
- return self.redirect_to_theme_assets(theme)
- return self.render(request, {"form": form, "theme": theme, "target": css})
- def initialize_form(self, form, request, theme, css):
- raise NotImplementedError(
- "Admin views extending the ThemeCssFormAdmin "
- "should define the initialize_form(form, request, theme, css)"
- )
- def handle_form(self, form, request, theme, css):
- form.save()
- if css.source_needs_building:
- build_single_theme_css.delay(css.pk)
- else:
- clear_theme_cache()
- messages.success(request, self.message_submit % {"name": css.name})
- class NewThemeCss(ThemeCssFormAdmin):
- message_submit = _('New CSS "%(name)s" has been saved.')
- form = CssEditorForm
- template = "assets/css-editor-form.html"
- def get_theme_css_or_none(self, theme, _):
- return Css(theme=theme)
- def initialize_form(self, form, request, theme, css):
- if request.method == "POST":
- return form(request.POST, instance=css)
- return form(instance=css)
- def redirect_to_edit_form(self, theme, css):
- return redirect(
- "misago:admin:appearance:themes:edit-css-file", pk=theme.pk, css_pk=css.pk
- )
- class EditThemeCss(NewThemeCss):
- message_submit = _('CSS "%(name)s" has been updated.')
- def get_theme_css_or_none(self, theme, css_pk):
- try:
- return theme.css.get(pk=css_pk, url__isnull=True)
- except ObjectDoesNotExist:
- return None
- def initialize_form(self, form, request, theme, css):
- if request.method == "POST":
- return form(request.POST, instance=css)
- initial_data = {"source": css.source_file.read()}
- return form(instance=css, initial=initial_data)
- def handle_form(self, form, request, theme, css):
- if "source" in form.changed_data:
- form.save()
- if css.source_needs_building:
- build_single_theme_css.delay(css.pk)
- else:
- clear_theme_cache()
- messages.success(request, self.message_submit % {"name": css.name})
- else:
- message = gettext('No changes have been made to "%(css)s".')
- messages.info(request, message % {"name": css.name})
- class NewThemeCssLink(ThemeCssFormAdmin):
- message_submit = _('New CSS link "%(name)s" has been saved.')
- form = CssLinkForm
- template = "assets/css-link-form.html"
- def get_theme_css_or_none(self, theme, _):
- return Css(theme=theme)
- def initialize_form(self, form, request, theme, css):
- if request.method == "POST":
- return form(request.POST, instance=css)
- return form(instance=css)
- def handle_form(self, form, *args):
- super().handle_form(form, *args)
- if "url" in form.changed_data:
- update_remote_css_size.delay(form.instance.pk)
- clear_theme_cache()
- def redirect_to_edit_form(self, theme, css):
- return redirect("misago:admin:appearance:themes:new-css-link", pk=theme.pk)
- class EditThemeCssLink(NewThemeCssLink):
- message_submit = _('CSS link "%(name)s" has been updated.')
- def get_theme_css_or_none(self, theme, css_pk):
- try:
- return theme.css.get(pk=css_pk, url__isnull=False)
- except ObjectDoesNotExist:
- return None
- def redirect_to_edit_form(self, theme, css):
- return redirect(
- "misago:admin:appearance:themes:edit-css-link", pk=theme.pk, css_pk=css.pk
- )
|