Skip to content

Commit 2bdf7e7

Browse files
committed
GH-93, GH-142, GH-133: Article List Plugins+Styles
1 parent 386ffac commit 2bdf7e7

32 files changed

+1916
-532
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# How to Conditionally Render Child Plugins
2+
3+
```handlebars
4+
{% for plugin_instance in instance.child_plugin_instances %}
5+
{% if plugin_instance.plugin_type == 'LinkPlugin' %}
6+
<a href="{{ link }}" <!-- ... -->>
7+
<!-- ... -->
8+
</a>
9+
{% endif %}
10+
{% endfor %}
11+
```
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# How To Extend a `djangocms-___` Plugin
2+
3+
These example codes extend the [`djangocms-link` plugin](https://github.com/django-cms/djangocms-link/tree/3.0.0/djangocms_link).
4+
5+
`.../models.py`:
6+
7+
```python
8+
from djangocms_link.models import AbstractLink
9+
10+
class Taccsite______(AbstractLink):
11+
"""
12+
Components > "Article List" Model
13+
https://confluence.tacc.utexas.edu/x/OIAjCQ
14+
"""
15+
# ___ = ___
16+
17+
class Meta:
18+
abstract = False
19+
```
20+
21+
`.../cms_plugins.py`:
22+
23+
```python
24+
from djangocms_link.cms_plugins import LinkPlugin
25+
26+
from .models import ______Preview
27+
28+
class ______Plugin(LinkPlugin):
29+
module = 'TACC Site'
30+
model = Taccsite______
31+
name = _('______')
32+
render_template = 'static_article_preview.html'
33+
def get_render_template(self, context, instance, placeholder):
34+
return self.render_template
35+
36+
fieldsets = [
37+
(_('Link'), {
38+
'fields': (
39+
('external_link', 'internal_link'),
40+
('anchor', 'target'),
41+
)
42+
}),
43+
]
44+
45+
# Render
46+
def render(self, context, instance, placeholder):
47+
context = super().render(context, instance, placeholder)
48+
request = context['request']
49+
50+
context.update({
51+
'link_url': instance.get_link(),
52+
'link_text': instance.name,
53+
'link_target': instance.target
54+
})
55+
return context
56+
```
57+
58+
`.../templates/______.py`:
59+
60+
```handlebars
61+
62+
<a class="______" href="{{ link_url }}"
63+
{% if link_target %}target="{{ link_target }}"{% endif %}>
64+
<span>{{ link_text }}
65+
</a>
66+
```
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# How to Handle "Non-Nullable" "Default Value"
2+
3+
## Sample Error
4+
5+
```text
6+
You are trying to add a non-nullable field '...'
7+
to choice without a default; we can't do that
8+
(the database needs something to populate existing rows).
9+
Please select a fix:
10+
1) Provide a one-off default now (will be set on all existing rows)
11+
2) Quit, and let me add a default in models.py
12+
Select an option:
13+
```
14+
15+
## Explanations
16+
17+
- (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/)
18+
- (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)
19+
20+
## Solutions
21+
22+
### For `cmsplugin_ptr`
23+
24+
1. ☑ Select option 1), then see:
25+
- [Follow-Up Error](#follow-up-error)
26+
- [Notes ▸ `cmsplugin_ptr`](#cmsplugin_ptr)
27+
28+
### For Other Fields
29+
30+
1. ⚠ Select option 1) and hope for the best.
31+
2. ☑ Select option 2) and provide a sensible default (_not_ `None` a.k.a. null).
32+
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)
33+
34+
## Follow-Up Error
35+
36+
If you allowed Null to be set as default, then you may have this new error:
37+
38+
```text
39+
django.db.utils.IntegrityError: column "..." contains null values
40+
```
41+
42+
Solutions:
43+
44+
1. [delete _relevant_ migration files and rebuild migrations](https://stackoverflow.com/a/37244199/11817077)
45+
2. [delete _all_ migration files and rebuild migrations](https://stackoverflow.com/a/37242930/11817077)
46+
47+
## Notes
48+
49+
### `cmsplugin_ptr`
50+
51+
If the field is `cmsplugin_ptr` then know that
52+
53+
- [it is a database relationship field managed automatically by Django](https://github.com/nephila/djangocms-blog/issues/316#issuecomment-242292787),
54+
- 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)),
55+
- you should __not__ add or overwrite it unless you know what you are doing.
56+
57+
_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)._
58+
59+
## Appendix
60+
61+
- [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)
62+
- [[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)
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# How To Override `ValidationError()` from Parent Model
2+
3+
Intercept Error(s):
4+
5+
```python
6+
from django.core.exceptions import ValidationError
7+
8+
from djangocms_Xxx.models import AbstractXxx
9+
10+
from taccsite_cms.contrib.helpers import (
11+
get_indices_that_start_with
12+
)
13+
14+
class OurModelThatUsesXxx(AbstractXxx):
15+
# Validate
16+
def clean(self):
17+
# Bypass irrelevant parent validation
18+
try:
19+
super().clean()
20+
except ValidationError as err:
21+
# Intercept single-field errors
22+
if hasattr(err, 'error_list'):
23+
for i in range(len(err.error_list)):
24+
# SEE: "Find Error(s)"
25+
# ...
26+
# Skip error
27+
del err.error_list[i]
28+
# Replace error
29+
# SEE: https://docs.djangoproject.com/en/2.2/ref/forms/validation/#raising-validationerror
30+
31+
# Intercept multi-field errors
32+
if hasattr(err, 'error_dict'):
33+
for field, errors in err.message_dict.items():
34+
# SEE: "Find Error(s)"
35+
# ...
36+
# Skip error
37+
del err.error_dict[field]
38+
# Replace error
39+
# SEE: https://docs.djangoproject.com/en/2.2/ref/forms/validation/#raising-validationerror
40+
41+
# NOTE: The conditional `pass` is only to skip multi-field errors;
42+
# single-field error skipping is unaffected by this logic;
43+
# so it seems safe to always include this logic block
44+
if len(err.messages) == 0:
45+
pass
46+
else:
47+
raise err
48+
```
49+
50+
Handle Error(s):
51+
52+
```python
53+
# SEE: "Find Error(s)"
54+
# ...
55+
56+
# Catch known static error
57+
if 'Known static error string' in error:
58+
# ...
59+
60+
# Catch known dynamic error
61+
indices_to_catch = get_indices_that_start_with(
62+
'Known dynamic error string that starts with same text',
63+
errors
64+
)
65+
for i in indices_to_catch:
66+
# ...
67+
```
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# Static Article Plugins
2+
3+
## Intention
4+
5+
Support static addition of news articles that originate from a Core news site.
6+
7+
A [dynamic solution that pulls form the Core news site](https://github.com/TACC/Core-CMS/issues/69) is preferable.
8+
9+
But this is not available due to constrainst of architecture, time, or ability.
10+
11+
## Architecture
12+
13+
### (Currently) Add Image via Child Plugin Instead of Via Fields
14+
15+
Instead, the image fields should be in the plugin, __not__ via a child plugin, but a solution has not yet been implemented.
16+
17+
#### Hope for the Future
18+
19+
The `AbstractLink` model was successfully extended.
20+
21+
See:
22+
- [./how-to-extend-django-cms-plugin.md](./how-to-extend-django-cms-plugin.md)
23+
- [../taccsite_static_article_preview](../taccsite_static_article_preview)
24+
- [../taccsite_static_article_list](../taccsite_static_article_list)
25+
26+
#### Failed Attempt
27+
28+
1. Build model so it extends `AbstractPicture` from `djangocms-picture`.
29+
2. Tweak model to sweep bugs under the rug.
30+
3. Quit when he was unable to resolve the error,
31+
`TaccsiteStaticNewsArticlePreview has no field named 'cmsplugin_ptr_id'`
32+
upon saving a plugin instance.
33+
4. Learn:
34+
- [one should not try to reduce `AbstractPicture`](https://stackoverflow.com/a/3674714/11817077)
35+
- [one should not subclass a subclass of `CMSPlugin`](https://github.com/django-cms/django-cms/blob/3.7.4/cms/models/pluginmodel.py#L104)
36+
37+
#### Abandoned Code
38+
39+
```python
40+
from djangocms_picture.models import AbstractPicture
41+
42+
# To allow user to not set image
43+
# FAQ: Emptying the clean() method avoids picture validation
44+
# SEE: https://github.com/django-cms/djangocms-picture/blob/3.0.0/djangocms_picture/models.py#L278
45+
def skip_image_validation():
46+
pass
47+
48+
class TaccsiteStaticNewsArticlePreview(AbstractPicture):
49+
#
50+
#
51+
#
52+
53+
# Remove error-prone attribute from parent class
54+
# FAQ: Avoid error when running `makemigrations`:
55+
# "You are trying to add a non-nullable field 'cmsplugin_ptr' […]"
56+
# SEE: https://github.com/django-cms/djangocms-picture/blob/3.0.0/djangocms_picture/models.py#L212
57+
# SEE: https://github.com/django-cms/djangocms-picture/blob/3.0.0/djangocms_picture/models.py#L234
58+
cmsplugin_ptr = None
59+
60+
class Meta:
61+
abstract = False
62+
63+
# Validate
64+
def clean(self):
65+
skip_image_validation()
66+
```
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Static Article List
2+
3+
See [./_docs/taccsite_static_article.md](./_docs/taccsite_static_article.md).

taccsite_cms/contrib/taccsite_static_article_list/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)