Skip to content

Commit 48e6659

Browse files
authored
Merge pull request #308 from TACC/task/GH-75-plugin
GH-75: Data List - Plugin
2 parents 1d50a7b + f2264ce commit 48e6659

File tree

13 files changed

+419
-0
lines changed

13 files changed

+419
-0
lines changed

taccsite_cms/contrib/constants.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
TEXT_FOR_NESTED_PLUGIN_CONTENT_SWAP = '\
2+
<dl>\
3+
<dt>To add {element},</dt>\
4+
<dd>nest "{plugin_name}" plugin inside this plugin.</dd>\
5+
<dt>To edit {element},</dt>\
6+
<dd>edit existing nested "{plugin_name}" plugin.</dd>\
7+
</dl>'

taccsite_cms/contrib/helpers.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Get Django `models.CharField` `choices`
2+
def get_choices(choice_dict):
3+
"""Get a sequence for a Django model field choices from a dictionary.
4+
:param Dict[str, Dict[str, str]] dictionary: choice as key for dictionary of classnames and descriptions
5+
:return: a sequence for django.db.models.CharField.choices
6+
:rtype: List[Tuple[str, str], ...]
7+
"""
8+
choices = []
9+
10+
for key, data in choice_dict.items():
11+
choice = (key, data['description'])
12+
choices.append(choice)
13+
14+
return choices
15+
16+
17+
18+
# GH-93, GH-142, GH-133: Upcoming functions here (ease merge conflict, maybe)
19+
20+
21+
22+
# Concatenate a list of CSS classes
23+
# SEE: https://github.com/django-cms/djangocms-bootstrap4/blob/master/djangocms_bootstrap4/helpers.py
24+
def concat_classnames(classes):
25+
"""Concatenate a list of classname strings (without failing on None)"""
26+
# SEE: https://stackoverflow.com/a/20271297/11817077
27+
return ' '.join(_class for _class in classes if _class)
28+
29+
30+
31+
# GH-93, GH-142, GH-133: Upcoming functions here (ease merge conflict, maybe)

taccsite_cms/contrib/taccsite_data_list/__init__.py

Whitespace-only changes.
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
from cms.plugin_base import CMSPluginBase
2+
from cms.plugin_pool import plugin_pool
3+
from django.utils.translation import gettext_lazy as _
4+
5+
from taccsite_cms.contrib.constants import TEXT_FOR_NESTED_PLUGIN_CONTENT_SWAP
6+
from taccsite_cms.contrib.helpers import concat_classnames
7+
8+
from .models import TaccsiteDataList, TaccsiteDataListItem
9+
from .constants import ORIENTATION_DICT, TYPE_STYLE_DICT, DENSITY_DICT
10+
11+
12+
13+
# Helpers
14+
15+
def get_classname(dict, value):
16+
"""Get layout class based on value."""
17+
return dict.get(value, {}).get('classname')
18+
19+
20+
21+
# Plugins
22+
23+
@plugin_pool.register_plugin
24+
class TaccsiteDataListPlugin(CMSPluginBase):
25+
"""
26+
Components > "Data List" Plugin
27+
https://confluence.tacc.utexas.edu/x/EiIFDg
28+
"""
29+
module = 'TACC Site'
30+
model = TaccsiteDataList
31+
name = _('Data List')
32+
render_template = 'data_list.html'
33+
34+
cache = True
35+
text_enabled = False
36+
allow_children = True
37+
child_classes = [
38+
'TaccsiteDataListItemPlugin'
39+
]
40+
41+
fieldsets = [
42+
(_('Required configuration'), {
43+
'fields': (
44+
'type_style',
45+
'orientation',
46+
'density',
47+
)
48+
}),
49+
(_('Optional configuration'), {
50+
'fields': (
51+
'truncate_values',
52+
)
53+
}),
54+
(_('Advanced settings'), {
55+
'classes': ('collapse',),
56+
'fields': (
57+
'attributes',
58+
)
59+
}),
60+
]
61+
62+
# Render
63+
64+
def render(self, context, instance, placeholder):
65+
context = super().render(context, instance, placeholder)
66+
request = context['request']
67+
68+
classes = concat_classnames([
69+
'c-data-list',
70+
get_classname(ORIENTATION_DICT, instance.orientation),
71+
get_classname(TYPE_STYLE_DICT, instance.type_style),
72+
get_classname(DENSITY_DICT, instance.density),
73+
'c-data-list--should-truncate-values'
74+
if instance.truncate_values else '',
75+
instance.attributes.get('class'),
76+
])
77+
instance.attributes['class'] = classes
78+
79+
return context
80+
81+
@plugin_pool.register_plugin
82+
class TaccsiteDataListItemPlugin(CMSPluginBase):
83+
"""
84+
Components > "Data List Item" Plugin
85+
https://confluence.tacc.utexas.edu/x/EiIFDg
86+
"""
87+
module = 'TACC Site'
88+
model = TaccsiteDataListItem
89+
name = _('Data List Item')
90+
render_template = 'data_list_item.html'
91+
92+
cache = True
93+
text_enabled = False
94+
allow_children = True
95+
child_classes = [
96+
'LinkPlugin'
97+
]
98+
max_children = 1 # Only a label until we know what value will need
99+
100+
fieldsets = [
101+
(None, {
102+
'fields': (
103+
('key', 'value'),
104+
),
105+
}),
106+
(_('Link'), {
107+
'classes': ('collapse',),
108+
'description': TEXT_FOR_NESTED_PLUGIN_CONTENT_SWAP.format(
109+
element='a link',
110+
plugin_name='Link'
111+
) + '\
112+
<br />\
113+
The "Link" plugin\'s "Display name" field takes precedence over this plugin\'s "Label" field. <small>If "Link" plugin is not rendered, then check "Advanced settings" of this plugin.</small>',
114+
'fields': (),
115+
}),
116+
(_('Advanced settings'), {
117+
'classes': ('collapse',),
118+
'fields': (
119+
'use_plugin_as_key',
120+
),
121+
})
122+
]
123+
124+
# Render
125+
126+
def render(self, context, instance, placeholder):
127+
context = super().render(context, instance, placeholder)
128+
request = context['request']
129+
130+
parent_plugin_instance = instance.parent.get_plugin_instance()[0]
131+
132+
context.update({
133+
'parent_plugin_instance': parent_plugin_instance
134+
})
135+
136+
return context
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# TODO: Consider using an Enum (and an Abstract Enum with `get_choices` and `get_classname` methods)
2+
3+
ORIENTATION_DICT = {
4+
'horizontal': {
5+
'classname': 'c-data-list--is-horz',
6+
'description': 'Horizontal (all data on one row)',
7+
'short_description': 'Horizontal',
8+
},
9+
'vertical': {
10+
'classname': 'c-data-list--is-vert',
11+
'description': 'Vertical (every label and value has its own row)',
12+
'short_description': 'Vertical',
13+
},
14+
}
15+
16+
TYPE_STYLE_DICT = {
17+
'table': {
18+
'description': 'Table (e.g. Columns)',
19+
'short_description': 'Table',
20+
},
21+
'dlist': {
22+
'description': 'List (e.g. Glossary, Metadata)',
23+
'short_description': 'List',
24+
},
25+
}
26+
27+
DENSITY_DICT = {
28+
'default': {
29+
'classname': 'c-data-list--is-wide',
30+
'description': 'Default (ample extra space)',
31+
'short_description': 'Default',
32+
},
33+
'compact': {
34+
'classname': 'c-data-list--is-narrow',
35+
'description': 'Compact (minimal extra space)',
36+
'short_description': 'Compact',
37+
},
38+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Generated by Django 2.2.16 on 2021-08-06 20:17
2+
3+
from django.db import migrations, models
4+
import django.db.models.deletion
5+
import djangocms_attributes_field.fields
6+
7+
8+
class Migration(migrations.Migration):
9+
10+
initial = True
11+
12+
dependencies = [
13+
('cms', '0022_auto_20180620_1551'),
14+
]
15+
16+
operations = [
17+
migrations.CreateModel(
18+
name='TaccsiteDataList',
19+
fields=[
20+
('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')),
21+
('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')),
22+
('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')),
23+
('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)')),
24+
('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)')),
25+
('attributes', djangocms_attributes_field.fields.AttributesField(default=dict)),
26+
],
27+
options={
28+
'abstract': False,
29+
},
30+
bases=('cms.cmsplugin',),
31+
),
32+
migrations.CreateModel(
33+
name='TaccsiteDataListItem',
34+
fields=[
35+
('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')),
36+
('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')),
37+
('value', models.CharField(help_text='The data value.', max_length=100, verbose_name='Value')),
38+
('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')),
39+
('attributes', djangocms_attributes_field.fields.AttributesField(default=dict)),
40+
],
41+
options={
42+
'abstract': False,
43+
},
44+
bases=('cms.cmsplugin',),
45+
),
46+
]

taccsite_cms/contrib/taccsite_data_list/migrations/__init__.py

Whitespace-only changes.
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
from cms.models.pluginmodel import CMSPlugin
2+
3+
from django.db import models
4+
from django.utils.translation import gettext_lazy as _
5+
6+
from djangocms_attributes_field import fields
7+
8+
from taccsite_cms.contrib.helpers import get_choices
9+
10+
from .constants import ORIENTATION_DICT, TYPE_STYLE_DICT, DENSITY_DICT
11+
12+
13+
14+
# Helpers
15+
16+
def get_short_description(dict, value):
17+
"""Get layout class based on value."""
18+
return dict.get(value, {}).get('short_description')
19+
20+
21+
22+
# Constants
23+
24+
ORIENTATION_CHOICES = get_choices(ORIENTATION_DICT)
25+
TYPE_STYLE_CHOICES = get_choices(TYPE_STYLE_DICT)
26+
DENSITY_CHOICES = get_choices(DENSITY_DICT)
27+
28+
29+
30+
# Models
31+
32+
class TaccsiteDataList(CMSPlugin):
33+
"""
34+
Components > "Data List" Model
35+
"""
36+
orientation = models.CharField(
37+
verbose_name=_('Orientation'),
38+
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.'),
39+
choices=ORIENTATION_CHOICES,
40+
blank=False,
41+
max_length=255,
42+
)
43+
type_style = models.CharField(
44+
verbose_name=_('Type / Style'),
45+
help_text=_('The type of data to display, glossary/metadata or tabular. Notice: Each type of list has a slightly different style.'),
46+
choices=TYPE_STYLE_CHOICES,
47+
blank=False,
48+
max_length=255,
49+
)
50+
density = models.CharField(
51+
verbose_name=_('Density (Layout Spacing)'),
52+
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.'),
53+
choices=DENSITY_CHOICES,
54+
blank=False,
55+
max_length=255,
56+
)
57+
truncate_values = models.BooleanField(
58+
verbose_name=_('Truncate the values (as necessary)'),
59+
help_text=_('Truncate values if there is not enough space to show the label and the value. Notice: Labels are truncated as necessary.'),
60+
default=False,
61+
)
62+
63+
attributes = fields.AttributesField()
64+
65+
def get_short_description(self):
66+
orientation = get_short_description(ORIENTATION_DICT, self.orientation)
67+
type_style = get_short_description(TYPE_STYLE_DICT, self.type_style)
68+
density = get_short_description(DENSITY_DICT, self.density)
69+
70+
return density + ', ' + orientation + ' ' + type_style
71+
72+
class TaccsiteDataListItem(CMSPlugin):
73+
"""
74+
Components > "Data List Item" Model
75+
"""
76+
key = models.CharField(
77+
verbose_name=_('Label'),
78+
help_text=_('A label for the data value.'),
79+
blank=True,
80+
max_length=50,
81+
)
82+
value = models.CharField(
83+
verbose_name=_('Value'),
84+
help_text=_('The data value.'),
85+
blank=False,
86+
max_length=100,
87+
)
88+
use_plugin_as_key = models.BooleanField(
89+
verbose_name=_('Support child plugin for Label'),
90+
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).'),
91+
default=True,
92+
)
93+
94+
attributes = fields.AttributesField()
95+
96+
def get_short_description(self):
97+
key = self.key
98+
val = self.value
99+
max_len = 4
100+
101+
should_truncate_key = len(key) > max_len
102+
key_desc = key[0:max_len] + '…' if should_truncate_key else key
103+
104+
should_truncate_val = len(key) > max_len
105+
val_desc = val[0:max_len] + '…' if should_truncate_val else val
106+
107+
return key_desc + ': ' + val_desc
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{% load cms_tags %}
2+
3+
{% if instance.type_style == 'dlist' %}
4+
5+
<dl {# class="c-data-list" #}{{ instance.attributes_str }}>
6+
{% for plugin_instance in instance.child_plugin_instances %}
7+
{% render_plugin plugin_instance %}
8+
{% endfor %}
9+
</dl>
10+
11+
{% elif instance.type_style == 'table' %}
12+
13+
<table {# class="c-data-list" #}{{ instance.attributes_str }}>
14+
<tbody>
15+
{% for plugin_instance in instance.child_plugin_instances %}
16+
{% render_plugin plugin_instance %}
17+
{% endfor %}
18+
</tbody>
19+
</table>
20+
21+
{% endif %}

0 commit comments

Comments
 (0)