We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Move course run enroll button to the contact block
## Feature Request

**Is your feature request related to a problem or unsupported use case? Please describe.**
Change the course detail template to be equal than the fun-mooc.
On fun-mooc site it is given much more visibility to the current open for enrollment course run button, because it was customized to be on the contact django block.
I've done exactly the same thing for nau.edu.pt.
So I think we should make this the default case for Richie.

**Describe the solution you'd like**
I've copy paste the fun-mooc `course_detail.html` that change the blocks `contact` and `runs_open`.

**Describe alternatives you've considered**
Add same information bellow the enrollment count, but I think the fun-mooc's version is better. 

**Discovery, Documentation, Adoption, Migration Strategy**
If we decide to go further this change, we need to document it on the UPGRADE.md, because the django blocks on the `course_detail.html` could be used to customize the UI.

**Do you want to work on it through a Pull Request?**
If we approve this ideia, I could send a PR.

FUN-MOOC version:
![image](https://user-images.githubusercontent.com/67018/155501008-5a2adc4b-e47b-4ac9-a3af-8be63d307baf.png)

NAU version:
![image](https://user-images.githubusercontent.com/67018/155501074-7871f97b-d92d-4a29-a100-28ef3310859c.png)

Demo version:
![image](https://user-images.githubusercontent.com/67018/155501476-3774b11c-fa42-48ab-a645-42903ba55e00.png)


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/richie/apps/courses/templatetags/extra_tags.py`
Content:
```
1 """Custom template tags for the courses application of Richie."""
2 import json
3 
4 from django import template
5 from django.core.exceptions import ObjectDoesNotExist
6 from django.template.defaultfilters import stringfilter
7 from django.template.loader import render_to_string
8 from django.utils import timezone
9 from django.utils.translation import get_language
10 from django.utils.translation import gettext as _
11 from django.utils.translation import to_locale
12 
13 import arrow
14 from classytags.arguments import Argument, MultiValueArgument
15 from classytags.core import Options, Tag
16 from classytags.utils import flatten_context
17 from cms.templatetags.cms_tags import (
18     Placeholder,
19     PlaceholderOptions,
20     _get_page_by_untyped_arg,
21 )
22 from cms.toolbar.utils import get_toolbar_from_request
23 from cms.utils import get_site_id
24 from cms.utils.plugins import get_plugins
25 
26 from ..lms import LMSHandler
27 
28 # pylint: disable=invalid-name
29 register = template.Library()
30 
31 
32 # pylint: disable=too-many-arguments
33 def get_plugins_render_tag(
34     context, name, varname, nodelist, page_lookup=None, edit=True
35 ):
36     """
37     Retrieve the placeholder's plugins and set them as a variable in the template context.
38     If the placeholder is empty, render the block as fallback content and return the
39     resulting HTML.
40     If the placeholder is editable and rendered on its own page, the edit script and markup
41     are added to the HTML content.
42     """
43     content = ""
44     request = context.get("request")
45 
46     if request:
47 
48         context[varname] = []
49         page = _get_page_by_untyped_arg(page_lookup, request, get_site_id(None))
50 
51         if not page:
52             return ""
53 
54         try:
55             placeholder = page.placeholders.get(slot=name)
56         except ObjectDoesNotExist:
57             return ""
58         else:
59             context[varname] = [
60                 cms_plugin.get_plugin_instance()[0]
61                 for cms_plugin in get_plugins(
62                     request, placeholder, template=page.get_template()
63                 )
64             ]
65 
66         # Default content if there is no plugins in the placeholder
67         if not context[varname] and nodelist:
68             content = nodelist.render(context)
69 
70         # Add the edit script and markup to the content, only if the placeholder is editable
71         if edit:
72             toolbar = get_toolbar_from_request(request)
73             if toolbar.edit_mode_active:
74                 renderer = toolbar.get_content_renderer()
75                 data = renderer.get_editable_placeholder_context(placeholder, page=page)
76                 data["content"] = content
77                 content = renderer.placeholder_edit_template.format(**data)
78 
79     return content
80 
81 
82 @register.tag("placeholder_as_plugins")
83 class PlaceholderAsPlugins(Placeholder):
84     """
85     Like DjangoCMS 'placeholder' but sets the list of linked plugins to a variable name
86     instead of rendering the placeholder.
87     """
88 
89     name = "placeholder_as_plugins"
90     options = PlaceholderOptions(
91         Argument("name", resolve=False),
92         "as",
93         Argument("varname", resolve=False),
94         MultiValueArgument("extra_bits", required=False, resolve=False),
95         blocks=[("endplaceholder_as_plugins", "nodelist")],
96     )
97 
98     # pylint: disable=arguments-renamed,too-many-arguments
99     def render_tag(self, context, name, varname, extra_bits, nodelist=None):
100         return get_plugins_render_tag(context, name, varname, nodelist)
101 
102 
103 @register.tag("get_placeholder_plugins")
104 class GetPlaceholderPlugins(Tag):
105     """
106     A template tag that gets plugins from a page's placeholder and sets them as a context variable:
107 
108         {% get_placeholder_plugins "logo" page_lookup as varname %}
109         {% get_placeholder_plugins "logo" page_lookup as varname or %}
110             <div>No content</div>
111         {% endget_placeholder_plugins %}
112 
113     The page_lookup parameter can be omitted and will default to the current page
114 
115         {% get_placeholder_plugins "logo" as varname %}
116         {% get_placeholder_plugins "logo" as varname or %}
117             <div>No content</div>
118         {% endget_placeholder_plugins %}
119 
120     This tag can typically be used in association with the block_plugin tag,
121     to render the retrieved plugins:
122 
123         {% get_placeholder_plugins "logo" page_lookup as plugins %}
124         {% blockplugin plugins.0 %}
125             <img src="{% thumbnail instance.picture 300x150 %}"/>
126         {% endblockplugin %}
127 
128     Keyword arguments:
129         name: the name of the placeholder
130         page_lookup: lookup argument for Page. See `_get_page_by_untyped_arg()`
131             for detailed information on the allowed types and their interpretation for the
132             `page_lookup` argument.
133         varname: context variable name. Output will be added to template context as this variable
134             instead of being returned.
135         or: optional argument which if given will make the template tag a block
136             tag whose content is shown if the placeholder is empty
137     """
138 
139     name = "get_placeholder_plugins"
140     options = PlaceholderOptions(
141         Argument("name", resolve=False),
142         Argument("page_lookup", required=False, default=None),
143         "as",
144         Argument("varname", resolve=False),
145         MultiValueArgument("extra_bits", required=False, resolve=False),
146         blocks=[("endget_placeholder_plugins", "nodelist")],
147     )
148 
149     # pylint: disable=arguments-differ,too-many-arguments, unused-argument
150     def render_tag(
151         self, context, name, page_lookup, varname, extra_bits, nodelist=None
152     ):
153         return get_plugins_render_tag(
154             context, name, varname, nodelist, page_lookup, edit=False
155         )
156 
157 
158 @register.tag()
159 class BlockPlugin(Tag):
160     """
161     Like DjangoCMS 'render_plugin_block' but only includes the edit script and markup when
162     the related placeholder is editable.
163 
164     This issue was raised to DjangoCMS and we need our own template tag until they find a way
165     to fix it in DjangoCMS (https://github.com/divio/django-cms/issues/6683).
166     """
167 
168     name = "blockplugin"
169     template = "cms/toolbar/plugin.html"
170     options = Options(Argument("plugin"), blocks=[("endblockplugin", "nodelist")])
171 
172     # pylint: disable=arguments-differ
173     def render_tag(self, context, plugin, nodelist):
174         """
175         Renders the block for the plugin and returns the resulting HTML leaving the temmpate
176         context untouched.
177         If the placeholder is editable, the edit script and markup are added to the rendered HTML.
178         """
179         request = context.get("request")
180         if not plugin or not request:
181             return ""
182 
183         # Add the plugin and its rendered content to an internal context
184         internal_context = flatten_context(context)
185         internal_context["instance"] = plugin
186         internal_context["content"] = nodelist.render(context.new(internal_context))
187 
188         # Add the edit script and markup to the content, only if the placeholder is editable
189         # and the visited page is the one on which the plugin's placeholder is declared.
190         toolbar = get_toolbar_from_request(request)
191         if plugin.placeholder.page == request.current_page and toolbar.edit_mode_active:
192             return render_to_string(self.template, internal_context)
193 
194         return internal_context["content"]
195 
196 
197 @register.filter()
198 def is_empty_placeholder(page, slot):
199     """A template filter to determine if a placeholder is empty.
200 
201     This is useful when we don't want to include any wrapper markup in our template unless
202     the placeholder unless it actually contains plugins.
203     """
204     placeholder = page.placeholders.get(slot=slot)
205     return not placeholder.cmsplugin_set.exists()
206 
207 
208 @register.filter()
209 def order_by(queryset, args):
210     """A template filter to force ordering on a queryset.
211 
212     Taken from: https://djangosnippets.org/snippets/741/
213     This is useful for DjangoCMS page querysets because we don't have access to the view.
214     """
215     args = [x.strip() for x in args.split(",")]
216     return queryset.order_by(*args)
217 
218 
219 @register.filter()
220 def has_connected_lms(course_run):
221     """
222     Determine if the passed course run has a connected LMS (as determined through out LMSHandler
223     and settings).
224     This enables our templates to either use the <CourseRunEnrollment /> component or a simple
225     link to the course run.
226     """
227     return LMSHandler.select_lms(course_run.resource_link) is not None
228 
229 
230 @register.simple_tag(takes_context=True)
231 def course_enrollment_widget_props(context):
232     """
233     Return a json dumps which contains all course_run's properties required by
234     CourseEnrollment React widget
235     """
236     course_run = context["run"]
237 
238     profile_urls = json.loads(
239         context.get("AUTHENTICATION", {}).get("profile_urls", "{}")
240     )
241     dashboard_link = profile_urls.get("dashboard", {}).get("action")
242 
243     starts_in_message = None
244     if course_run.start > timezone.now():
245         course_start = arrow.get(course_run.start)
246         humanized_course_start = course_start.humanize(
247             arrow.now(), locale=to_locale(get_language())
248         )
249         # Translators: delay indicates when the course will start as a duration.
250         # In english the string will be "The course will start in 3 days"
251         starts_in_message = _("The course will start {delay:s}").format(
252             delay=humanized_course_start
253         )
254 
255     return json.dumps(
256         {
257             "courseRun": {
258                 "id": course_run.id,
259                 "resource_link": course_run.resource_link,
260                 "priority": course_run.state["priority"],
261                 "starts_in_message": starts_in_message,
262                 "dashboard_link": dashboard_link,
263             }
264         }
265     )
266 
267 
268 @register.filter
269 @stringfilter
270 def trim(value):
271     """
272     Remove whitespaces before and after a string.
273     """
274     return value.strip()
275
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/src/richie/apps/courses/templatetags/extra_tags.py b/src/richie/apps/courses/templatetags/extra_tags.py
--- a/src/richie/apps/courses/templatetags/extra_tags.py
+++ b/src/richie/apps/courses/templatetags/extra_tags.py
@@ -5,7 +5,7 @@
 from django.core.exceptions import ObjectDoesNotExist
 from django.template.defaultfilters import stringfilter
 from django.template.loader import render_to_string
-from django.utils import timezone
+from django.utils import timezone, translation
 from django.utils.translation import get_language
 from django.utils.translation import gettext as _
 from django.utils.translation import to_locale
@@ -227,6 +227,33 @@
     return LMSHandler.select_lms(course_run.resource_link) is not None
 
 
+@register.filter()
+def visible_on_course_page(course_runs, edit_mode_active=None):
+    """
+    Determine if the passed course run should be visible on the course page, if on edit mode
+    show all the course runs.
+    """
+    if edit_mode_active:
+        return course_runs
+    return list(filter(lambda run: run.is_visible_on_course_page, course_runs))
+
+
+@register.filter()
+def sort_runs_by_language_and_start_date(course_runs):
+    """
+    Order course runs by: firstly runs that contains the language of the current user and only
+    after the runs that don't match the current user authenticated language. On both groups, they
+    should be sorted by course start date.
+    """
+    current_language = translation.get_language()
+    return list(
+        sorted(
+            course_runs,
+            key=lambda run: (current_language not in run.languages, run.start),
+        )
+    )
+
+
 @register.simple_tag(takes_context=True)
 def course_enrollment_widget_props(context):
     """

{"golden_diff": "diff --git a/src/richie/apps/courses/templatetags/extra_tags.py b/src/richie/apps/courses/templatetags/extra_tags.py\n--- a/src/richie/apps/courses/templatetags/extra_tags.py\n+++ b/src/richie/apps/courses/templatetags/extra_tags.py\n@@ -5,7 +5,7 @@\n from django.core.exceptions import ObjectDoesNotExist\n from django.template.defaultfilters import stringfilter\n from django.template.loader import render_to_string\n-from django.utils import timezone\n+from django.utils import timezone, translation\n from django.utils.translation import get_language\n from django.utils.translation import gettext as _\n from django.utils.translation import to_locale\n@@ -227,6 +227,33 @@\n     return LMSHandler.select_lms(course_run.resource_link) is not None\n \n \n+@register.filter()\n+def visible_on_course_page(course_runs, edit_mode_active=None):\n+    \"\"\"\n+    Determine if the passed course run should be visible on the course page, if on edit mode\n+    show all the course runs.\n+    \"\"\"\n+    if edit_mode_active:\n+        return course_runs\n+    return list(filter(lambda run: run.is_visible_on_course_page, course_runs))\n+\n+\n+@register.filter()\n+def sort_runs_by_language_and_start_date(course_runs):\n+    \"\"\"\n+    Order course runs by: firstly runs that contains the language of the current user and only\n+    after the runs that don't match the current user authenticated language. On both groups, they\n+    should be sorted by course start date.\n+    \"\"\"\n+    current_language = translation.get_language()\n+    return list(\n+        sorted(\n+            course_runs,\n+            key=lambda run: (current_language not in run.languages, run.start),\n+        )\n+    )\n+\n+\n @register.simple_tag(takes_context=True)\n def course_enrollment_widget_props(context):\n     \"\"\"\n", "issue": "Move course run enroll button to the contact block\n## Feature Request\r\n\r\n**Is your feature request related to a problem or unsupported use case? Please describe.**\r\nChange the course detail template to be equal than the fun-mooc.\r\nOn fun-mooc site it is given much more visibility to the current open for enrollment course run button, because it was customized to be on the contact django block.\r\nI've done exactly the same thing for nau.edu.pt.\r\nSo I think we should make this the default case for Richie.\r\n\r\n**Describe the solution you'd like**\r\nI've copy paste the fun-mooc `course_detail.html` that change the blocks `contact` and `runs_open`.\r\n\r\n**Describe alternatives you've considered**\r\nAdd same information bellow the enrollment count, but I think the fun-mooc's version is better. \r\n\r\n**Discovery, Documentation, Adoption, Migration Strategy**\r\nIf we decide to go further this change, we need to document it on the UPGRADE.md, because the django blocks on the `course_detail.html` could be used to customize the UI.\r\n\r\n**Do you want to work on it through a Pull Request?**\r\nIf we approve this ideia, I could send a PR.\r\n\r\nFUN-MOOC version:\r\n![image](https://user-images.githubusercontent.com/67018/155501008-5a2adc4b-e47b-4ac9-a3af-8be63d307baf.png)\r\n\r\nNAU version:\r\n![image](https://user-images.githubusercontent.com/67018/155501074-7871f97b-d92d-4a29-a100-28ef3310859c.png)\r\n\r\nDemo version:\r\n![image](https://user-images.githubusercontent.com/67018/155501476-3774b11c-fa42-48ab-a645-42903ba55e00.png)\r\n\n", "before_files": [{"content": "\"\"\"Custom template tags for the courses application of Richie.\"\"\"\nimport json\n\nfrom django import template\nfrom django.core.exceptions import ObjectDoesNotExist\nfrom django.template.defaultfilters import stringfilter\nfrom django.template.loader import render_to_string\nfrom django.utils import timezone\nfrom django.utils.translation import get_language\nfrom django.utils.translation import gettext as _\nfrom django.utils.translation import to_locale\n\nimport arrow\nfrom classytags.arguments import Argument, MultiValueArgument\nfrom classytags.core import Options, Tag\nfrom classytags.utils import flatten_context\nfrom cms.templatetags.cms_tags import (\n    Placeholder,\n    PlaceholderOptions,\n    _get_page_by_untyped_arg,\n)\nfrom cms.toolbar.utils import get_toolbar_from_request\nfrom cms.utils import get_site_id\nfrom cms.utils.plugins import get_plugins\n\nfrom ..lms import LMSHandler\n\n# pylint: disable=invalid-name\nregister = template.Library()\n\n\n# pylint: disable=too-many-arguments\ndef get_plugins_render_tag(\n    context, name, varname, nodelist, page_lookup=None, edit=True\n):\n    \"\"\"\n    Retrieve the placeholder's plugins and set them as a variable in the template context.\n    If the placeholder is empty, render the block as fallback content and return the\n    resulting HTML.\n    If the placeholder is editable and rendered on its own page, the edit script and markup\n    are added to the HTML content.\n    \"\"\"\n    content = \"\"\n    request = context.get(\"request\")\n\n    if request:\n\n        context[varname] = []\n        page = _get_page_by_untyped_arg(page_lookup, request, get_site_id(None))\n\n        if not page:\n            return \"\"\n\n        try:\n            placeholder = page.placeholders.get(slot=name)\n        except ObjectDoesNotExist:\n            return \"\"\n        else:\n            context[varname] = [\n                cms_plugin.get_plugin_instance()[0]\n                for cms_plugin in get_plugins(\n                    request, placeholder, template=page.get_template()\n                )\n            ]\n\n        # Default content if there is no plugins in the placeholder\n        if not context[varname] and nodelist:\n            content = nodelist.render(context)\n\n        # Add the edit script and markup to the content, only if the placeholder is editable\n        if edit:\n            toolbar = get_toolbar_from_request(request)\n            if toolbar.edit_mode_active:\n                renderer = toolbar.get_content_renderer()\n                data = renderer.get_editable_placeholder_context(placeholder, page=page)\n                data[\"content\"] = content\n                content = renderer.placeholder_edit_template.format(**data)\n\n    return content\n\n\n@register.tag(\"placeholder_as_plugins\")\nclass PlaceholderAsPlugins(Placeholder):\n    \"\"\"\n    Like DjangoCMS 'placeholder' but sets the list of linked plugins to a variable name\n    instead of rendering the placeholder.\n    \"\"\"\n\n    name = \"placeholder_as_plugins\"\n    options = PlaceholderOptions(\n        Argument(\"name\", resolve=False),\n        \"as\",\n        Argument(\"varname\", resolve=False),\n        MultiValueArgument(\"extra_bits\", required=False, resolve=False),\n        blocks=[(\"endplaceholder_as_plugins\", \"nodelist\")],\n    )\n\n    # pylint: disable=arguments-renamed,too-many-arguments\n    def render_tag(self, context, name, varname, extra_bits, nodelist=None):\n        return get_plugins_render_tag(context, name, varname, nodelist)\n\n\n@register.tag(\"get_placeholder_plugins\")\nclass GetPlaceholderPlugins(Tag):\n    \"\"\"\n    A template tag that gets plugins from a page's placeholder and sets them as a context variable:\n\n        {% get_placeholder_plugins \"logo\" page_lookup as varname %}\n        {% get_placeholder_plugins \"logo\" page_lookup as varname or %}\n            <div>No content</div>\n        {% endget_placeholder_plugins %}\n\n    The page_lookup parameter can be omitted and will default to the current page\n\n        {% get_placeholder_plugins \"logo\" as varname %}\n        {% get_placeholder_plugins \"logo\" as varname or %}\n            <div>No content</div>\n        {% endget_placeholder_plugins %}\n\n    This tag can typically be used in association with the block_plugin tag,\n    to render the retrieved plugins:\n\n        {% get_placeholder_plugins \"logo\" page_lookup as plugins %}\n        {% blockplugin plugins.0 %}\n            <img src=\"{% thumbnail instance.picture 300x150 %}\"/>\n        {% endblockplugin %}\n\n    Keyword arguments:\n        name: the name of the placeholder\n        page_lookup: lookup argument for Page. See `_get_page_by_untyped_arg()`\n            for detailed information on the allowed types and their interpretation for the\n            `page_lookup` argument.\n        varname: context variable name. Output will be added to template context as this variable\n            instead of being returned.\n        or: optional argument which if given will make the template tag a block\n            tag whose content is shown if the placeholder is empty\n    \"\"\"\n\n    name = \"get_placeholder_plugins\"\n    options = PlaceholderOptions(\n        Argument(\"name\", resolve=False),\n        Argument(\"page_lookup\", required=False, default=None),\n        \"as\",\n        Argument(\"varname\", resolve=False),\n        MultiValueArgument(\"extra_bits\", required=False, resolve=False),\n        blocks=[(\"endget_placeholder_plugins\", \"nodelist\")],\n    )\n\n    # pylint: disable=arguments-differ,too-many-arguments, unused-argument\n    def render_tag(\n        self, context, name, page_lookup, varname, extra_bits, nodelist=None\n    ):\n        return get_plugins_render_tag(\n            context, name, varname, nodelist, page_lookup, edit=False\n        )\n\n\n@register.tag()\nclass BlockPlugin(Tag):\n    \"\"\"\n    Like DjangoCMS 'render_plugin_block' but only includes the edit script and markup when\n    the related placeholder is editable.\n\n    This issue was raised to DjangoCMS and we need our own template tag until they find a way\n    to fix it in DjangoCMS (https://github.com/divio/django-cms/issues/6683).\n    \"\"\"\n\n    name = \"blockplugin\"\n    template = \"cms/toolbar/plugin.html\"\n    options = Options(Argument(\"plugin\"), blocks=[(\"endblockplugin\", \"nodelist\")])\n\n    # pylint: disable=arguments-differ\n    def render_tag(self, context, plugin, nodelist):\n        \"\"\"\n        Renders the block for the plugin and returns the resulting HTML leaving the temmpate\n        context untouched.\n        If the placeholder is editable, the edit script and markup are added to the rendered HTML.\n        \"\"\"\n        request = context.get(\"request\")\n        if not plugin or not request:\n            return \"\"\n\n        # Add the plugin and its rendered content to an internal context\n        internal_context = flatten_context(context)\n        internal_context[\"instance\"] = plugin\n        internal_context[\"content\"] = nodelist.render(context.new(internal_context))\n\n        # Add the edit script and markup to the content, only if the placeholder is editable\n        # and the visited page is the one on which the plugin's placeholder is declared.\n        toolbar = get_toolbar_from_request(request)\n        if plugin.placeholder.page == request.current_page and toolbar.edit_mode_active:\n            return render_to_string(self.template, internal_context)\n\n        return internal_context[\"content\"]\n\n\n@register.filter()\ndef is_empty_placeholder(page, slot):\n    \"\"\"A template filter to determine if a placeholder is empty.\n\n    This is useful when we don't want to include any wrapper markup in our template unless\n    the placeholder unless it actually contains plugins.\n    \"\"\"\n    placeholder = page.placeholders.get(slot=slot)\n    return not placeholder.cmsplugin_set.exists()\n\n\n@register.filter()\ndef order_by(queryset, args):\n    \"\"\"A template filter to force ordering on a queryset.\n\n    Taken from: https://djangosnippets.org/snippets/741/\n    This is useful for DjangoCMS page querysets because we don't have access to the view.\n    \"\"\"\n    args = [x.strip() for x in args.split(\",\")]\n    return queryset.order_by(*args)\n\n\n@register.filter()\ndef has_connected_lms(course_run):\n    \"\"\"\n    Determine if the passed course run has a connected LMS (as determined through out LMSHandler\n    and settings).\n    This enables our templates to either use the <CourseRunEnrollment /> component or a simple\n    link to the course run.\n    \"\"\"\n    return LMSHandler.select_lms(course_run.resource_link) is not None\n\n\n@register.simple_tag(takes_context=True)\ndef course_enrollment_widget_props(context):\n    \"\"\"\n    Return a json dumps which contains all course_run's properties required by\n    CourseEnrollment React widget\n    \"\"\"\n    course_run = context[\"run\"]\n\n    profile_urls = json.loads(\n        context.get(\"AUTHENTICATION\", {}).get(\"profile_urls\", \"{}\")\n    )\n    dashboard_link = profile_urls.get(\"dashboard\", {}).get(\"action\")\n\n    starts_in_message = None\n    if course_run.start > timezone.now():\n        course_start = arrow.get(course_run.start)\n        humanized_course_start = course_start.humanize(\n            arrow.now(), locale=to_locale(get_language())\n        )\n        # Translators: delay indicates when the course will start as a duration.\n        # In english the string will be \"The course will start in 3 days\"\n        starts_in_message = _(\"The course will start {delay:s}\").format(\n            delay=humanized_course_start\n        )\n\n    return json.dumps(\n        {\n            \"courseRun\": {\n                \"id\": course_run.id,\n                \"resource_link\": course_run.resource_link,\n                \"priority\": course_run.state[\"priority\"],\n                \"starts_in_message\": starts_in_message,\n                \"dashboard_link\": dashboard_link,\n            }\n        }\n    )\n\n\n@register.filter\n@stringfilter\ndef trim(value):\n    \"\"\"\n    Remove whitespaces before and after a string.\n    \"\"\"\n    return value.strip()\n", "path": "src/richie/apps/courses/templatetags/extra_tags.py"}], "after_files": [{"content": "\"\"\"Custom template tags for the courses application of Richie.\"\"\"\nimport json\n\nfrom django import template\nfrom django.core.exceptions import ObjectDoesNotExist\nfrom django.template.defaultfilters import stringfilter\nfrom django.template.loader import render_to_string\nfrom django.utils import timezone, translation\nfrom django.utils.translation import get_language\nfrom django.utils.translation import gettext as _\nfrom django.utils.translation import to_locale\n\nimport arrow\nfrom classytags.arguments import Argument, MultiValueArgument\nfrom classytags.core import Options, Tag\nfrom classytags.utils import flatten_context\nfrom cms.templatetags.cms_tags import (\n    Placeholder,\n    PlaceholderOptions,\n    _get_page_by_untyped_arg,\n)\nfrom cms.toolbar.utils import get_toolbar_from_request\nfrom cms.utils import get_site_id\nfrom cms.utils.plugins import get_plugins\n\nfrom ..lms import LMSHandler\n\n# pylint: disable=invalid-name\nregister = template.Library()\n\n\n# pylint: disable=too-many-arguments\ndef get_plugins_render_tag(\n    context, name, varname, nodelist, page_lookup=None, edit=True\n):\n    \"\"\"\n    Retrieve the placeholder's plugins and set them as a variable in the template context.\n    If the placeholder is empty, render the block as fallback content and return the\n    resulting HTML.\n    If the placeholder is editable and rendered on its own page, the edit script and markup\n    are added to the HTML content.\n    \"\"\"\n    content = \"\"\n    request = context.get(\"request\")\n\n    if request:\n\n        context[varname] = []\n        page = _get_page_by_untyped_arg(page_lookup, request, get_site_id(None))\n\n        if not page:\n            return \"\"\n\n        try:\n            placeholder = page.placeholders.get(slot=name)\n        except ObjectDoesNotExist:\n            return \"\"\n        else:\n            context[varname] = [\n                cms_plugin.get_plugin_instance()[0]\n                for cms_plugin in get_plugins(\n                    request, placeholder, template=page.get_template()\n                )\n            ]\n\n        # Default content if there is no plugins in the placeholder\n        if not context[varname] and nodelist:\n            content = nodelist.render(context)\n\n        # Add the edit script and markup to the content, only if the placeholder is editable\n        if edit:\n            toolbar = get_toolbar_from_request(request)\n            if toolbar.edit_mode_active:\n                renderer = toolbar.get_content_renderer()\n                data = renderer.get_editable_placeholder_context(placeholder, page=page)\n                data[\"content\"] = content\n                content = renderer.placeholder_edit_template.format(**data)\n\n    return content\n\n\n@register.tag(\"placeholder_as_plugins\")\nclass PlaceholderAsPlugins(Placeholder):\n    \"\"\"\n    Like DjangoCMS 'placeholder' but sets the list of linked plugins to a variable name\n    instead of rendering the placeholder.\n    \"\"\"\n\n    name = \"placeholder_as_plugins\"\n    options = PlaceholderOptions(\n        Argument(\"name\", resolve=False),\n        \"as\",\n        Argument(\"varname\", resolve=False),\n        MultiValueArgument(\"extra_bits\", required=False, resolve=False),\n        blocks=[(\"endplaceholder_as_plugins\", \"nodelist\")],\n    )\n\n    # pylint: disable=arguments-renamed,too-many-arguments\n    def render_tag(self, context, name, varname, extra_bits, nodelist=None):\n        return get_plugins_render_tag(context, name, varname, nodelist)\n\n\n@register.tag(\"get_placeholder_plugins\")\nclass GetPlaceholderPlugins(Tag):\n    \"\"\"\n    A template tag that gets plugins from a page's placeholder and sets them as a context variable:\n\n        {% get_placeholder_plugins \"logo\" page_lookup as varname %}\n        {% get_placeholder_plugins \"logo\" page_lookup as varname or %}\n            <div>No content</div>\n        {% endget_placeholder_plugins %}\n\n    The page_lookup parameter can be omitted and will default to the current page\n\n        {% get_placeholder_plugins \"logo\" as varname %}\n        {% get_placeholder_plugins \"logo\" as varname or %}\n            <div>No content</div>\n        {% endget_placeholder_plugins %}\n\n    This tag can typically be used in association with the block_plugin tag,\n    to render the retrieved plugins:\n\n        {% get_placeholder_plugins \"logo\" page_lookup as plugins %}\n        {% blockplugin plugins.0 %}\n            <img src=\"{% thumbnail instance.picture 300x150 %}\"/>\n        {% endblockplugin %}\n\n    Keyword arguments:\n        name: the name of the placeholder\n        page_lookup: lookup argument for Page. See `_get_page_by_untyped_arg()`\n            for detailed information on the allowed types and their interpretation for the\n            `page_lookup` argument.\n        varname: context variable name. Output will be added to template context as this variable\n            instead of being returned.\n        or: optional argument which if given will make the template tag a block\n            tag whose content is shown if the placeholder is empty\n    \"\"\"\n\n    name = \"get_placeholder_plugins\"\n    options = PlaceholderOptions(\n        Argument(\"name\", resolve=False),\n        Argument(\"page_lookup\", required=False, default=None),\n        \"as\",\n        Argument(\"varname\", resolve=False),\n        MultiValueArgument(\"extra_bits\", required=False, resolve=False),\n        blocks=[(\"endget_placeholder_plugins\", \"nodelist\")],\n    )\n\n    # pylint: disable=arguments-differ,too-many-arguments, unused-argument\n    def render_tag(\n        self, context, name, page_lookup, varname, extra_bits, nodelist=None\n    ):\n        return get_plugins_render_tag(\n            context, name, varname, nodelist, page_lookup, edit=False\n        )\n\n\n@register.tag()\nclass BlockPlugin(Tag):\n    \"\"\"\n    Like DjangoCMS 'render_plugin_block' but only includes the edit script and markup when\n    the related placeholder is editable.\n\n    This issue was raised to DjangoCMS and we need our own template tag until they find a way\n    to fix it in DjangoCMS (https://github.com/divio/django-cms/issues/6683).\n    \"\"\"\n\n    name = \"blockplugin\"\n    template = \"cms/toolbar/plugin.html\"\n    options = Options(Argument(\"plugin\"), blocks=[(\"endblockplugin\", \"nodelist\")])\n\n    # pylint: disable=arguments-differ\n    def render_tag(self, context, plugin, nodelist):\n        \"\"\"\n        Renders the block for the plugin and returns the resulting HTML leaving the temmpate\n        context untouched.\n        If the placeholder is editable, the edit script and markup are added to the rendered HTML.\n        \"\"\"\n        request = context.get(\"request\")\n        if not plugin or not request:\n            return \"\"\n\n        # Add the plugin and its rendered content to an internal context\n        internal_context = flatten_context(context)\n        internal_context[\"instance\"] = plugin\n        internal_context[\"content\"] = nodelist.render(context.new(internal_context))\n\n        # Add the edit script and markup to the content, only if the placeholder is editable\n        # and the visited page is the one on which the plugin's placeholder is declared.\n        toolbar = get_toolbar_from_request(request)\n        if plugin.placeholder.page == request.current_page and toolbar.edit_mode_active:\n            return render_to_string(self.template, internal_context)\n\n        return internal_context[\"content\"]\n\n\n@register.filter()\ndef is_empty_placeholder(page, slot):\n    \"\"\"A template filter to determine if a placeholder is empty.\n\n    This is useful when we don't want to include any wrapper markup in our template unless\n    the placeholder unless it actually contains plugins.\n    \"\"\"\n    placeholder = page.placeholders.get(slot=slot)\n    return not placeholder.cmsplugin_set.exists()\n\n\n@register.filter()\ndef order_by(queryset, args):\n    \"\"\"A template filter to force ordering on a queryset.\n\n    Taken from: https://djangosnippets.org/snippets/741/\n    This is useful for DjangoCMS page querysets because we don't have access to the view.\n    \"\"\"\n    args = [x.strip() for x in args.split(\",\")]\n    return queryset.order_by(*args)\n\n\n@register.filter()\ndef has_connected_lms(course_run):\n    \"\"\"\n    Determine if the passed course run has a connected LMS (as determined through out LMSHandler\n    and settings).\n    This enables our templates to either use the <CourseRunEnrollment /> component or a simple\n    link to the course run.\n    \"\"\"\n    return LMSHandler.select_lms(course_run.resource_link) is not None\n\n\n@register.filter()\ndef visible_on_course_page(course_runs, edit_mode_active=None):\n    \"\"\"\n    Determine if the passed course run should be visible on the course page, if on edit mode\n    show all the course runs.\n    \"\"\"\n    if edit_mode_active:\n        return course_runs\n    return list(filter(lambda run: run.is_visible_on_course_page, course_runs))\n\n\n@register.filter()\ndef sort_runs_by_language_and_start_date(course_runs):\n    \"\"\"\n    Order course runs by: firstly runs that contains the language of the current user and only\n    after the runs that don't match the current user authenticated language. On both groups, they\n    should be sorted by course start date.\n    \"\"\"\n    current_language = translation.get_language()\n    return list(\n        sorted(\n            course_runs,\n            key=lambda run: (current_language not in run.languages, run.start),\n        )\n    )\n\n\n@register.simple_tag(takes_context=True)\ndef course_enrollment_widget_props(context):\n    \"\"\"\n    Return a json dumps which contains all course_run's properties required by\n    CourseEnrollment React widget\n    \"\"\"\n    course_run = context[\"run\"]\n\n    profile_urls = json.loads(\n        context.get(\"AUTHENTICATION\", {}).get(\"profile_urls\", \"{}\")\n    )\n    dashboard_link = profile_urls.get(\"dashboard\", {}).get(\"action\")\n\n    starts_in_message = None\n    if course_run.start > timezone.now():\n        course_start = arrow.get(course_run.start)\n        humanized_course_start = course_start.humanize(\n            arrow.now(), locale=to_locale(get_language())\n        )\n        # Translators: delay indicates when the course will start as a duration.\n        # In english the string will be \"The course will start in 3 days\"\n        starts_in_message = _(\"The course will start {delay:s}\").format(\n            delay=humanized_course_start\n        )\n\n    return json.dumps(\n        {\n            \"courseRun\": {\n                \"id\": course_run.id,\n                \"resource_link\": course_run.resource_link,\n                \"priority\": course_run.state[\"priority\"],\n                \"starts_in_message\": starts_in_message,\n                \"dashboard_link\": dashboard_link,\n            }\n        }\n    )\n\n\n@register.filter\n@stringfilter\ndef trim(value):\n    \"\"\"\n    Remove whitespaces before and after a string.\n    \"\"\"\n    return value.strip()\n", "path": "src/richie/apps/courses/templatetags/extra_tags.py"}]}