Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
2bdf7e7
GH-93, GH-142, GH-133: Article List Plugins+Styles
wesleyboar Jul 13, 2021
2b66f11
Merge branch 'main' into task/GH-93-GH-142-GH-133-article-list-plugin…
wesleyboar Jul 13, 2021
b531503
Merge branch 'main' into task/GH-93-GH-142-GH-133-article-list-plugin…
wesleyboar Jul 20, 2021
f6b8f3a
GH-93: Frontera: Plugin-less style updates snippet
wesleyboar Jul 20, 2021
5bd6484
GH-93: Frontera: Plugin-less style … snippet — Fix
wesleyboar Jul 20, 2021
89b1136
GH-93: Frontera: Plugin-less style … — Real Fix
wesleyboar Jul 20, 2021
eea0768
Merge branch 'main' into task/GH-93-GH-142-GH-133-article-list-plugin…
wesleyboar Jul 30, 2021
1729430
GH-142, GH-133: Article List Plugin Helpers
wesleyboar Jul 30, 2021
74b9a9c
GH-93: Avoid title color bug with upcoming PR #312
wesleyboar Aug 13, 2021
dcf606c
Merge branch 'main' into task/GH-93-GH-142-GH-133-article-list-plugin…
wesleyboar Aug 16, 2021
647d394
Merge branch 'main' into task/GH-93-GH-142-GH-133-article-list-plugin…
wesleyboar Aug 16, 2021
266a9ad
Merge branch 'main' into task/GH-93-GH-142-GH-133-article-list-plugin…
wesleyboar Aug 16, 2021
3bdde39
Merge branch 'main' into task/GH-93-GH-142-GH-133-article-list-plugin…
wesleyboar Aug 16, 2021
579f5c6
Docs: Corrections to "How to Extend Plugin" doc
wesleyboar Aug 24, 2021
7563f7c
Merge branch 'main' into task/GH-93-GH-142-GH-133-article-list-plugin…
wesleyboar Aug 31, 2021
c4186df
GH-93/GH-142/GH-133: Remove errant submod commits
wesleyboar Sep 1, 2021
cb9c1a1
Merge branch 'main' into task/GH-93-GH-142-GH-133-article-list-plugin…
wesleyboar Sep 2, 2021
fc21621
GH-93-ETC: Update submod to have its latest main
wesleyboar Sep 28, 2021
265fec0
Merge branch 'main' into task/GH-93-GH-142-GH-133-article-list-plugin…
wesleyboar Sep 28, 2021
7ffbd47
Merge branch 'main' into task/GH-93-GH-142-GH-133-article-list-plugin…
wesleyboar Oct 7, 2021
23abf1d
GH-93: Frontera homepage banner fixes/improvements
wesleyboar Oct 7, 2021
9525e25
GH-93: Fix: Do NOT load migrate CSS on homepage
wesleyboar Oct 7, 2021
17d5174
Major: Deprecate `.x-overlay--` classes
wesleyboar Oct 7, 2021
d452772
GH-93: Fix bug from intro of `x-overlay--` mixin
wesleyboar Oct 7, 2021
92b234e
Merge branch 'main' into task/GH-93-GH-142-GH-133-article-list-plugin…
wesleyboar Oct 14, 2021
7245196
Merge branch 'main' into task/GH-93-GH-142-GH-133-article-list-plugin…
wesleyboar Nov 4, 2021
df7a458
Quick: Submod update for GH-93, GH-142, GH-133
wesleyboar Nov 12, 2021
a854b0b
Merge branch 'main' into task/GH-93-GH-142-GH-133-article-list-plugin…
wesleyboar Nov 12, 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# How to Conditionally Render Child Plugins

```handlebars
{% for plugin_instance in instance.child_plugin_instances %}
{% if plugin_instance.plugin_type == 'LinkPlugin' %}
<a href="{{ link }}" <!-- ... -->>
<!-- ... -->
</a>
{% endif %}
{% endfor %}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# How to Handle "Non-Nullable" "Default Value"

## Sample Error

```text
You are trying to add a non-nullable field '...'
to choice without a default; we can't do that
(the database needs something to populate existing rows).
Please select a fix:
1) Provide a one-off default now (will be set on all existing rows)
2) Quit, and let me add a default in models.py
Select an option:
```

## Explanations

- (blog post) [What do you do when 'makemigrations' is telling you About your Lack of Default Value](https://chrisbartos.com/articles/what-do-you-do-when-makemigrations-is-telling-you-about-your-lack-of-default-value/)
- (video) [You are trying to add a non-nullable field ' ' to ' ' without a default; we can't do that](https://www.youtube.com/watch?v=NgaTUEijQSQ)

## Solutions

### For `cmsplugin_ptr`

1. ☑ Select option 1), then see:
- [Follow-Up Error](#follow-up-error)
- [Notes ▸ `cmsplugin_ptr`](#cmsplugin_ptr)

### For Other Fields

1. ⚠ Select option 1) and hope for the best.
2. ☑ Select option 2) and provide a sensible default (_not_ `None` a.k.a. null).
3. ⚠ (blog post) (hack) [Add A Migration For A Non-Null Foreignkey Field In Django](https://jaketrent.com/post/add-migration-nonnull-foreignkey-field-django)

## Follow-Up Error

If you allowed Null to be set as default, then you may have this new error:

```text
django.db.utils.IntegrityError: column "..." contains null values
```

Solutions:

1. [delete _relevant_ migration files and rebuild migrations](https://stackoverflow.com/a/37244199/11817077)
2. [delete _all_ migration files and rebuild migrations](https://stackoverflow.com/a/37242930/11817077)

## Notes

### `cmsplugin_ptr`

If the field is `cmsplugin_ptr` then know that

- [it is a database relationship field managed automatically by Django](https://github.com/nephila/djangocms-blog/issues/316#issuecomment-242292787),
- you may see it in workarounds for other plugins ([source a](https://github.com/django-cms/djangocms-link/blob/3.0.0/djangocms_link/models.py#L125), [source b](https://github.com/django-cms/djangocms-picture/blob/3.0.0/djangocms_picture/models.py#L208)),
- you should __not__ add or overwrite it unless you know what you are doing.

_W. Bomar learned everything in the intitial version of this document after trying to overwrite `cmsplugin_ptr` while extending its model from [source a](https://github.com/django-cms/djangocms-link/blob/3.0.0/djangocms_link/models.py#L125). His solution was [delete _all_ migration files and rebuild migrations](https://stackoverflow.com/a/37242930/11817077)._

## Appendix

- [Django CMS ▸ How to create Plugins ▸ Handling Relations](https://docs.django-cms.org/en/release-3.7.x/how_to/custom_plugins.html#handling-relations)
- [[BUG] Plugins with models that don't directly inherit from CMSPlugin or an abstract model cannot be copied](https://github.com/django-cms/django-cms/issues/6987)
66 changes: 66 additions & 0 deletions taccsite_cms/contrib/_docs/taccsite_static_article.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Static Article Plugins

## Intention

Support static addition of news articles that originate from a Core news site.

A [dynamic solution that pulls form the Core news site](https://github.com/TACC/Core-CMS/issues/69) is preferable.

But this is not available due to constrainst of architecture, time, or ability.

## Architecture

### (Currently) Add Image via Child Plugin Instead of Via Fields

Instead, the image fields should be in the plugin, __not__ via a child plugin, but a solution has not yet been implemented.

#### Hope for the Future

The `AbstractLink` model was successfully extended.

See:
- [./how-to-extend-django-cms-plugin.md](./how-to-extend-django-cms-plugin.md)
- [../taccsite_static_article_preview](../taccsite_static_article_preview)
- [../taccsite_static_article_list](../taccsite_static_article_list)

#### Failed Attempt

1. Build model so it extends `AbstractPicture` from `djangocms-picture`.
2. Tweak model to sweep bugs under the rug.
3. Quit when he was unable to resolve the error,
`TaccsiteStaticNewsArticlePreview has no field named 'cmsplugin_ptr_id'`
upon saving a plugin instance.
4. Learn:
- [one should not try to reduce `AbstractPicture`](https://stackoverflow.com/a/3674714/11817077)
- [one should not subclass a subclass of `CMSPlugin`](https://github.com/django-cms/django-cms/blob/3.7.4/cms/models/pluginmodel.py#L104)

#### Abandoned Code

```python
from djangocms_picture.models import AbstractPicture

# To allow user to not set image
# FAQ: Emptying the clean() method avoids picture validation
# SEE: https://github.com/django-cms/djangocms-picture/blob/3.0.0/djangocms_picture/models.py#L278
def skip_image_validation():
pass

class TaccsiteStaticNewsArticlePreview(AbstractPicture):
#
# …
#

# Remove error-prone attribute from parent class
# FAQ: Avoid error when running `makemigrations`:
# "You are trying to add a non-nullable field 'cmsplugin_ptr' […]"
# SEE: https://github.com/django-cms/djangocms-picture/blob/3.0.0/djangocms_picture/models.py#L212
# SEE: https://github.com/django-cms/djangocms-picture/blob/3.0.0/djangocms_picture/models.py#L234
cmsplugin_ptr = None

class Meta:
abstract = False

# Validate
def clean(self):
skip_image_validation()
```
165 changes: 162 additions & 3 deletions taccsite_cms/contrib/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,23 @@ def get_choices(choice_dict):



# GH-93, GH-142, GH-133: Upcoming functions here (ease merge conflict, maybe)
# Filter Django `models.CharField` `choices`
# SEE: get_choices
def filter_choices_by_prefix(choices, prefix):
"""Reduce sequence of choices to items whose values begin with given string
:param List[Tuple[str, str], ...] choices: the sequence to filter
:param str prefix: the starting text required of an item value to retain it
:returns: a sequence for django.db.models.CharField.choices
:rtype: List[Tuple[str, str], ...]
"""
new_choices = []

for choice in choices:
should_keep = choice[0].startswith(prefix)
if should_keep:
new_choices.append(choice)

return new_choices



Expand All @@ -28,7 +44,26 @@ def concat_classnames(classes):



# GH-93, GH-142, GH-133: Upcoming functions here (ease merge conflict, maybe)
# Create a list clone that has another list shoved into it
# SEE: https://newbedev.com/how-to-insert-multiple-elements-into-a-list
def insert_at_position(position, list, list_to_insert):
"""Insert list at position within another list
:returns: New list
"""
return list[:position] + list_to_insert + list[position:]



# Get the date from a list that is nearest
# SEE: https://stackoverflow.com/a/32237949/11817077
def get_nearest(items, pivot):
"""Get nearest date (or other arithmatic value)
:returns: The item value nearest the given "pivot" value
"""
return min(items, key=lambda x: abs(x - pivot))



# 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):
Expand All @@ -39,6 +74,129 @@ def get_indices_that_start_with(text, list):
return [i for i in range(len(list)) if list[i].startswith(text)]



# Populate class attribute of plugin instances
def add_classname_to_instances(classname, plugin_instances):
"""Add class names to class attribute of plugin instances"""
for instance in plugin_instances:
# A plugin must not have any class set
if not hasattr(instance.attributes, 'class'):
instance.attributes['class'] = ''

# The class should occur before any CMS or user classes
# FAQ: This keeps plugin author classes together
instance.attributes['class'] = instance.attributes['class'] + classname



# Get date nearest today

from datetime import date

# HELP: Can this logic be less verbose?
# HELP: Is the `preferred_time_period` parameter effectual?
def which_date_is_nearest_today(date_a, date_b, preferred_time_period):
"""
Returns whether each date is today or nearest today, and whether nearest date is past or today or future.
Only two dates are supported. You may prefer 'future' or 'past' date(s).
If both dates are the same date, then both are reported as True.
:param datetime date_a: a date "A" to compare
:param datetime date_b: a date "B" to compare
:param str preferred_time_period: whether to prefer 'future' or 'past' dates
:returns:
A tuple of tuples:
((
``boolean`` of whether ``date_a`` is nearest,
``string`` of ``date_a`` time period ``past``/``today``/``future``
),
(
``boolean`` of whether ``date_b`` is nearest,
``string`` of ``date_b`` time period ``past``/``today``/``future``
)),
:rtype: tuple
"""
today = date.today()
is_a = False
is_b = False
a_time_period = 'today'
b_time_period = 'today'

# Match preferred time

if today in {date_a, date_b}:
is_a = True
is_b = True
a_time_period = 'today'
b_time_period = 'today'

elif preferred_time_period == 'future':
is_a = date_a and date_a >= today
is_b = date_b and date_b >= today
if is_a: a_time_period = 'future'
if is_b: b_time_period = 'future'
if not is_a and not is_b:
is_a = date_a and date_a < today
is_b = date_b and date_b < today
if is_a: a_time_period = 'past'
if is_b: b_time_period = 'past'

elif preferred_time_period == 'past':
is_a = date_a and date_a < today
is_b = date_b and date_b < today
if is_a: a_time_period = 'past'
if is_b: b_time_period = 'past'
if not is_a and not is_b:
is_a = date_a and date_a >= today
is_b = date_b and date_b >= today
if is_a: a_time_period = 'future'
if is_b: b_time_period = 'future'

# Show nearest date
if is_a and is_b and date_a != date_b:
nearest_date = get_nearest((date_a, date_b), today)

if date_a == nearest_date:
is_b = False
if date_b == nearest_date:
is_a = False

return ((is_a, a_time_period), (is_b, b_time_period))



# Allow plugins to set max number of nested children

from django.shortcuts import render

# SEE: https://github.com/django-cms/django-cms/issues/5102#issuecomment-597150141
class AbstractMaxChildrenPlugin():
"""
Abstract extension of `CMSPluginBase` that allows setting maximum amount of nested/child plugins.
Usage:
1. Extend this class,
after extending `CMSPluginBase` or a class that extends `CMSPluginBase`.
2. Set `max_children` to desired limit.
"""

max_children = None

def add_view(self,request, form_url='', extra_context=None):

if self.max_children:
# FAQ: Placeholders do not have a parent, only plugins do
if self._cms_initial_attributes['parent']:
num_allowed = len([v for v in self._cms_initial_attributes['parent'].get_children() if v.get_plugin_instance()[0] is not None])
else:
num_allowed = len([v for v in self.placeholder.get_plugins() if v.get_plugin_instance()[0] is not None and v.get_plugin_name() == self.name])

if num_allowed >= self.max_children:
return render(request , "path/to/your/max_reached_template.html", {
'max_children': self.max_children,
})
return super(AbstractMaxChildrenPlugin, self).add_view(request, form_url, extra_context)



# Tweak validation on Django CMS `AbstractLink` for TACC

from cms.models.pluginmodel import CMSPlugin
Expand Down Expand Up @@ -82,8 +240,9 @@ def clean(self):
if len(err.messages):
raise err

# Get name of field from a given model


# 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()
Expand Down
3 changes: 3 additions & 0 deletions taccsite_cms/contrib/taccsite_static_article_list/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Static Article List

See [./_docs/taccsite_static_article.md](./_docs/taccsite_static_article.md).
Empty file.
Loading