Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GH-83: Callout Plugin #324

Merged
merged 32 commits into from
Sep 30, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
82b768f
GH-83: Callout Component
wesleyboar Aug 20, 2021
c2c96ec
GH-83: Temp: Callout Snippet (Frontera)
wesleyboar Aug 20, 2021
c15d766
GH-83: WIP: Callout Plugin
wesleyboar Aug 20, 2021
4fa354c
GH-83: Fix Callout Component after Plugin's tweaks
wesleyboar Aug 21, 2021
a15e585
GH-83: Fix Callout Plugin, Update Styles to Match
wesleyboar Aug 21, 2021
dba3f4b
GH-83: Remove leftover templates
wesleyboar Aug 23, 2021
c24e0f9
GH-83: Noop: Add markup whitespace for legibility
wesleyboar Aug 23, 2021
54fc04a
GH-83: Quick: Remove temp snippet
wesleyboar Aug 23, 2021
1266e4e
Merge branch 'main' into task/GH-83-callout-element
wesleyboar Aug 23, 2021
ed0e749
Quick: Add x-article-link-active mixin
wesleyboar Aug 24, 2021
51d3f1d
GH-83: Support Hyperlink (requires 69f47cc)
wesleyboar Aug 24, 2021
df1060b
GH-83: Add missing helper clean_for_abstract_link
wesleyboar Aug 24, 2021
d59c3f6
GH-83: Squash migrations. Accept GH-321
wesleyboar Aug 24, 2021
acefadf
GH-83/GH-321: Add missing static template tag load
wesleyboar Aug 24, 2021
9767d45
GH-321: Resize Callout image to parent height
wesleyboar Aug 23, 2021
7ffceeb
GH-321: Default to not resize image. Add warning.
wesleyboar Aug 24, 2021
a07957e
GH-83: Add two relevant docs from PR #280
wesleyboar Aug 24, 2021
5059e02
GH-83: Require all fields (can't require image)
wesleyboar Aug 24, 2021
1a57226
GH-83: Support Optional Image/Link in CSS Docblock
wesleyboar Aug 24, 2021
a7134e2
GH-83: Quick: Update script comments & equality
wesleyboar Aug 24, 2021
66eae8f
GH-321: Fix figure size and note about its resize
wesleyboar Aug 24, 2021
fa185d0
GH-83/GH-321: Show image on narrow windows
wesleyboar Aug 24, 2021
75a6ece
GH-83: Fix: Ensure 0001 taccsite_callout migration
wesleyboar Aug 30, 2021
1f2d736
Merge branch 'main' into task/GH-83-callout-element
wesleyboar Aug 31, 2021
2b23df8
Merge branch 'main' into task/GH-83-callout-element
wesleyboar Sep 1, 2021
93d7a0a
GH-83: Style Tweask to Match Design
wesleyboar Sep 1, 2021
a438972
Merge branch 'main' into task/GH-83-callout-element
wesleyboar Sep 2, 2021
8e8a6c0
Add missing function to taccsite_cms/contrib/helpers.py
wesleyboar Sep 14, 2021
a6cca13
Merge branch 'main' into task/GH-83-callout-element
wesleyboar Sep 29, 2021
d471a67
GH-83: Add missing migration (help text uptdates)
wesleyboar Sep 29, 2021
47347fa
GH-83: Shorten verbose logic. Drop comment.
wesleyboar Sep 29, 2021
6e83ef8
Merge branch 'main' into task/GH-83-callout-element
wesleyboar Sep 30, 2021
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
78 changes: 78 additions & 0 deletions taccsite_cms/contrib/_docs/how-to-extend-django-cms-plugin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# How To Extend a `djangocms-___` Plugin
wesleyboar marked this conversation as resolved.
Show resolved Hide resolved

These example codes extend the [`djangocms-link` plugin](https://github.com/django-cms/djangocms-link/tree/3.0.0/djangocms_link).

`.../models.py`:

```python
from djangocms_link.models import AbstractLink

from taccsite_cms.contrib.helpers import clean_for_abstract_link

class Taccsite______(AbstractLink):
"""
Components > "Article List" Model
https://confluence.tacc.utexas.edu/x/OIAjCQ
"""
# ...



# Parent

link_is_optional = True # or False

class Meta:
abstract = False

# Validate
def clean(self):
clean_for_abstract_link(__class__, self)
```

`.../cms_plugins.py`:

```python
from djangocms_link.cms_plugins import LinkPlugin

from .models import ______Preview

class ______Plugin(LinkPlugin):
# ...
render_template = 'static_article_preview.html'
def get_render_template(self, context, instance, placeholder):
return self.render_template

fieldsets = [
# ...
(_('Link'), {
'fields': (
# 'name', # to use LinkPlugin "Display name"
('external_link', 'internal_link'),
('anchor', 'target'),
)
}),
]

# Render
def render(self, context, instance, placeholder):
context = super().render(context, instance, placeholder)
request = context['request']

context.update({
'link_url': instance.get_link(),
'link_target': instance.target
# 'link_text': instance.name, # to use LinkPlugin "Display name"
})
return context
```

`.../templates/______.py`:

```handlebars

<a class="______" href="{{ link_url }}"
{% if link_target %}target="{{ link_target }}"{% endif %}>
<span>{{ link_text }}
</a>
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# How To Override `ValidationError()` from Parent Model
wesleyboar marked this conversation as resolved.
Show resolved Hide resolved

Intercept Error(s):

```python
from django.core.exceptions import ValidationError

from djangocms_Xxx.models import AbstractXxx

from taccsite_cms.contrib.helpers import (
get_indices_that_start_with
)

class OurModelThatUsesXxx(AbstractXxx):
# Validate
def clean(self):
# Bypass irrelevant parent validation
try:
super().clean()
except ValidationError as err:
# Intercept single-field errors
if hasattr(err, 'error_list'):
for i in range(len(err.error_list)):
# SEE: "Find Error(s)"
# ...
# Skip error
del err.error_list[i]
# Replace error
# SEE: https://docs.djangoproject.com/en/2.2/ref/forms/validation/#raising-validationerror

# Intercept multi-field errors
if hasattr(err, 'error_dict'):
for field, errors in err.message_dict.items():
# SEE: "Find Error(s)"
# ...
# Skip error
del err.error_dict[field]
# Replace error
# SEE: https://docs.djangoproject.com/en/2.2/ref/forms/validation/#raising-validationerror

if err.messages:
raise err
```

Handle Error(s):

```python
# SEE: "Find Error(s)"
# ...

# Catch known static error
if 'Known static error string' in error:
# ...

# Catch known dynamic error
indices_to_catch = get_indices_that_start_with(
'Known dynamic error string that starts with same text',
errors
)
for i in indices_to_catch:
# ...
```
24 changes: 24 additions & 0 deletions taccsite_cms/contrib/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,22 @@ def concat_classnames(classes):


# GH-93, GH-142, GH-133: Upcoming functions here (ease merge conflict, maybe)
# Get list of indicies of items that start with text
# SEE: https://stackoverflow.com/a/67393343/11817077
def get_indices_that_start_with(text, list):
"""
Get a list of indices of list elements that starts with given text
:rtype: list
"""
return [i for i in range(len(list)) if list[i].startswith(text)]


# Tweak validation on Django CMS `AbstractLink` for TACC

from cms.models.pluginmodel import CMSPlugin

from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _



Expand Down Expand Up @@ -65,3 +81,11 @@ def clean(self):

if len(err.messages):
raise err

# Get name of field from a given model

# SEE: https://stackoverflow.com/a/14498938/11817077
def get_model_field_name(model, field_name):
model_field_name = model._meta.get_field(field_name).verbose_name.title()

return model_field_name
wesleyboar marked this conversation as resolved.
Show resolved Hide resolved
Empty file.
108 changes: 108 additions & 0 deletions taccsite_cms/contrib/taccsite_callout/cms_plugins.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
from cms.plugin_base import CMSPluginBase
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.helpers import (
concat_classnames,
get_model_field_name
)
from taccsite_cms.contrib.constants import TEXT_FOR_NESTED_PLUGIN_CONTENT_SWAP

from .models import TaccsiteCallout




# Constants

RESIZE_FIGURE_FIELD_NAME = get_model_field_name(TaccsiteCallout, 'resize_figure_to_fit')



# Plugin

@plugin_pool.register_plugin
class TaccsiteCalloutPlugin(LinkPlugin):
"""
Components > "Callout" Plugin
https://confluence.tacc.utexas.edu/x/EiIFDg
"""
module = 'TACC Site'
model = TaccsiteCallout
name = _('Callout')
render_template = 'callout.html'
def get_render_template(self, context, instance, placeholder):
return self.render_template

cache = True
text_enabled = False
allow_children = True
# GH-91: Enable this limitation
# parent_classes = [
# 'SectionPlugin'
# ]
child_classes = [
'PicturePlugin'
]
max_children = 1

fieldsets = [
(None, {
'fields': (
'title', 'description',
),
}),
(_('Link'), {
'fields': (
('external_link', 'internal_link'),
('anchor', 'target'),
)
}),
(_('Image'), {
'classes': ('collapse',),
'description': TEXT_FOR_NESTED_PLUGIN_CONTENT_SWAP.format(
element='an image',
plugin_name='Image'
) + '\
<br />\
If image disappears while editing, then reload the page to reload the image.',
'fields': (),
}),
(_('Advanced settings'), {
'classes': ('collapse',),
'description': 'Only use the "' + RESIZE_FIGURE_FIELD_NAME + '" in emergencies. It is preferable to resize the image. <small>When the "Advanced settings" field "' + RESIZE_FIGURE_FIELD_NAME + '" is checked, the image may disappear after saving this plugin (because of a JavaScript race condition). Using a server-side solution would eliminate this caveat.</small>',
wesleyboar marked this conversation as resolved.
Show resolved Hide resolved
'fields': (
'resize_figure_to_fit',
'attributes',
)
}),
]

# Render

def render(self, context, instance, placeholder):
context = super().render(context, instance, placeholder)
request = context['request']
has_child_plugin = {}

# To identify child plugins
for plugin_instance in instance.child_plugin_instances:
if (type(plugin_instance).__name__ == 'Picture'):
has_child_plugin['image'] = True
context.update({ 'image_plugin': plugin_instance })

classes = concat_classnames([
'c-callout',
'c-callout--has-figure' if has_child_plugin.get('image') else '',
'c-callout--is-link' if instance.get_link() else '',
instance.attributes.get('class'),
])
instance.attributes['class'] = classes

context.update({
'link_url': instance.get_link(),
'link_target': instance.target
})
return context
43 changes: 43 additions & 0 deletions taccsite_cms/contrib/taccsite_callout/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Generated by Django 2.2.16 on 2021-08-24 13:03

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='TaccsiteCallout',
fields=[
('cmsplugin_ptr', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, related_name='taccsite_callout_taccsitecallout', serialize=False, to='cms.CMSPlugin')),
('title', models.CharField(blank=True, help_text='A heading for the callout.', max_length=100, verbose_name='Title')),
('description', models.CharField(blank=True, help_text='A paragraph for the callout.', max_length=200, verbose_name='Description')),
('resize_figure_to_fit', models.BooleanField(default=True, help_text='Make image shorter or taller to match the height of text beside it.', verbose_name='Resize any image to fit')),
('attributes', djangocms_attributes_field.fields.AttributesField(default=dict)),
('anchor', models.CharField(blank=True, help_text='Appends the value only after the internal or external link. Do <em>not</em> include a preceding "#" symbol.', max_length=255, verbose_name='Anchor')),
('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')),
('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')),
('mailto', models.EmailField(blank=True, max_length=255, verbose_name='Email address')),
('name', models.CharField(blank=True, max_length=255, verbose_name='Display name')),
('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')),
('template', models.CharField(choices=[('default', 'Default')], default='default', max_length=255, verbose_name='Template')),
],
options={
'abstract': False,
},
bases=('cms.cmsplugin',),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Generated by Django 2.2.16 on 2021-09-28 22:17

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('taccsite_callout', '0001_initial'),
]

operations = [
migrations.AlterField(
model_name='taccsitecallout',
name='description',
field=models.CharField(help_text='A paragraph for the callout.', max_length=200, verbose_name='Description'),
),
migrations.AlterField(
model_name='taccsitecallout',
name='resize_figure_to_fit',
field=models.BooleanField(default=False, help_text='Make image shorter or taller to match the height of text beside it (as it would be without the image).', verbose_name='Resize any image to fit'),
),
migrations.AlterField(
model_name='taccsitecallout',
name='title',
field=models.CharField(help_text='A heading for the callout.', max_length=100, verbose_name='Title'),
),
]
Empty file.
Loading