diff --git a/taccsite_cms/contrib/taccsite_system_specs/TODO.md b/taccsite_cms/contrib/taccsite_system_specs/TODO.md new file mode 100644 index 000000000..a5a876fda --- /dev/null +++ b/taccsite_cms/contrib/taccsite_system_specs/TODO.md @@ -0,0 +1,32 @@ +# To Do + +## Preparation for Urgent + +- [x] Re-word "Read More / See All" ticket as "- Styles" ticket. + +## Urgent + +- [x] Undo initial commit and create indpendent branches for: + - [x] `c-see-all-link` → `task/GH-298-add-see-all-component` + - [x] `c-data-list` → `task/GH-75-styles` + - [x] plan for not loading new components in `site.css` → (merged to `main`) + - [x] changes to `taccsite_sysmon` → `task/GH-89` + - [x] changes to `x-truncate` → (merged to `main`) + +## Problems to Fix + +- [x] Prevent duplicate entires in "Internal Link". +- [ ] Add missing link feature to "Data List Item" plugin. → GH-75 +- [ ] Find makeshift way to list dependency plugins i.e. taccsite_data_list. + +## Continued Development + +- [x] Add "Data List" plugin. → GH-75 +- [x] Add "Data List Item" plugin. → GH-75 +- [x] Integrate "Data List(…)" plugins into "System Specs" plugin. +- [x] Create new "See All - Component" ticket. → GH-298 +- [ ] Style the new markup. + +## Related Development Planning + +- [x] Create new "See All - Plugin" ticket. → GH-299 diff --git a/taccsite_cms/contrib/taccsite_system_specs/__init__.py b/taccsite_cms/contrib/taccsite_system_specs/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/taccsite_cms/contrib/taccsite_system_specs/cms_plugins.py b/taccsite_cms/contrib/taccsite_system_specs/cms_plugins.py new file mode 100644 index 000000000..a8377b10d --- /dev/null +++ b/taccsite_cms/contrib/taccsite_system_specs/cms_plugins.py @@ -0,0 +1,124 @@ +from cms.plugin_pool import plugin_pool +from django.utils.translation import gettext_lazy as _ + +from djangocms_link.cms_plugins import LinkPlugin + +from taccsite_cms.contrib.constants import TEXT_FOR_NESTED_PLUGIN_CONTENT_ADD +from taccsite_cms.contrib.helpers import concat_classnames + +from .constants import DEFAULT_OTHER_TITLE + +from .models import TaccsiteSystemSpecs + +@plugin_pool.register_plugin +class TaccsiteSystemSpecsPlugin(LinkPlugin): + """ + Components > "System Specs" Plugin + """ + module = 'TACC Site' + model = TaccsiteSystemSpecs + name = _('System Specs') + render_template = 'system_specs.html' + def get_render_template(self, context, instance, placeholder): + return self.render_template + + cache = True + text_enabled = False + allow_children = True + child_classes = [ + 'TaccsiteSystemMonitorPlugin', + 'PicturePlugin', + 'Bootstrap4PicturePlugin', + 'TaccsiteDataListPlugin', + ] + + fieldsets = [ + (_('Specifications'), { + 'fields': ( + 'system_desc', + 'system_processor_count', + 'system_processor_type', + 'system_node_ram', + 'system_network', + 'system_performance', + 'system_memory', + ) + }), + (_('Footer link'), { + 'classes': ('collapse',), + 'description': 'The "See More Detailed Specs" link at the bottom of the list. "Display name" is for alternate link text.', + 'fields': ( + ('external_link', 'internal_link'), + ('anchor', 'target'), + 'name', + ) + }), + (_('Subsystems and/or resources - Introduction'), { + 'fields': ( + 'other_title', + 'other_desc', + ) + }), + (_('Subsystems and/or resources - Data'), { + 'classes': ('collapse',), + 'description': TEXT_FOR_NESTED_PLUGIN_CONTENT_ADD.format( + element='data', + plugin_name='Data List' + ), + 'fields': () + }), + (_('Image'), { + 'classes': ('collapse',), + 'description': TEXT_FOR_NESTED_PLUGIN_CONTENT_ADD.format( + element='an image', + plugin_name='(…) Image' + ), + 'fields': () + }), + (_('System monitor'), { + 'classes': ('collapse',), + 'description': TEXT_FOR_NESTED_PLUGIN_CONTENT_ADD.format( + element='a system monitor', + plugin_name='(…) System Monitor' + ), + 'fields': () + }), + (_('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([ + 's-system-specs', + instance.attributes.get('class'), + ]) + instance.attributes['class'] = classes + + # To identify child plugins + for plugin_instance in instance.child_plugin_instances: + print(type(plugin_instance).__name__) + if (type(plugin_instance).__name__ == 'TaccsiteSysmon'): + context.update({ 'sysmon_plugin': plugin_instance }) + if (type(plugin_instance).__name__ == 'Picture' or + type(plugin_instance).__name__ == 'Bootstrap4Picture'): + context.update({ 'image_plugin': plugin_instance }) + if (type(plugin_instance).__name__ == 'TaccsiteDataList'): + context.update({ 'data_plugin': plugin_instance }) + + context.update({ + 'default_other_title': DEFAULT_OTHER_TITLE, + 'has_other': instance.other_title or instance.other_desc, + 'link_url': instance.get_link(), + 'link_text': instance.name, + 'link_target': instance.target + }) + return context diff --git a/taccsite_cms/contrib/taccsite_system_specs/constants.py b/taccsite_cms/contrib/taccsite_system_specs/constants.py new file mode 100644 index 000000000..c6dc34ceb --- /dev/null +++ b/taccsite_cms/contrib/taccsite_system_specs/constants.py @@ -0,0 +1 @@ +DEFAULT_OTHER_TITLE = 'Subsystems and Associated Resources' diff --git a/taccsite_cms/contrib/taccsite_system_specs/migrations/0001_initial.py b/taccsite_cms/contrib/taccsite_system_specs/migrations/0001_initial.py new file mode 100644 index 000000000..9bea875f1 --- /dev/null +++ b/taccsite_cms/contrib/taccsite_system_specs/migrations/0001_initial.py @@ -0,0 +1,49 @@ +# Generated by Django 2.2.16 on 2021-08-06 04:17 + +from django.db import migrations, models +import django.db.models.deletion +import djangocms_attributes_field.fields +import djangocms_link.validators +import filer.fields.file + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('filer', '0012_file_mime_type'), + ('cms', '0022_auto_20180620_1551'), + ] + + operations = [ + migrations.CreateModel( + name='TaccsiteSystemSpecs', + fields=[ + ('template', models.CharField(choices=[('default', 'Default')], default='default', max_length=255, verbose_name='Template')), + ('name', models.CharField(blank=True, max_length=255, verbose_name='Display name')), + ('external_link', models.CharField(blank=True, help_text='Provide a link to an external source.', max_length=2040, validators=[djangocms_link.validators.IntranetURLValidator(intranet_host_re=None)], verbose_name='External link')), + ('anchor', models.CharField(blank=True, help_text='Appends the value only after the internal or external link. Do not include a preceding "#" symbol.', max_length=255, verbose_name='Anchor')), + ('mailto', models.EmailField(blank=True, max_length=255, verbose_name='Email address')), + ('phone', models.CharField(blank=True, max_length=255, verbose_name='Phone')), + ('target', models.CharField(blank=True, choices=[('_blank', 'Open in new window'), ('_self', 'Open in same window'), ('_parent', 'Delegate to parent'), ('_top', 'Delegate to top')], max_length=255, verbose_name='Target')), + ('attributes', djangocms_attributes_field.fields.AttributesField(blank=True, default=dict, verbose_name='Attributes')), + ('cmsplugin_ptr', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, related_name='taccsite_system_specs_taccsitesystemspecs', serialize=False, to='cms.CMSPlugin')), + ('system_desc', models.TextField(default='', help_text='Description of the system machine and mission.', verbose_name='System Description')), + ('system_processor_count', models.IntegerField(blank=True, help_text='The number of processors in the system', null=True, verbose_name='Processors')), + ('system_processor_type', models.CharField(blank=True, help_text='The number of processors in the system', max_length=50, verbose_name='Processor Type')), + ('system_node_ram', models.CharField(blank=True, help_text='The amount of RAM in each node of the system, including the unit. (Reminder: Type "GB" for Gigabyte, not "Gb".)', max_length=50, verbose_name='RAM per Node')), + ('system_network', models.CharField(blank=True, help_text='The network hardware in the system. (Reminder: Type "Gb" for Gigabit, not GB.)', max_length=50, verbose_name='Network')), + ('system_performance', models.CharField(blank=True, help_text='The peak performance of the system, including the unit. (Reminder: Type number, then space, then unit; example: "38.8 PetaFLOPS".)', max_length=50, verbose_name='Peak Performance')), + ('system_memory', models.CharField(blank=True, help_text='The amount of memory for the system, including the unit. (Reminder: Type "PB" for Petabyte, not "Pb".)', max_length=50, verbose_name='Memory')), + ('other_title', models.CharField(blank=True, help_text='An alternate title to replace "Subsystems and Associated Resources".', max_length=40, verbose_name='Alternate Resources Title')), + ('other_desc', models.TextField(blank=True, default='', help_text='Description of "Subsystems and Associated Resources".', verbose_name='Resources Description')), + ('file_link', filer.fields.file.FilerFileField(blank=True, help_text='If provided links a file from the filer app.', null=True, on_delete=django.db.models.deletion.SET_NULL, to='filer.File', verbose_name='File link')), + ('internal_link', models.ForeignKey(blank=True, help_text='If provided, overrides the external link.', null=True, on_delete=django.db.models.deletion.SET_NULL, to='cms.Page', verbose_name='Internal link')), + ], + options={ + 'abstract': False, + }, + bases=('cms.cmsplugin',), + ), + ] diff --git a/taccsite_cms/contrib/taccsite_system_specs/migrations/__init__.py b/taccsite_cms/contrib/taccsite_system_specs/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/taccsite_cms/contrib/taccsite_system_specs/models.py b/taccsite_cms/contrib/taccsite_system_specs/models.py new file mode 100644 index 000000000..e3941e30d --- /dev/null +++ b/taccsite_cms/contrib/taccsite_system_specs/models.py @@ -0,0 +1,95 @@ +from django.core.exceptions import ValidationError +from django.utils.translation import gettext_lazy as _ + +from django.db import models + +from djangocms_link.models import AbstractLink + +from taccsite_cms.contrib.helpers import clean_for_abstract_link + +from .constants import DEFAULT_OTHER_TITLE + +class TaccsiteSystemSpecs(AbstractLink): + """ + Components > "System Specs" Model + """ + system_desc = models.TextField( + verbose_name=_('System Description'), + help_text=_('Description of the system machine and mission.'), + blank=False, + default='' + ) + system_processor_count = models.IntegerField( + verbose_name=_('Processors'), + help_text=_('The number of processors in the system'), + blank=True, + null=True, + # HELP: Is it worth it to create a min. value zero to avoid neg. values? + # SEE: https://stackoverflow.com/a/849426/11817077 + # min_value=0, + ) + system_processor_type = models.CharField( + verbose_name=_('Processor Type'), + help_text=_('The number of processors in the system'), + blank=True, + max_length=50, + ) + system_node_ram = models.CharField( + verbose_name=_('RAM per Node'), + help_text=_('The amount of RAM in each node of the system, including the unit. (Reminder: Type "GB" for Gigabyte, not "Gb".)'), + blank=True, + max_length=50, + ) + system_network = models.CharField( + verbose_name=_('Network'), + help_text=_('The network hardware in the system. (Reminder: Type "Gb" for Gigabit, not GB.)'), + blank=True, + max_length=50, + ) + system_performance = models.CharField( + verbose_name=_('Peak Performance'), + help_text=_('The peak performance of the system, including the unit. (Reminder: Type number, then space, then unit; example: "38.8 PetaFLOPS".)'), + blank=True, + max_length=50, + ) + system_memory = models.CharField( + verbose_name=_('Memory'), + help_text=_('The amount of memory for the system, including the unit. (Reminder: Type "PB" for Petabyte, not "Pb".)'), + blank=True, + max_length=50, + ) + + other_title = models.CharField( + verbose_name=_('Alternate Resources Title'), + help_text=_('An alternate title to replace "%(default_value)s".') % { 'default_value': DEFAULT_OTHER_TITLE }, + blank=True, + max_length=40, # Based on approx. space available in design + ) + other_desc = models.TextField( + verbose_name=_('Resources Description'), + help_text=_('Description of "%(default_value)s".') % { 'default_value': DEFAULT_OTHER_TITLE }, + blank=True, + default='' + ) + + def get_short_description(self): + return self.system_desc + + + + # Parent + + link_is_optional = True + + class Meta: + abstract = False + + # Validate + def clean(self): + clean_for_abstract_link(__class__, self) + + # If user provided link text, then require link + if self.name and not self.get_link(): + raise ValidationError( + _('Please provide a footer link or delete its display name.'), code='invalid' + ) diff --git a/taccsite_cms/contrib/taccsite_system_specs/templates/system_specs.html b/taccsite_cms/contrib/taccsite_system_specs/templates/system_specs.html new file mode 100644 index 000000000..5590501f0 --- /dev/null +++ b/taccsite_cms/contrib/taccsite_system_specs/templates/system_specs.html @@ -0,0 +1,96 @@ +{% load cms_tags static %} + + + +
+ + + + {# Image & Monitor #} + {% if image_plugin or sysmon_plugin %} +
+ {% if image_plugin %} + {% render_plugin image_plugin %} + {% endif %} + {% if sysmon_plugin %} +
+ {% render_plugin sysmon_plugin %} +
+ {% endif %} +
+ {% endif %} + + + + {# System #} +
+ {# System Intro #} +

System Specifications

+

{{ instance.system_desc }}

+ + {# System Data #} + {# HELP: Build DataList plugin instance? What if DataList is not installed? #} + {# SEE: https://stackoverflow.com/q/27937448/11817077 #} + {# SEE taccsite_data_list #} + + + + + + + + + + + + + + + + + + + + + + + + + +
Processors{{ instance.system_processor_count }}
Processor Type{{ instance.system_processor_type }}
RAM/Node{{ instance.system_node_ram }}
Network{{ instance.system_network }}
Peak Performance{{ instance.system_performance }}
System Memory{{ instance.system_memory }}
+ + {# System Link #} + {% if link_url %} + {% spaceless %} + + + {{ link_text|default:"See More Detailed Specs" }} + + {% endspaceless %} + {% endif %} +
+ + + + {# Other #} + {% if has_other %} + + {% endif %} + + + +
diff --git a/taccsite_cms/settings.py b/taccsite_cms/settings.py index 530c2d3fd..f880812e8 100644 --- a/taccsite_cms/settings.py +++ b/taccsite_cms/settings.py @@ -241,6 +241,7 @@ def getsecrets(): # TODO: Extract TACC CMS UI components into pip-installable plugins # FAQ: The djangocms_bootstrap4 library can serve as an example 'taccsite_cms.contrib.taccsite_sample', + 'taccsite_cms.contrib.taccsite_system_specs', ] # Convert list of paths to list of dotted module names diff --git a/taccsite_cms/static/site_cms/css/src/README.md b/taccsite_cms/static/site_cms/css/src/README.md index abb4a9601..1a94e6a09 100644 --- a/taccsite_cms/static/site_cms/css/src/README.md +++ b/taccsite_cms/static/site_cms/css/src/README.md @@ -23,6 +23,7 @@ _This directory exists in `static/` __only__ because it is customary, using Djan | `site(.*).css` | styles that apply to the entire website i.e. global styles | `template.*.css` | styles that apply only to certain templates | `page.*.css` | styles that apply only to certain pages +| `app.*.css` | styles that apply only to certain apps (a.k.a. plugins) ## Documentation Format diff --git a/taccsite_cms/static/site_cms/css/src/_imports/trumps/s-system-specs.css b/taccsite_cms/static/site_cms/css/src/_imports/trumps/s-system-specs.css new file mode 100644 index 000000000..691c3cef9 --- /dev/null +++ b/taccsite_cms/static/site_cms/css/src/_imports/trumps/s-system-specs.css @@ -0,0 +1,121 @@ +/* +System Specifications + +Styles for System Specifications content which assumes external code: + +- custom properties + (for `--global-...`) +- `c-data-list` + (for "Data List") +- `s-sysmon` + (for "System Monitor") + +Styleguide Trumps.Scopes.SystemSpecs +*/ +@import url("_imports/tools/media-queries.css"); + + + +/* Cascade */ + +.s-system-specs { + font-size: var(--global-font-size--small); + font-weight: var(--global-font-weight--medium); +} + + + +/* Layout */ + +@media only screen and (--narrow-and-above) { + .s-system-specs { + display: flow-root; + } + .s-system-specs > * { + display: inline-block; + } + .s-system-specs > figure { + float: left; + } +} + + + +/* Spacing */ + +.s-system-specs { + --row-height: 32px; +} + +.s-system-specs > aside { + margin-top: var(--row-height); +} + +@media only screen and (--medium-and-below) { + .s-system-specs > figure { + margin-bottom: var(--row-height); + } +} + + + +/* Sizes */ + +@media only screen and (--medium-and-above) { + .s-system-specs { + --col-width--thin: 42%; + --col-width--wide: 48%; + --col-gutter: 10%; + --col-padding: 40px; + + padding-inline: var(--col-padding); + } + .s-system-specs > div, + .s-system-specs > aside { + width: var(--col-width--thin); + padding-inline: var(--col-padding); + } + .s-system-specs > figure { + width: var(--col-width--wide); + margin-right: var(--col-gutter); + } +} + + + +/* Position */ + +.s-system-specs > figure > img { + width: 100%; + display: block; /* to support margin */ +} +.s-system-specs > figure > figcaption { + /* To nudge upward on top of image */ + position: relative; /* to prevent image from appearing on top */ + margin-top: -40px; +} + +@media only screen and (--medium-and-below) { + .s-system-specs > figure > img { + /* To center horizontally */ + width: 60%; + margin-left: auto; + margin-right: auto; + } +} +@media only screen and (--medium-and-above) { + .s-system-specs > figure > figcaption { + /* To center horizontally */ + width: 80%; + margin-left: auto; + margin-right: auto; + } +} + + + +/* Components */ + +.s-system-specs .c-data-list__key a { + font-weight: var(--medium); +} diff --git a/taccsite_cms/static/site_cms/css/src/app.taccsite_system_specs.css b/taccsite_cms/static/site_cms/css/src/app.taccsite_system_specs.css new file mode 100644 index 000000000..330df832e --- /dev/null +++ b/taccsite_cms/static/site_cms/css/src/app.taccsite_system_specs.css @@ -0,0 +1,7 @@ +/* DO NOT ADD STYLES HERE; ONLY IMPORT OTHER STYLESHEETS */ + +/* Organize via ITCSS */ +/* SEE: https://confluence.tacc.utexas.edu/x/IAA9Cw */ + +/* TRUMPS */ +@import url("_imports/trumps/s-system-specs.css"); diff --git a/taccsite_custom b/taccsite_custom index af8c7cc4c..23ab68ff3 160000 --- a/taccsite_custom +++ b/taccsite_custom @@ -1 +1 @@ -Subproject commit af8c7cc4cd5d2512104858742226680943f88e9d +Subproject commit 23ab68ff3b1560b16858f9c1e877a3a2354e7e62