We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Improve meta description on each type of page
## Feature Request

**Is your feature request related to a problem or unsupported use case? Please describe.**
The meta description is filled via the "meta_description" field on the page. This field is rarely filled whereas we have some interesting information on each page that could be used to fill it.

**Describe the solution you'd like**
If the "meta_description" field is filled on a page, use it. Otherwise, use the following information:
- blogpost page : use  first few words of the "excerpt" placeholder OR "body" placeholder
- category  use first few words of the "description" placeholder
- course page : use first few words of the "course_introduction" placeholder OR "course_description" placeholder. Maybe start by the state of the course and the title of the main organization if any?
- organization page : use  first few words of the "description" placeholder
- person page : use  first few words of the "bio" placeholder OR "maincontent" placeholder
- program page : use  first few words of the "program_excerpt" OR "program_body" placeholder

**Discovery, Documentation, Adoption, Migration Strategy**
Trunk the content used for the meta description to **200 characters** (150 recommended).


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

{"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@@ -3,6 +3,7 @@\n \n from django import template\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.translation import get_language\n@@ -273,3 +274,12 @@\n             }\n         }\n     )\n+\n+\n+@register.filter\n+@stringfilter\n+def trim(value):\n+    \"\"\"\n+    Remove whitespaces before and after a string.\n+    \"\"\"\n+    return value.strip()\n", "issue": "Improve meta description on each type of page\n## Feature Request\r\n\r\n**Is your feature request related to a problem or unsupported use case? Please describe.**\r\nThe meta description is filled via the \"meta_description\" field on the page. This field is rarely filled whereas we have some interesting information on each page that could be used to fill it.\r\n\r\n**Describe the solution you'd like**\r\nIf the \"meta_description\" field is filled on a page, use it. Otherwise, use the following information:\r\n- blogpost page : use  first few words of the \"excerpt\" placeholder OR \"body\" placeholder\r\n- category  use first few words of the \"description\" placeholder\r\n- course page : use first few words of the \"course_introduction\" placeholder OR \"course_description\" placeholder. Maybe start by the state of the course and the title of the main organization if any?\r\n- organization page : use  first few words of the \"description\" placeholder\r\n- person page : use  first few words of the \"bio\" placeholder OR \"maincontent\" placeholder\r\n- program page : use  first few words of the \"program_excerpt\" OR \"program_body\" placeholder\r\n\r\n**Discovery, Documentation, Adoption, Migration Strategy**\r\nTrunk the content used for the meta description to **200 characters** (150 recommended).\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.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\ndef get_plugins_render_tag(context, name, varname, nodelist, page_lookup=None):\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        # and the visited page is the one on which the placeholder is declared.\n        toolbar = get_toolbar_from_request(request)\n        if placeholder.page == request.current_page and 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(\"get_placeholder_plugins\")\nclass GetPlaceholderPlugins(Placeholder):\n    \"\"\"\n    A template tag that declares a placeholder and sets its plugins as a context variable\n    instead of rendering them eg:\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, to customize the\n    way it is rendered eg:\n\n        {% get_placeholder_plugins \"logo\" 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        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    Note: We must derive from the Placeholder class so that the tag is recognized as a\n          placeholder and shown in the structure toolbar.\n    \"\"\"\n\n    name = \"get_placeholder_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=[(\"endget_placeholder_plugins\", \"nodelist\")],\n    )\n\n    # pylint: disable=arguments-differ,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_page_plugins\")\nclass GetPagePlugins(Tag):\n    \"\"\"\n    A template tag that gets plugins from a page's placeholder returns them as a context variable:\n\n        {% get_page_plugins \"logo\" page_lookup as varname %}\n        {% get_page_plugins \"logo\" page_lookup as varname or %}\n            <div>No content</div>\n        {% endget_page_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_page_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_page_plugins\"\n    options = PlaceholderOptions(\n        Argument(\"name\", resolve=False),\n        Argument(\"page_lookup\"),\n        \"as\",\n        Argument(\"varname\", resolve=False),\n        MultiValueArgument(\"extra_bits\", required=False, resolve=False),\n        blocks=[(\"endget_page_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(context, name, varname, nodelist, page_lookup)\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 placholder 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", "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\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\ndef get_plugins_render_tag(context, name, varname, nodelist, page_lookup=None):\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        # and the visited page is the one on which the placeholder is declared.\n        toolbar = get_toolbar_from_request(request)\n        if placeholder.page == request.current_page and 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(\"get_placeholder_plugins\")\nclass GetPlaceholderPlugins(Placeholder):\n    \"\"\"\n    A template tag that declares a placeholder and sets its plugins as a context variable\n    instead of rendering them eg:\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, to customize the\n    way it is rendered eg:\n\n        {% get_placeholder_plugins \"logo\" 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        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    Note: We must derive from the Placeholder class so that the tag is recognized as a\n          placeholder and shown in the structure toolbar.\n    \"\"\"\n\n    name = \"get_placeholder_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=[(\"endget_placeholder_plugins\", \"nodelist\")],\n    )\n\n    # pylint: disable=arguments-differ,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_page_plugins\")\nclass GetPagePlugins(Tag):\n    \"\"\"\n    A template tag that gets plugins from a page's placeholder returns them as a context variable:\n\n        {% get_page_plugins \"logo\" page_lookup as varname %}\n        {% get_page_plugins \"logo\" page_lookup as varname or %}\n            <div>No content</div>\n        {% endget_page_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_page_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_page_plugins\"\n    options = PlaceholderOptions(\n        Argument(\"name\", resolve=False),\n        Argument(\"page_lookup\"),\n        \"as\",\n        Argument(\"varname\", resolve=False),\n        MultiValueArgument(\"extra_bits\", required=False, resolve=False),\n        blocks=[(\"endget_page_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(context, name, varname, nodelist, page_lookup)\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 placholder 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"}]}