Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions designsafe/apps/workspace/cms_plugins.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""CMS plugins for Tools & Applications pages."""

import logging
from typing import Optional, Union
from cms.plugin_base import CMSPluginBase
from cms.plugin_pool import plugin_pool
from designsafe.apps.workspace.models.app_entries import (
Expand All @@ -10,11 +11,30 @@
AppCategoryListingPlugin,
RelatedAppsPlugin,
AppVariantsPlugin,
AppUserGuideLinkPlugin,
)
from designsafe.apps.workspace.forms import AppUserGuideLinkPluginForm

logger = logging.getLogger(__name__)


def get_entry_instance(url):
"""Helper function to get an AppListingEntry instance based on URL."""

app_listing_entries = AppListingEntry.objects.filter(enabled=True)
for entry in app_listing_entries:
if entry.href in url:
return entry

return None

def is_editing_text(url):
"""Helper function to determine if the current context is editing text."""

# e.g. /…/page/edit-plugin/123/, /…/page/plugin/text_plugin/render-plugin/
return 'admin/cms/page' in url


class AppCategoryListing(CMSPluginBase):
"""CMS plugin to render the list of apps for a given category."""

Expand Down Expand Up @@ -53,6 +73,7 @@ def render(self, context, instance, placeholder):

class RelatedApps(CMSPluginBase):
"""CMS plugin to render related apps."""
# IDEA: Use get_entry_instance if model field is empty (i.e. auto value)

model = RelatedAppsPlugin
name = "Related Apps"
Expand Down Expand Up @@ -89,6 +110,7 @@ def render(self, context, instance: AppListingEntry, placeholder):

class AppVariants(CMSPluginBase):
"""CMS plugin to render an apps versions/variants."""
# IDEA: Use get_entry_instance if model field is empty (i.e. auto value)

model = AppVariantsPlugin
name = "App Version Selection"
Expand All @@ -105,3 +127,37 @@ def render(self, context, instance: AppListingEntry, placeholder):


plugin_pool.register_plugin(AppVariants)


class AppUserGuideLink(CMSPluginBase):
"""CMS plugin to render the user guide link."""

model = AppUserGuideLinkPlugin
form = AppUserGuideLinkPluginForm
name = "App User Guide Link"
module = "Tools & Applications"
render_template = "designsafe/apps/workspace/app_user_guide_link_plugin.html"
text_enabled = True
cache = False

def render(
self,
context,
instance: Optional[Union[AppUserGuideLinkPlugin, AppListingEntry]] = None,
placeholder=None,
):
plugin_instance = instance if isinstance(instance, AppUserGuideLinkPlugin) else None

instance_app = None
if isinstance(instance, AppUserGuideLinkPlugin):
instance_app = getattr(instance, "app", None)
if instance_app is None:
instance_app = get_entry_instance(context.get("request").path)

context = super().render(context, plugin_instance, placeholder)
context["user_guide_link"] = getattr(instance_app, "user_guide_link", None)
context["is_editing_text"] = is_editing_text(context.get("request").path)
return context


plugin_pool.register_plugin(AppUserGuideLink)
16 changes: 16 additions & 0 deletions designsafe/apps/workspace/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from django.forms import ModelForm

from designsafe.apps.workspace.models.app_entries import AppVariant
from designsafe.apps.workspace.models.app_cms_plugins import AppUserGuideLinkPlugin


class AppVariantForm(ModelForm):
Expand All @@ -20,3 +21,18 @@ class Meta:
" only after save."
),
}

class AppUserGuideLinkPluginForm(ModelForm):
"""Customizes app user guide link plugin admin form"""

class Meta:
"""To add or change help text"""

model = AppUserGuideLinkPlugin
fields = '__all__'

help_texts = {
"app": (
"If no app is selected, then app can be derived from URL path."
),
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Generated by Django 4.2.20 on 2025-09-22 21:33

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):
dependencies = [
("cms", "0022_auto_20180620_1551"),
("workspace", "0018_appvariant_external_href"),
]

operations = [
migrations.CreateModel(
name="AppUserGuideLinkPlugin",
fields=[
(
"cmsplugin_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
related_name="%(app_label)s_%(class)s",
serialize=False,
to="cms.cmsplugin",
),
),
(
"app",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
to="workspace.applistingentry",
),
),
],
options={
"abstract": False,
},
bases=("cms.cmsplugin",),
),
]
14 changes: 14 additions & 0 deletions designsafe/apps/workspace/models/app_cms_plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,17 @@ class AppVariantsPlugin(CMSPlugin):

def __str__(self):
return self.app.label


class AppUserGuideLinkPlugin(CMSPlugin):
"""Model for rendering an app user guide link."""

app = models.ForeignKey(
to=AppListingEntry,
on_delete=models.deletion.CASCADE,
null=True,
blank=True,
)

def __str__(self):
return self.app.label if self.app else "Based on URL"
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{# @var user_guide_link #}

{% if user_guide_link or is_editing_text %}
<a
class="btn btn-secondary"
{% if is_editing_text %}
href="__DYNAMIC__"
{% else %}
href="{{ user_guide_link }}"
{% endif %}
target="_blank"
aria-describedby="msg-open-new-window"
>User Guide</a>
{% else %}
<span>User guide not found.</span>
{% endif %}
3 changes: 2 additions & 1 deletion designsafe/settings/common_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,8 @@
'ResponsiveEmbedPlugin',
'AppCategoryListing',
'RelatedApps',
'AppVariants'
'AppVariants',
'AppUserGuideLink',
)
}
CMSPLUGIN_CASCADE_PLUGINS = [
Expand Down