diff --git a/taccsite_cms/contrib/taccsite_data_list/TODO.md b/taccsite_cms/contrib/taccsite_data_list/TODO.md new file mode 100644 index 000000000..10ed2591a --- /dev/null +++ b/taccsite_cms/contrib/taccsite_data_list/TODO.md @@ -0,0 +1,3 @@ +# To Do + +- [ ] Support label that is link within text. → GH-306 diff --git a/taccsite_cms/contrib/taccsite_data_list/__init__.py b/taccsite_cms/contrib/taccsite_data_list/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/taccsite_cms/contrib/taccsite_data_list/cms_plugins.py b/taccsite_cms/contrib/taccsite_data_list/cms_plugins.py new file mode 100644 index 000000000..4f280c658 --- /dev/null +++ b/taccsite_cms/contrib/taccsite_data_list/cms_plugins.py @@ -0,0 +1,123 @@ +from cms.plugin_base import CMSPluginBase +from cms.plugin_pool import plugin_pool +from django.utils.translation import gettext_lazy as _ + +from taccsite_cms.contrib.helpers import concat_classnames +from taccsite_cms.contrib.taccsite_offset.cms_plugins import get_direction_classname + +from taccsite_cms.contrib.helpers import AbstractMaxChildrenPlugin + +from .models import TaccsiteDataList, TaccsiteDataListItem +from .constants import ORIENTATION_DICT, TYPE_STYLE_DICT, DENSITY_DICT + + + +# Helpers + +def get_classname(dict, value): + """Get layout class based on value.""" + return dict.get(value, {}).get('classname') + + + +# Plugins + +@plugin_pool.register_plugin +class TaccsiteDataListPlugin(CMSPluginBase, AbstractMaxChildrenPlugin): + """ + Components > "Data List" Plugin + https://confluence.tacc.utexas.edu/x/EiIFDg + """ + module = 'TACC Site' + model = TaccsiteDataList + name = _('Data List') + render_template = 'data_list.html' + + cache = True + text_enabled = True + allow_children = True + child_classes = [ + 'TaccsiteDataListItemPlugin' + ] + + fieldsets = [ + (_('Required configuration'), { + 'fields': ( + 'type_style', + 'orientation', + 'density', + ) + }), + (_('Optional configuration'), { + 'fields': ( + 'truncate_values', + ) + }), + (_('Advanced settings'), { + 'classes': ('collapse',), + 'fields': ( + 'attributes', + ) + }), + ] + + # Render + + def render(self, context, instance, placeholder): + context = super().render(context, instance, placeholder) + request = context['request'] + + classes = concat_classnames([ + 'c-data-list', + get_classname(ORIENTATION_DICT, instance.orientation), + get_classname(TYPE_STYLE_DICT, instance.type_style), + get_classname(DENSITY_DICT, instance.density), + 'c-data-list--should-truncate-values' + if instance.truncate_values else '', + instance.attributes.get('class'), + ]) + instance.attributes['class'] = classes + + return context + +@plugin_pool.register_plugin +class TaccsiteDataListItemPlugin(CMSPluginBase): + """ + Components > "Data List Item" Plugin + https://confluence.tacc.utexas.edu/x/EiIFDg + """ + module = 'TACC Site' + model = TaccsiteDataListItem + name = _('Data List Item') + render_template = 'data_list_item.html' + + cache = True + text_enabled = False + allow_children = True + child_classes = [ + 'LinkPlugin' + ] + max_children = 1 # Only a label until we know what value will need + + fieldsets = [ + (None, { + 'fields': ( + ('key', 'value'), + ('use_plugin_as_key'), + ) + }) + ] + + # Render + + def render(self, context, instance, placeholder): + context = super().render(context, instance, placeholder) + request = context['request'] + + parent_plugin_instance = instance.parent.get_plugin_instance()[0] + + context.update({ + 'parent_plugin_instance': parent_plugin_instance + }) + + return context diff --git a/taccsite_cms/contrib/taccsite_data_list/constants.py b/taccsite_cms/contrib/taccsite_data_list/constants.py new file mode 100644 index 000000000..8a27bd084 --- /dev/null +++ b/taccsite_cms/contrib/taccsite_data_list/constants.py @@ -0,0 +1,38 @@ +# TODO: Consider using an Enum (and an Abstract Enum with `get_choices` and `get_classname` methods) + +ORIENTATION_DICT = { + 'horizontal': { + 'classname': 'c-data-list--is-horz', + 'description': 'Horizontal (all data on one row)', + 'short_description': 'Horizontal', + }, + 'vertical': { + 'classname': 'c-data-list--is-vert', + 'description': 'Vertical (every label and value has its own row)', + 'short_description': 'Vertical', + }, +} + +TYPE_STYLE_DICT = { + 'table': { + 'description': 'Table (e.g. Columns)', + 'short_description': 'Table', + }, + 'dlist': { + 'description': 'List (e.g. Glossary, Metadata)', + 'short_description': 'List', + }, +} + +DENSITY_DICT = { + 'default': { + 'classname': 'c-data-list--is-wide', + 'description': 'Default (ample extra space)', + 'short_description': 'Default', + }, + 'compact': { + 'classname': 'c-data-list--is-narrow', + 'description': 'Compact (minimal extra space)', + 'short_description': 'Compact', + }, +} diff --git a/taccsite_cms/contrib/taccsite_data_list/migrations/0001_initial.py b/taccsite_cms/contrib/taccsite_data_list/migrations/0001_initial.py new file mode 100644 index 000000000..2c82520b9 --- /dev/null +++ b/taccsite_cms/contrib/taccsite_data_list/migrations/0001_initial.py @@ -0,0 +1,46 @@ +# Generated by Django 2.2.16 on 2021-08-06 20:17 + +from django.db import migrations, models +import django.db.models.deletion +import djangocms_attributes_field.fields + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('cms', '0022_auto_20180620_1551'), + ] + + operations = [ + migrations.CreateModel( + name='TaccsiteDataList', + fields=[ + ('cmsplugin_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, related_name='taccsite_data_list_taccsitedatalist', serialize=False, to='cms.CMSPlugin')), + ('orientation', models.CharField(choices=[('horizontal', 'Horizontal (all data on one row)'), ('vertical', 'Vertical (every label and value has its own row)')], help_text='The direction in which to lay out the data. Hint: Choose based on the amount of space available in the layout for the data.', max_length=255, verbose_name='Orientation')), + ('type_style', models.CharField(choices=[('table', 'Table (e.g. Columns)'), ('dlist', 'List (e.g. Glossary, Metadata)')], help_text='The type of data to display, glossary/metadata or tabular. Notice: Each type of list has a slightly different style.', max_length=255, verbose_name='Type / Style')), + ('density', models.CharField(choices=[('default', 'Default (ample extra space)'), ('compact', 'Compact (minimal extra space)')], help_text='The amount of extra space in the layout. Hint: Choose based on the amount of space available in the layout for the data.', max_length=255, verbose_name='Density (Layout Spacing)')), + ('truncate_values', models.BooleanField(default=False, help_text='Truncate values if there is not enough space to show the label and the value. Notice: Labels are truncated as necessary.', verbose_name='Truncate the values (as necessary)')), + ('attributes', djangocms_attributes_field.fields.AttributesField(default=dict)), + ], + options={ + 'abstract': False, + }, + bases=('cms.cmsplugin',), + ), + migrations.CreateModel( + name='TaccsiteDataListItem', + fields=[ + ('cmsplugin_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, related_name='taccsite_data_list_taccsitedatalistitem', serialize=False, to='cms.CMSPlugin')), + ('key', models.CharField(help_text='A label for the data value. To create a link, add a child plugin.', max_length=50, verbose_name='Label')), + ('value', models.CharField(help_text='The data value.', max_length=100, verbose_name='Value')), + ('use_plugin_as_key', models.BooleanField(default=True, help_text='If a child plugin is added, and this option is checked, then the child plugin will be used (not the Label field text).', verbose_name='Support child plugin for Label')), + ('attributes', djangocms_attributes_field.fields.AttributesField(default=dict)), + ], + options={ + 'abstract': False, + }, + bases=('cms.cmsplugin',), + ), + ] diff --git a/taccsite_cms/contrib/taccsite_data_list/migrations/__init__.py b/taccsite_cms/contrib/taccsite_data_list/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/taccsite_cms/contrib/taccsite_data_list/models.py b/taccsite_cms/contrib/taccsite_data_list/models.py new file mode 100644 index 000000000..4d1190647 --- /dev/null +++ b/taccsite_cms/contrib/taccsite_data_list/models.py @@ -0,0 +1,99 @@ +from cms.models.pluginmodel import CMSPlugin + +from django.db import models +from django.utils.translation import gettext_lazy as _ + +from djangocms_attributes_field import fields + +from taccsite_cms.contrib.helpers import get_choices + +from .constants import ORIENTATION_DICT, TYPE_STYLE_DICT, DENSITY_DICT + + + +# Constants + +ORIENTATION_CHOICES = get_choices(ORIENTATION_DICT) +TYPE_STYLE_CHOICES = get_choices(TYPE_STYLE_DICT) +DENSITY_CHOICES = get_choices(DENSITY_DICT) + + + +# Models + +class TaccsiteDataList(CMSPlugin): + """ + Components > "Data List" Model + """ + orientation = models.CharField( + verbose_name=_('Orientation'), + help_text=_('The direction in which to lay out the data. Hint: Choose based on the amount of space available in the layout for the data.'), + choices=ORIENTATION_CHOICES, + blank=False, + max_length=255, + ) + type_style = models.CharField( + verbose_name=_('Type / Style'), + help_text=_('The type of data to display, glossary/metadata or tabular. Notice: Each type of list has a slightly different style.'), + choices=TYPE_STYLE_CHOICES, + blank=False, + max_length=255, + ) + density = models.CharField( + verbose_name=_('Density (Layout Spacing)'), + help_text=_('The amount of extra space in the layout. Hint: Choose based on the amount of space available in the layout for the data.'), + choices=DENSITY_CHOICES, + blank=False, + max_length=255, + ) + truncate_values = models.BooleanField( + verbose_name=_('Truncate the values (as necessary)'), + help_text=_('Truncate values if there is not enough space to show the label and the value. Notice: Labels are truncated as necessary.'), + default=False, + ) + + attributes = fields.AttributesField() + + def get_short_description(self): + orientation = ORIENTATION_DICT[self.orientation]['short_description'] + type_style = TYPE_STYLE_DICT[self.type_style]['short_description'] + density = DENSITY_DICT[self.density]['short_description'] + + return density + ', ' + orientation + ' ' + type_style + +class TaccsiteDataListItem(CMSPlugin): + """ + Components > "Data List Item" Model + """ + key = models.CharField( + verbose_name=_('Label'), + help_text=_('A label for the data value. To create a link, add a child plugin.'), + blank=False, + max_length=50, + ) + value = models.CharField( + verbose_name=_('Value'), + help_text=_('The data value.'), + blank=False, + max_length=100, + ) + use_plugin_as_key = models.BooleanField( + verbose_name=_('Support child plugin for Label'), + help_text=_('If a child plugin is added, and this option is checked, then the child plugin will be used (not the Label field text).'), + default=True, + ) + + attributes = fields.AttributesField() + + def get_short_description(self): + key = self.key + val = self.value + max_len = 4 + + should_truncate_key = len(key) > max_len + key_desc = key[0:max_len] + '…' if should_truncate_key else key + + should_truncate_val = len(key) > max_len + val_desc = val[0:max_len] + '…' if should_truncate_val else val + + return key_desc + ': ' + val_desc diff --git a/taccsite_cms/contrib/taccsite_data_list/templates/data_list.html b/taccsite_cms/contrib/taccsite_data_list/templates/data_list.html new file mode 100644 index 000000000..d6c38f17c --- /dev/null +++ b/taccsite_cms/contrib/taccsite_data_list/templates/data_list.html @@ -0,0 +1,21 @@ +{% load cms_tags %} + +{% if instance.type_style == 'dlist' %} + +
+ {% for plugin_instance in instance.child_plugin_instances %} + {% render_plugin plugin_instance %} + {% endfor %} +
+ +{% elif instance.type_style == 'table' %} + + + + {% for plugin_instance in instance.child_plugin_instances %} + {% render_plugin plugin_instance %} + {% endfor %} + +
+ +{% endif %} diff --git a/taccsite_cms/contrib/taccsite_data_list/templates/data_list_item.html b/taccsite_cms/contrib/taccsite_data_list/templates/data_list_item.html new file mode 100644 index 000000000..0ed71f277 --- /dev/null +++ b/taccsite_cms/contrib/taccsite_data_list/templates/data_list_item.html @@ -0,0 +1,17 @@ +{% if parent_plugin_instance.type_style == 'dlist' %} + +
+ {% include "./data_list_item_key.html" %} +
+
{{ instance.value }}
+ +{% elif parent_plugin_instance.type_style == 'table' %} + + + + {% include "./data_list_item_key.html" %} + + {{ instance.value }} + + +{% endif %} diff --git a/taccsite_cms/contrib/taccsite_data_list/templates/data_list_item_key.html b/taccsite_cms/contrib/taccsite_data_list/templates/data_list_item_key.html new file mode 100644 index 000000000..a6cc3ad86 --- /dev/null +++ b/taccsite_cms/contrib/taccsite_data_list/templates/data_list_item_key.html @@ -0,0 +1,9 @@ +{% load cms_tags %} + +{% if instance.use_plugin_as_key and instance.child_plugin_instances|length %} + {% for plugin_instance in instance.child_plugin_instances %} + {% render_plugin plugin_instance %} + {% endfor %} +{% else %} + {{ instance.key }} +{% endif %} diff --git a/taccsite_cms/settings.py b/taccsite_cms/settings.py index 63f68a01f..c75541900 100644 --- a/taccsite_cms/settings.py +++ b/taccsite_cms/settings.py @@ -250,6 +250,7 @@ def getsecrets(): 'taccsite_cms.contrib.taccsite_offset', 'taccsite_cms.contrib.taccsite_sysmon', 'taccsite_cms.contrib.taccsite_system_specs', + 'taccsite_cms.contrib.taccsite_data_list', ] # Convert list of paths to list of dotted module names