Просмотр исходного кода

Add linking to remote CSS from themes

rafalp 6 лет назад
Родитель
Сommit
eec53d5d8d

+ 0 - 5
devproject/celery.py

@@ -15,8 +15,3 @@ app.config_from_object("django.conf:settings", namespace="CELERY")
 
 # Load task modules from all registered Django app configs.
 app.autodiscover_tasks()
-
-
-@app.task(bind=True)
-def debug_task(self):
-    print("Request: {0!r}".format(self.request))

+ 0 - 0
misago/templates/misago/admin/themes/assets/css-editor.html → misago/templates/misago/admin/themes/assets/css-editor-form.html


+ 47 - 0
misago/templates/misago/admin/themes/assets/css-link-form.html

@@ -0,0 +1,47 @@
+{% extends "misago/admin/generic/form.html" %}
+{% load i18n static misago_admin_form %}
+
+
+{% block title %}
+{% if target.pk %}
+  {{ target }}
+{% else %}
+  {% trans "New CSS link" %}
+{% endif %} | {{ theme }} | {{ active_link.name }} | {{ block.super }}
+{% endblock title %}
+
+
+{% block page-target %}
+<a href="{% url 'misago:admin:appearance:themes:assets' pk=theme.pk %}">
+  {{ theme }}
+</a>
+{% endblock page-target %}
+
+
+{% block form-header %}
+<h1>
+  {% if target.pk %}
+    {{ target }}
+  {% else %}
+    {% trans "New CSS link" %}
+  {% endif %}
+</h1>
+{% endblock %}
+
+
+{% block form-extra %}
+class="form-horizontal"
+{% endblock form-extra%}
+
+
+{% block form-body %}
+<div class="form-body no-fieldsets">
+  {% form_row form.name %}
+  {% form_row form.url %}
+</div>
+{% endblock form-body %}
+
+
+{% block form-footer-cancel %}
+<a href="{% url 'misago:admin:appearance:themes:assets' pk=theme.pk %}" class="btn btn-default">{% trans "Cancel" %}</a>
+{% endblock %}

+ 16 - 9
misago/templates/misago/admin/themes/assets/css.html

@@ -6,13 +6,13 @@
     </h3>
     <button type="button" class="btn btn-default btn-sm pull-right" data-toggle="modal" data-target="#uploadCss">
       <span class="fa fa-upload"></span>
-      {% trans "Upload CSS" %}
+      {% trans "Upload" %}
     </button>
-    <button type="button" class="btn btn-default btn-sm pull-right">
+    <a href="{% url 'misago:admin:appearance:themes:new-css-link' pk=theme.pk %}" class="btn btn-default btn-sm pull-right">
       <span class="fa fa-link"></span>
       {% trans "Link" %}
-    </button>
-    <a href="{% url 'misago:admin:appearance:themes:new-css' pk=theme.pk %}" class="btn btn-default btn-sm pull-right">
+    </a>
+    <a href="{% url 'misago:admin:appearance:themes:new-css-file' pk=theme.pk %}" class="btn btn-default btn-sm pull-right">
       <span class="fa fa-file-text"></span>
       {% trans "Create" %}
     </a>
@@ -36,7 +36,7 @@
         {% for item in css %}
           <tr>
             <td>
-              <a class="btn btn-default btn-sm tooltip-top" href="{{ item.source_file.url }}" target="blank" title="{% trans 'Preview' %}">
+              <a class="btn btn-default btn-sm tooltip-top" href="{% if item.url %}{{ item.url }}{% else %}{{ item.source_file.url }}{% endif %}" target="blank" title="{% trans 'Preview' %}">
                 <span class="fa fa-share-square-o"></span>
               </a>
             </td>
@@ -58,10 +58,17 @@
               </button>
             </td>
             <td style="width: 1%;">
-              <a href="{% url 'misago:admin:appearance:themes:edit-css' pk=theme.pk css_pk=item.pk %}" class="btn btn-default btn-sm">
-                <span class="fa fa-pencil"></span>
-                {% trans "Edit" %}
-              </a>
+              {% if item.url %}
+                <a href="{% url 'misago:admin:appearance:themes:edit-css-link' pk=theme.pk css_pk=item.pk %}" class="btn btn-default btn-sm">
+                  <span class="fa fa-pencil"></span>
+                  {% trans "Edit" %}
+                </a>
+              {% else %}
+                <a href="{% url 'misago:admin:appearance:themes:edit-css-file' pk=theme.pk css_pk=item.pk %}" class="btn btn-default btn-sm">
+                  <span class="fa fa-pencil"></span>
+                  {% trans "Edit" %}
+                </a>
+              {% endif %}
             </td>
             <td style="width: 1%;">
               <input type="checkbox" name="item" value="{{ item.pk }}">

+ 1 - 1
misago/templates/misago/admin/themes/assets/media.html

@@ -6,7 +6,7 @@
     </h3>
     <button type="button" class="btn btn-default btn-sm pull-right" data-toggle="modal" data-target="#uploadMedia">
       <span class="fa fa-upload"></span>
-      {% trans "Upload media" %}
+      {% trans "Upload" %}
     </button>
   </div>
   {% with theme.media.all as media %}

+ 16 - 2
misago/themes/admin/__init__.py

@@ -8,10 +8,12 @@ from .views import (
     DeleteThemeMedia,
     EditTheme,
     EditThemeCss,
+    EditThemeCssLink,
     MoveThemeCssDown,
     MoveThemeCssUp,
     NewTheme,
     NewThemeCss,
+    NewThemeCssLink,
     ThemeAssets,
     ThemesList,
     UploadThemeCss,
@@ -65,12 +67,24 @@ class MisagoAdminExtension:
                 name="move-css-up",
             ),
             url(
-                r"^assets/(?P<pk>\d+)/new-css/$", NewThemeCss.as_view(), name="new-css"
+                r"^assets/(?P<pk>\d+)/new-css/$",
+                NewThemeCss.as_view(),
+                name="new-css-file",
             ),
             url(
                 r"^assets/(?P<pk>\d+)/edit-css/(?P<css_pk>\d+)/$",
                 EditThemeCss.as_view(),
-                name="edit-css",
+                name="edit-css-file",
+            ),
+            url(
+                r"^assets/(?P<pk>\d+)/new-css-link/$",
+                NewThemeCssLink.as_view(),
+                name="new-css-link",
+            ),
+            url(
+                r"^assets/(?P<pk>\d+)/edit-css-link/(?P<css_pk>\d+)/$",
+                EditThemeCssLink.as_view(),
+                name="edit-css-link",
             ),
         )
 

+ 0 - 1
misago/themes/admin/css.py

@@ -1,4 +1,3 @@
-from ..models import Css
 from .utils import get_file_hash
 
 

+ 27 - 9
misago/themes/admin/forms.py

@@ -113,7 +113,7 @@ class UploadMediaForm(UploadAssetsForm):
         create_media(self.instance, asset)
 
 
-class CssForm(forms.ModelForm):
+class CssEditorForm(forms.ModelForm):
     name = forms.CharField(
         label=_("Name"),
         help_text=_(
@@ -121,6 +121,11 @@ class CssForm(forms.ModelForm):
         ),
         validators=[validate_css_name],
     )
+    source = forms.CharField(widget=forms.Textarea(), required=False)
+
+    class Meta:
+        model = Css
+        fields = ["name"]
 
     def clean_name(self):
         data = self.cleaned_data["name"]
@@ -134,14 +139,6 @@ class CssForm(forms.ModelForm):
 
         return data
 
-
-class CssEditorForm(CssForm):
-    source = forms.CharField(widget=forms.Textarea(), required=False)
-
-    class Meta:
-        model = Css
-        fields = ["name"]
-
     def clean(self):
         cleaned_data = super().clean()
         if not cleaned_data.get("source"):
@@ -167,3 +164,24 @@ class CssEditorForm(CssForm):
 
         self.instance.save()
         return self.instance
+
+
+class CssLinkForm(forms.ModelForm):
+    name = forms.CharField(
+        label=_("Link name"),
+        help_text=_('Can be descriptive (e.g. "roboto from fonts.google.com").'),
+    )
+    url = forms.URLField(label=_("Remote CSS URL"))
+
+    class Meta:
+        model = Css
+        fields = ["name", "url"]
+
+    def save(self):
+        if not self.instance.pk:
+            self.instance.order = get_next_css_order(self.instance.theme)
+            self.instance.save()
+        else:
+            self.instance.save(update_fields=["name", "url", "modified_on"])
+
+        return self.instance

+ 29 - 0
misago/themes/admin/tasks.py

@@ -0,0 +1,29 @@
+import requests
+from celery import shared_task
+from requests.exceptions import RequestException
+
+from ..models import Css
+
+
+@shared_task
+def update_remote_css_size(pk):
+    try:
+        css = Css.objects.get(pk=pk)
+        css.size = get_remove_css_size(css.url)
+    except Css.DoesNotExist:
+        pass
+    else:
+        css.save(update_fields=["size"])
+
+
+def get_remove_css_size(url):
+    try:
+        response = requests.head(url)
+        response.raise_for_status()
+    except RequestException:
+        return 0
+    else:
+        try:
+            return int(response.headers.get("content-length", 0))
+        except (TypeError, ValueError):
+            return 0

+ 1 - 1
misago/themes/admin/tests/conftest.py

@@ -33,7 +33,7 @@ TESTS_DIR = os.path.dirname(os.path.abspath(__file__))
 def css(admin_client, theme):
     url = reverse("misago:admin:appearance:themes:upload-css", kwargs={"pk": theme.pk})
     with open(os.path.join(TESTS_DIR, "css", "test.css")) as fp:
-        response = admin_client.post(url, {"assets": [fp]})
+        admin_client.post(url, {"assets": [fp]})
     return theme.css.get(name="test.css")
 
 

+ 19 - 22
misago/themes/admin/tests/test_css_creation_and_edition.py

@@ -8,13 +8,15 @@ from ....test import assert_contains, assert_has_error_message
 
 @pytest.fixture
 def create_link(theme):
-    return reverse("misago:admin:appearance:themes:new-css", kwargs={"pk": theme.pk})
+    return reverse(
+        "misago:admin:appearance:themes:new-css-file", kwargs={"pk": theme.pk}
+    )
 
 
 @pytest.fixture
 def edit_link(theme, css):
     return reverse(
-        "misago:admin:appearance:themes:edit-css",
+        "misago:admin:appearance:themes:edit-css-file",
         kwargs={"pk": theme.pk, "css_pk": css.pk},
     )
 
@@ -107,7 +109,7 @@ def test_css_name_usage_check_passess_if_name_is_used_by_other_theme_css(
     other_theme, admin_client, data, css
 ):
     create_link = reverse(
-        "misago:admin:appearance:themes:new-css", kwargs={"pk": other_theme.pk}
+        "misago:admin:appearance:themes:new-css-file", kwargs={"pk": other_theme.pk}
     )
     data["name"] = css.name
     admin_client.post(create_link, data)
@@ -126,7 +128,7 @@ def test_error_message_is_set_if_user_attempts_to_create_css_in_default_theme(
     default_theme, admin_client
 ):
     create_link = reverse(
-        "misago:admin:appearance:themes:new-css", kwargs={"pk": default_theme.pk}
+        "misago:admin:appearance:themes:new-css-file", kwargs={"pk": default_theme.pk}
     )
     response = admin_client.get(create_link)
     assert_has_error_message(response)
@@ -136,7 +138,8 @@ def test_error_message_is_set_if_user_attempts_to_create_css_in_nonexisting_them
     nonexisting_theme, admin_client
 ):
     create_link = reverse(
-        "misago:admin:appearance:themes:new-css", kwargs={"pk": nonexisting_theme.pk}
+        "misago:admin:appearance:themes:new-css-file",
+        kwargs={"pk": nonexisting_theme.pk},
     )
     response = admin_client.get(create_link)
     assert_has_error_message(response)
@@ -148,7 +151,7 @@ def test_css_creation_form_redirects_user_to_edition_after_creation(
     data["stay"] = "1"
     response = admin_client.post(create_link, data)
     assert response["location"] == reverse(
-        "misago:admin:appearance:themes:edit-css",
+        "misago:admin:appearance:themes:edit-css-file",
         kwargs={"pk": theme.pk, "css_pk": theme.css.last().pk},
     )
 
@@ -241,26 +244,19 @@ def test_file_order_stays_the_same_after_edit(admin_client, edit_link, css, data
     assert css.order == original_order
 
 
-# check if exists
-# check if belongs to theme
-
-
-def test_css_creation_form_redirects_user_to_edition_after_creation(
-    theme, other_theme, admin_client, css
+def test_css_edit_form_redirects_user_to_edition_after_saving(
+    theme, admin_client, edit_link, css, data
 ):
-    edit_link = reverse(
-        "misago:admin:appearance:themes:edit-css",
-        kwargs={"pk": other_theme.pk, "css_pk": css.pk},
-    )
-
-    response = admin_client.get(edit_link)
+    data["stay"] = "1"
+    response = admin_client.post(edit_link, data)
+    assert response["location"] == edit_link
 
 
 def test_error_message_is_set_if_user_attempts_to_edit_css_in_default_theme(
     default_theme, admin_client
 ):
     edit_link = reverse(
-        "misago:admin:appearance:themes:edit-css",
+        "misago:admin:appearance:themes:edit-css-file",
         kwargs={"pk": default_theme.pk, "css_pk": 1},
     )
     response = admin_client.get(edit_link)
@@ -271,7 +267,7 @@ def test_error_message_is_set_if_user_attempts_to_edit_css_in_nonexisting_theme(
     nonexisting_theme, admin_client
 ):
     edit_link = reverse(
-        "misago:admin:appearance:themes:edit-css",
+        "misago:admin:appearance:themes:edit-css-file",
         kwargs={"pk": nonexisting_theme.pk, "css_pk": 1},
     )
     response = admin_client.get(edit_link)
@@ -282,7 +278,7 @@ def test_error_message_is_set_if_user_attempts_to_edit_css_belonging_to_other_th
     other_theme, admin_client, css
 ):
     edit_link = reverse(
-        "misago:admin:appearance:themes:edit-css",
+        "misago:admin:appearance:themes:edit-css-file",
         kwargs={"pk": other_theme.pk, "css_pk": css.pk},
     )
     response = admin_client.get(edit_link)
@@ -293,7 +289,8 @@ def test_error_message_is_set_if_user_attempts_to_edit_nonexisting_css(
     theme, admin_client
 ):
     edit_link = reverse(
-        "misago:admin:appearance:themes:edit-css", kwargs={"pk": theme.pk, "css_pk": 1}
+        "misago:admin:appearance:themes:edit-css-file",
+        kwargs={"pk": theme.pk, "css_pk": 1},
     )
     response = admin_client.get(edit_link)
     assert_has_error_message(response)

+ 1 - 15
misago/themes/admin/tests/test_reordering_css.py

@@ -84,20 +84,6 @@ def test_middle_css_can_be_moved_up(move_up, theme, css_list):
     assert middle_css.order == FIRST
 
 
-def test_middle_css_can_be_moved_down(move_down, theme, css_list):
-    middle_css = css_list[MIDDLE]
-    move_down(theme, middle_css)
-    middle_css.refresh_from_db()
-    assert middle_css.order == LAST
-
-
-def test_middle_css_can_be_moved_up(move_up, theme, css_list):
-    middle_css = css_list[MIDDLE]
-    move_up(theme, middle_css)
-    middle_css.refresh_from_db()
-    assert middle_css.order == FIRST
-
-
 def test_first_css_changes_order_with_middle_css_when_moved_down(
     move_down, theme, css_list
 ):
@@ -209,5 +195,5 @@ def test_if_given_nonexisting_theme_id_move_up_action_sets_error_message(
 
 
 def test_next_new_css_order_is_larger_than_largest_existing_css_order(theme):
-    theme.css.create(name="CSS", url="https://test.cdn/font.css", order=4),
+    theme.css.create(name="CSS", url="https://test.cdn/font.css", order=4)
     assert get_next_css_order(theme) == 5

+ 2 - 2
misago/themes/admin/validators.py

@@ -3,8 +3,8 @@ import re
 from django.forms import ValidationError
 from django.utils.translation import gettext as _
 
-FILENAME_CONTENT = re.compile("([a-zA-Z0-9]|\.|_|-)+")
-FILENAME_TANGIBILITY = re.compile("[a-zA-Z0-9]")
+FILENAME_CONTENT = re.compile(r"([a-zA-Z0-9]|\.|_|-)+")
+FILENAME_TANGIBILITY = re.compile(r"[a-zA-Z0-9]")
 
 
 def validate_css_name(filename):

+ 58 - 9
misago/themes/admin/views.py

@@ -1,13 +1,13 @@
 from django.contrib import messages
 from django.db.models import ObjectDoesNotExist
 from django.shortcuts import redirect
-from django.urls import reverse
 from django.utils.translation import gettext, gettext_lazy as _
 
 from ...admin.views import generic
 from ..models import Theme, Css
 from .css import move_css_down, move_css_up
-from .forms import CssEditorForm, ThemeForm, UploadCssForm, UploadMediaForm
+from .forms import CssEditorForm, CssLinkForm, ThemeForm, UploadCssForm, UploadMediaForm
+from .tasks import update_remote_css_size
 
 
 class ThemeAdmin(generic.AdminBaseMixin):
@@ -196,6 +196,12 @@ class ThemeCssAdmin(ThemeAssetsAdmin, generic.TargetedView):
         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):
@@ -229,20 +235,21 @@ class ThemeCssFormAdmin(ThemeCssAdmin, generic.ModelFormView):
 
         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()
         messages.success(request, self.message_submit % {"name": css.name})
 
-    def redirect_to_edit_form(self, theme, css):
-        return redirect(
-            "misago:admin:appearance:themes:edit-css", pk=theme.pk, css_pk=css.pk
-        )
-
 
 class NewThemeCss(ThemeCssFormAdmin):
     message_submit = _('New CSS "%(name)s" has been saved.')
     form = CssEditorForm
-    template = "assets/css-editor.html"
+    template = "assets/css-editor-form.html"
 
     def get_theme_css_or_none(self, theme, _):
         return Css(theme=theme)
@@ -252,6 +259,11 @@ class NewThemeCss(ThemeCssFormAdmin):
             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.')
@@ -274,4 +286,41 @@ class EditThemeCss(NewThemeCss):
             messages.success(request, self.message_submit % {"name": css.name})
         else:
             message = gettext('No changes have been made to "%(css)s".')
-            messages.info(request, self.message_submit % {"name": css.name})
+            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 form.has_changed():
+            update_remote_css_size.delay(form.instance.pk)
+
+    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
+        )

+ 4 - 0
misago/themes/apps.py

@@ -5,3 +5,7 @@ class MisagoThemesConfig(AppConfig):
     name = "misago.themes"
     label = "misago_themes"
     verbose_name = "Misago Theming"
+
+    def ready(self):
+        # pylint: disable=unused-import
+        from .admin import tasks

+ 0 - 2
misago/themes/cache.py

@@ -1,5 +1,3 @@
-from django.core.cache import cache
-
 from . import THEME_CACHE
 from ..cache.versions import invalidate_cache
 

+ 4 - 1
misago/themes/context_processors.py

@@ -13,6 +13,9 @@ def theme(request):
         if theme.is_default:
             include_defaults = True
         for css in theme.css.all():
-            styles.append(css.file.url)
+            if css.url:
+                styles.append(css.url)
+            else:
+                styles.append(css.source_file.url)
 
     return {"theme": {"include_defaults": include_defaults, "styles": styles}}