Browse Source

Make OG image customizable by admin panel (#1238)

Rafał Pitoń 6 years ago
parent
commit
05268d9cd7

+ 1 - 0
devproject/settings.py

@@ -269,6 +269,7 @@ TEMPLATES = [
                 "django.contrib.messages.context_processors.messages",
                 "misago.acl.context_processors.user_acl",
                 "misago.conf.context_processors.conf",
+                "misago.conf.context_processors.og_image",
                 "misago.core.context_processors.misago_version",
                 "misago.core.context_processors.request_path",
                 "misago.core.context_processors.momentjs_locale",

+ 0 - 1
misago/conf/admin/forms/captcha.py

@@ -1,7 +1,6 @@
 from django import forms
 from django.utils.translation import gettext_lazy as _
 
-from ....admin.forms import YesNoSwitch
 from .base import ChangeSettingsForm
 
 

+ 22 - 0
misago/conf/admin/forms/general.py

@@ -15,6 +15,9 @@ class ChangeGeneralSettingsForm(ChangeSettingsForm):
         "logo",
         "logo_small",
         "logo_text",
+        "og_image",
+        "og_image_avatar_on_profile",
+        "og_image_avatar_on_thread",
         "forum_footnote",
         "email_footer",
     ]
@@ -66,6 +69,25 @@ class ChangeGeneralSettingsForm(ChangeSettingsForm):
         required=False,
     )
 
+    og_image = forms.ImageField(
+        label=_("Image"),
+        help_text=_(
+            "Custom image that will appear next to links to your forum posted on "
+            "social sites. Facebook recommends that this image should be "
+            "1200 pixels wide and 630 pixels tall."
+        ),
+        required=False,
+    )
+    og_image_delete = forms.BooleanField(
+        label=_("Delete current image"), required=False
+    )
+    og_image_avatar_on_profile = YesNoSwitch(
+        label=_("Replace image with avatar on user profiles")
+    )
+    og_image_avatar_on_thread = YesNoSwitch(
+        label=_("Replace image with avatar on threads")
+    )
+
     forum_footnote = forms.CharField(
         label=_("Forum footnote"),
         help_text=_("Short message displayed in forum footer."),

+ 0 - 1
misago/conf/admin/forms/threads.py

@@ -1,7 +1,6 @@
 from django import forms
 from django.utils.translation import gettext_lazy as _
 
-from ....admin.forms import YesNoSwitch
 from .base import ChangeSettingsForm
 
 

+ 14 - 0
misago/conf/context_processors.py

@@ -21,6 +21,20 @@ def conf(request):
     }
 
 
+def og_image(request):
+    og_image = request.settings.get("og_image")
+    if not og_image["value"]:
+        return {"og_image": None}
+
+    return {
+        "og_image": {
+            "url": og_image["value"],
+            "width": og_image["width"],
+            "height": og_image["height"],
+        }
+    }
+
+
 def preload_settings_json(request):
     preloaded_settings = request.settings.get_public_settings()
 

+ 21 - 15
misago/conf/dynamicsettings.py

@@ -11,6 +11,9 @@ class DynamicSettings:
             self._settings = get_settings_from_db()
             set_settings_cache(cache_versions, self._settings)
 
+    def get(self, setting):
+        return self._settings.get(setting)
+
     def get_public_settings(self):
         public_settings = {}
         for name, setting in self._settings.items():
@@ -50,22 +53,25 @@ class DynamicSettings:
 def get_settings_from_db():
     settings = {}
     for setting in Setting.objects.iterator():
+        settings[setting.setting] = {
+            "value": None,
+            "is_lazy": setting.is_lazy,
+            "is_public": setting.is_public,
+            "width": None,
+            "height": None,
+        }
+
         if setting.is_lazy:
-            settings[setting.setting] = {
-                "value": True if setting.value else None,
-                "is_lazy": setting.is_lazy,
-                "is_public": setting.is_public,
-            }
+            settings[setting.setting]["value"] = True if setting.value else None
         elif setting.python_type == "image":
-            settings[setting.setting] = {
-                "value": setting.value.url if setting.value else None,
-                "is_lazy": setting.is_lazy,
-                "is_public": setting.is_public,
-            }
+            settings[setting.setting].update(
+                {
+                    "value": setting.value.url if setting.value else None,
+                    "width": setting.image_width,
+                    "height": setting.image_height,
+                }
+            )
         else:
-            settings[setting.setting] = {
-                "value": setting.value,
-                "is_lazy": setting.is_lazy,
-                "is_public": setting.is_public,
-            }
+            settings[setting.setting]["value"] = setting.value
+
     return settings

+ 7 - 0
misago/conf/migrations/0004_create_settings.py

@@ -45,6 +45,13 @@ default_settings = [
         "dry_value": 5,
         "is_public": True,
     },
+    {"setting": "og_image", "python_type": "image"},
+    {
+        "setting": "og_image_avatar_on_profile",
+        "python_type": "bool",
+        "dry_value": False,
+    },
+    {"setting": "og_image_avatar_on_thread", "python_type": "bool", "dry_value": False},
     {"setting": "qa_answers"},
     {"setting": "qa_help_text"},
     {"setting": "qa_question"},

+ 11 - 0
misago/conf/tests/test_getting_dynamic_settings_values.py

@@ -129,3 +129,14 @@ def test_public_settings_getter_excludes_private_settings_from_dict(
     settings = DynamicSettings(cache_versions)
     public_settings = settings.get_public_settings()
     assert "private_setting" not in public_settings
+
+
+def test_getter_returns_setting_dict(cache_versions, public_setting):
+    settings = DynamicSettings(cache_versions)
+    assert settings.get(public_setting.setting) == {
+        "value": public_setting.value,
+        "is_lazy": public_setting.is_lazy,
+        "is_public": public_setting.is_public,
+        "width": public_setting.image_width,
+        "height": public_setting.image_height,
+    }

+ 55 - 0
misago/conf/tests/test_open_graph_image.py

@@ -0,0 +1,55 @@
+from ...test import assert_contains, assert_not_contains
+from ...threads.test import post_thread
+from ..models import Setting
+from ..test import override_dynamic_settings
+
+
+@override_dynamic_settings(forum_address="http://test.com/")
+def test_default_og_image_is_used_when_none_is_set(db, client):
+    response = client.get("/")
+    assert_contains(response, "http://test.com/static/misago/img/og-image.jpg")
+
+
+@override_dynamic_settings(forum_address="http://test.com/")
+def test_custom_og_image_is_used_instead_of_default_one_when_set(db, client):
+    Setting.objects.filter(setting="og_image").update(
+        image="custom-image.jpg", image_width=600, image_height=300
+    )
+
+    response = client.get("/")
+    assert_not_contains(response, "http://test.com/media/misago/img/og-image.jpg")
+    assert_contains(response, "http://test.com/media/custom-image.jpg")
+    assert_contains(response, 'property="og:image:width" content="600"')
+    assert_contains(response, 'property="og:image:height" content="300"')
+
+
+@override_dynamic_settings(forum_address="http://test.com/")
+def test_default_og_image_is_used_on_user_profiles(client, user):
+    response = client.get("%sposts/" % user.get_absolute_url())
+    assert_contains(response, "http://test.com/static/misago/img/og-image.jpg")
+
+
+@override_dynamic_settings(
+    forum_address="http://test.com/", og_image_avatar_on_profile=True
+)
+def test_user_avatar_can_be_used_as_og_image_on_user_profiles(client, user):
+    response = client.get("%sposts/" % user.get_absolute_url())
+    assert_not_contains(response, "http://test.com/static/misago/img/og-image.jpg")
+
+
+@override_dynamic_settings(forum_address="http://test.com/")
+def test_default_og_image_is_used_on_thread_page(client, default_category, user):
+    thread = post_thread(default_category, poster=user)
+    response = client.get(thread.get_absolute_url())
+    assert_contains(response, "http://test.com/static/misago/img/og-image.jpg")
+
+
+@override_dynamic_settings(
+    forum_address="http://test.com/", og_image_avatar_on_thread=True
+)
+def test_thread_started_avatar_can_be_used_as_og_image_on_thread_page(
+    client, default_category, user
+):
+    thread = post_thread(default_category, poster=user)
+    response = client.get(thread.get_absolute_url())
+    assert_not_contains(response, "http://test.com/static/misago/img/og-image.jpg")

+ 0 - 0
misago/static/og-image.jpg → misago/static/misago/img/og-image.jpg


+ 24 - 10
misago/templates/misago/admin/conf/general_settings.html

@@ -14,16 +14,6 @@
 </div>
 <div class="form-fieldset">
   <fieldset>
-    <legend>{% trans "Forum index" %}</legend>
-
-    {% form_row form.index_header %}
-    {% form_row form.index_title %}
-    {% form_row form.index_meta_description %}
-
-  </fieldset>
-</div>
-<div class="form-fieldset">
-  <fieldset>
     <legend>{% trans "Logo" %}</legend>
 
     {% form_row form.logo_text %}
@@ -42,6 +32,30 @@
 </div>
 <div class="form-fieldset">
   <fieldset>
+    <legend>{% trans "Open Graph image" %}</legend>
+
+    {% with form.og_image_delete as delete_field %}
+      {% with form_settings.og_image as setting %}
+        {% form_image_row form.og_image delete_field=delete_field size=setting.image_size dimensions=setting.image_dimensions %}
+      {% endwith %}
+    {% endwith %}
+    {% form_row form.og_image_avatar_on_profile %}
+    {% form_row form.og_image_avatar_on_thread %}
+
+  </fieldset>
+</div>
+<div class="form-fieldset">
+  <fieldset>
+    <legend>{% trans "Forum index" %}</legend>
+
+    {% form_row form.index_header %}
+    {% form_row form.index_title %}
+    {% form_row form.index_meta_description %}
+
+  </fieldset>
+</div>
+<div class="form-fieldset">
+  <fieldset>
     <legend>{% trans "Forum footnote" %}</legend>
 
     {% form_row form.forum_footnote %}

+ 13 - 2
misago/templates/misago/base.html

@@ -1,4 +1,4 @@
-{% load i18n static misago_json %}
+{% load i18n static misago_absoluteurl misago_json %}
 <!DOCTYPE html>
 <html lang="{{ LANGUAGE_CODE_SHORT }}">
   <head>
@@ -15,7 +15,18 @@
         <meta property="og:description" content="{% spaceless %}{% block og-description %}{{ settings.forum_index_meta_description|default:'' }}{% endblock og-description %}{% endspaceless %}" />
         <meta property="og:type" content="website" />
         <meta property="og:url" content="{% spaceless %}{% block og-url %}{{ settings.forum_address }}{% endblock og-url %}{% endspaceless %}" />
-        <meta property="og:image" content="{% spaceless %}{% block og-image %}{% static 'og-image.jpg' %}{% endblock og-image %}{% endspaceless %}" />
+        {% block og-image %}
+          {% if og_image %}
+            <meta property="og:image" content="{% absoluteurl og_image.url %}" />
+            <meta property="og:image:width" content="{{ og_image.width }}" />
+            <meta property="og:image:height" content="{{ og_image.height }}" />
+          {% else %}
+            {% static "misago/img/og-image.jpg" as og_image_url %}
+            <meta property="og:image" content="{% absoluteurl og_image_url %}" />
+            <meta property="og:image:width" content="1200" />
+            <meta property="og:image:height" content="630" />
+          {% endif %}
+        {% endblock og-image %}
       {% endblock og-tags %}
     {% endspaceless %}
     {% include "misago/head.html" %}

+ 14 - 0
misago/templates/misago/profile/base.html

@@ -33,6 +33,20 @@
 {% block og-url %}{% absoluteurl request_path %}{% endblock %}
 
 
+{% block og-image %}
+{% if settings.og_image_avatar_on_profile %}
+  {% with profile.avatars|first as og_image %}
+    <meta property="og:image" content="{% absoluteurl og_image.url %}" />
+    <meta property="og:image:width" content="{{ og_image.size }}" />
+    <meta property="og:image:height" content="{{ og_image.size }}" />
+  {% endwith %}
+{% else %}
+  {{ block.super }}
+{% endif %}
+{% endblock og-image %}
+
+
+
 {% block content %}
 <div class="page page-user-profile">
 

+ 13 - 0
misago/templates/misago/thread/thread.html

@@ -30,6 +30,19 @@
 {% endblock og-url %}
 
 
+{% block og-image %}
+{% if settings.og_image_avatar_on_thread and thread.starter %}
+  {% with thread.starter.avatars|first as og_image %}
+    <meta property="og:image" content="{% absoluteurl og_image.url %}" />
+    <meta property="og:image:width" content="{{ og_image.size }}" />
+    <meta property="og:image:height" content="{{ og_image.size }}" />
+  {% endwith %}
+{% else %}
+  {{ block.super }}
+{% endif %}
+{% endblock og-image %}
+
+
 {% block meta-extra %}
 {% if paginator.page > 1 %}
   <link rel="canonical" href="{% url url_name slug=thread.slug pk=thread.pk page=paginator.page %}" />