Skip to content

Commit

Permalink
Merge pull request #190 from TACC/task/GH-67-tacc-sample-plugin
Browse files Browse the repository at this point in the history
Task/gh 67 tacc sample plugin
  • Loading branch information
wesleyboar authored Apr 22, 2021
2 parents 24f099a + e6a6ff4 commit 3deb6b4
Show file tree
Hide file tree
Showing 12 changed files with 378 additions and 0 deletions.
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,31 @@ Whenever static files are changed, the CMS may need to be manually told to serve
2. In this parent repo, add `/taccsite_custom` change.
3. In this parent repo, commit changes (__not__ to `main`).

## How to Run Tests

### Test TACC CMS Plugins

Testing is run through Django. From within `core_cms` container, run `python manage.py test PATH_TO_DIR_WITH_TESTS` from the repository root directory. Example:

```bash
docker exec -it core_cms /bin/bash
python manage.py test taccsite_cms.contrib.taccsite_sample
```

> __Notice__: To test without migrations—which is _much_ faster—add the flag `--nomigrations` (or `-n`). Example:
>
> ```bash
> docker exec -it core_cms /bin/bash
> python manage.py test taccsite_cms.contrib.taccsite_sample --nomigrations
> ```

<!-- TODO: Make `python manage.py test` recursively find all the tests -->
<!-- SEE: https://docs.djangoproject.com/en/2.2/topics/testing/overview/#running-tests -->

### Test PostCSS Plugin Configs

Testing is manual review of build output. From within `core_cms` container, run `npm run build` from the repository root directory, then review `taccsite_cms/static/site_cms/css/build/_test.css`.

## Reference

- [Formatting & Linting](https://confluence.tacc.utexas.edu/x/HoBGCw)
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ djangocms-snippet==3.0.0
djangocms-style==3.0.0
djangocms-text-ckeditor==3.10.0
djangocms-video==3.0.0
django-test-without-migrations==0.6
docopt==0.6.2
easy-thumbnails==2.7
elasticsearch==7.9.1
Expand Down
Empty file.
51 changes: 51 additions & 0 deletions taccsite_cms/contrib/taccsite_sample/cms_plugins.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
from cms.plugin_base import CMSPluginBase
from cms.plugin_pool import plugin_pool
from cms.models.pluginmodel import CMSPlugin
from django.utils.translation import gettext_lazy as _
from django.utils.encoding import force_text

from .models import TaccsiteSample

from .defaults import user_name as default_name
from .utils import has_proper_name

# SEE: http://docs.django-cms.org/en/release-3.7.x/reference/plugins.html
@plugin_pool.register_plugin
class TaccsiteSamplePlugin(CMSPluginBase):
"""
Components > "Sample (Greet User)" Plugin
https://url.to/docs/components/sample/
"""
module = 'TACC Site'
model = TaccsiteSample
name = _('Sample (Greet User)')
render_template = 'sample.html'

cache = False
text_enabled = True
# NOTE: Use case is unclear
# admin_preview = True
# NOTE: To change for all TACC plugins add taccsite_cms/templates/admin/...
# change_form_template = 'templates/plugin_change_form.html'
# NOTE: To change field widget and other attribute beyond `models.…Field`
# form = TaccsiteSamplePluginForm # TODO: Provide example

# FAQ: Sets tooltip of preview of this plugin within a Text plugin
def icon_alt(self, instance):
super_value = force_text(super().icon_alt(instance))
return f'Hello, […] ({super_value})'
# NOTE: Our previews (see `icon_alt`) are rich and have no icon...
# TODO: Confirm whether these are ever necessary
# def icon_src(self, instance)
# def text_editor_button_icon(...)

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

context.update({
'name': instance.get_name(request.user),
'has_proper_name': has_proper_name(request.user),
'is_authenticated': request.user.is_authenticated
})
return context
1 change: 1 addition & 0 deletions taccsite_cms/contrib/taccsite_sample/defaults.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
user_name = 'Guest'
27 changes: 27 additions & 0 deletions taccsite_cms/contrib/taccsite_sample/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Generated by Django 2.2.16 on 2021-04-13 20:16

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

initial = True

dependencies = [
('cms', '0022_auto_20180620_1551'),
]

operations = [
migrations.CreateModel(
name='TaccsiteSample',
fields=[
('cmsplugin_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, related_name='taccsite_sample_taccsitesample', serialize=False, to='cms.CMSPlugin')),
('guest_name', models.CharField(blank=True, default='Guest', help_text='If user is logged in they are greeted by their name. If not logged in, they are greeted as this value. If this value is blank, they are greeted as "Guest".', max_length=50)),
],
options={
'abstract': False,
},
bases=('cms.cmsplugin',),
),
]
Empty file.
51 changes: 51 additions & 0 deletions taccsite_cms/contrib/taccsite_sample/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
from cms.models.pluginmodel import CMSPlugin

from django.db import models

from .defaults import user_name as default_name
from .utils import has_proper_name, get_proper_name

class TaccsiteSample(CMSPlugin):
# Overwrites

def get_short_description(self):
return 'Hello, […]'

# Fields

"""
Components > "Sample (Greet User)" Model
https://url.to/docs/components/sample/
"""
guest_name = models.CharField(
max_length=50,
default=default_name,
help_text=f'If user is logged in they are greeted by their name. If not logged in, they are greeted as this value. If this value is blank, they are greeted as "{default_name}".',
# To change the widget, a new Form class is required
# FAQ: Wesley B searched for hours to find this important information
# SEE: http://disq.us/p/210zgp2
# SEE: [`TaccsiteSamplePlugin.form`](./cms_plugins.py)
# widget=forms.TextInput(attrs={'placeholder': 'Search'}),
blank=True
)

# Custom

def get_name(self, user=None):
"""Get name by which to greet the user.
:param user: Django user object
:rtype: str
:returns: Name of authenticated user or the name for any guest
"""
if has_proper_name(user):
name = get_proper_name(user)
elif user.is_authenticated:
name = user.username
elif bool(self.guest_name):
name = self.guest_name
else:
name = default_name

return name
7 changes: 7 additions & 0 deletions taccsite_cms/contrib/taccsite_sample/templates/sample.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<h1>{% spaceless %}
{% if has_proper_name or not is_authenticated %}
Hello, {{name}}.
{% elif is_authenticated %}
Hello, <samp>{{name}}</samp>.
{% endif %}
{% endspaceless %}</h1>
162 changes: 162 additions & 0 deletions taccsite_cms/contrib/taccsite_sample/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
from django.test import TestCase
from django.test.client import RequestFactory

from django.contrib.auth.models import AnonymousUser, User

from cms.api import add_plugin
from cms.models import Placeholder
from cms.plugin_rendering import ContentRenderer

from taccsite_cms.contrib.taccsite_sample.cms_plugins import TaccsiteSamplePlugin
from taccsite_cms.contrib.taccsite_sample.models import TaccsiteSample

# TODO: Isolate model test from plugin test
class TaccsiteSampleTests(TestCase):
def setUp(self):
self.factory = RequestFactory()
self.auth_user = None # set via _create_user()
self.anon_user = AnonymousUser()
self.context = None # set via _create_user()
self.placeholder = Placeholder.objects.create(slot='test')
self.plugin = None # set via _populate_plugin_model()



# Helpers

def _create_auth_user(self, username='test', first_name='', last_name=''):
self.auth_user = User.objects.create_user(
username=username,
first_name=first_name,
last_name=last_name,
email='[email protected]',
password='top_secret'
)
self.context = { 'request': self.factory.get('/test/user') }
self.context['request'].user = self.auth_user

def _create_anon_user(self, guest_name='Guest'):
self.auth_user = AnonymousUser()
self.context = { 'request': self.factory.get('/test/user') }
self.context['request'].user = self.anon_user

def _populate_plugin_model(self, guest_name=None):
data = {'guest_name': guest_name} if bool(guest_name) else {}
self.plugin = add_plugin(
self.placeholder,
TaccsiteSamplePlugin,
'en',
**data
)

def _get_data(self):
"""Return context necessary for testing plugin logic"""
plugin_instance = self.plugin.get_plugin_class_instance()
data = plugin_instance.render(self.context, self.plugin, None)
return data

def _get_html(self):
"""Return markup necessary for testing plugin template"""
renderer = ContentRenderer(request=self.factory)
html = renderer.render_plugin(self.plugin, self.context)
return html

def _test_data(self, assertDict):
"""Reusable plugin logic test"""
context = self._get_data()
for key in assertDict.keys():
self.assertIn(key, context)
self.assertEqual(context[key], assertDict[key], msg=f"key='{key}'")

def _test_html(self, assertDict):
"""Reusable plugin markup test"""
html = self._get_html()
self.assertTrue(html.find('Hello') > -1)
self.assertTrue(html.find(assertDict['name']) > -1)



# Tests

# Tests: Guest User

def test_anon_user_default(self):
"""Test guest user with plugin default value(s)"""
self._create_anon_user()
self._populate_plugin_model()
assertDict = {
'name': 'Guest',
'has_proper_name': None,
'is_authenticated': False
}

self._test_data(assertDict)
self._test_html(assertDict)

def test_anon_user_custom(self):
"""Test guest user with plugin custom value(s)"""
self._create_anon_user(guest_name='Friend')
self._populate_plugin_model(guest_name='Friend')
assertDict = {
'name': 'Friend',
'has_proper_name': None,
'is_authenticated': False
}

self._test_data(assertDict)
self._test_html(assertDict)

# Tests: Auth'd User

def test_auth_user_username(self):
"""Test logged-in user with no first nor last name"""
self._create_auth_user(username='fred')
self._populate_plugin_model()
assertDict = {
'name': 'fred',
'has_proper_name': False,
'is_authenticated': True
}

self._test_data(assertDict)
self._test_html(assertDict)

def test_auth_user_lastname(self):
"""Test logged-in user with only last name"""
self._create_auth_user(username='fred', last_name='Flintstone')
self._populate_plugin_model()
assertDict = {
'name': 'fred',
'has_proper_name': False,
'is_authenticated': True
}

self._test_data(assertDict)
self._test_html(assertDict)

def test_auth_user_firstname(self):
"""Test logged-in user with only first name"""
self._create_auth_user(username='fred', first_name='Fred')
self._populate_plugin_model()
assertDict = {
'name': 'Fred',
'has_proper_name': True,
'is_authenticated': True
}

self._test_data(assertDict)
self._test_html(assertDict)

def test_auth_user_fullname(self):
"""Test logged-in user with first and last name"""
self._create_auth_user(
username='fred', first_name='Fred', last_name='Flintstone')
self._populate_plugin_model()
assertDict = {
'name': 'Fred Flintstone',
'has_proper_name': True,
'is_authenticated': True
}

self._test_data(assertDict)
self._test_html(assertDict)
47 changes: 47 additions & 0 deletions taccsite_cms/contrib/taccsite_sample/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"""
.. module:: taccsite_sample.utils
:synopsis: Utilities to process user name.
"""

def has_proper_name(user=None):
"""Whether user has enough data with which to form a proper name.
:param user: Django user object
:returns: True, False, or None (if unknown)
:rtype: bool | None
.. note::
User must be authenticated or function will return None.
"""
ret = None

if user and user.is_authenticated:
if user.first_name:
ret = True
else:
ret = False

return ret

def get_proper_name(user=None):
"""Get proper name of an authenticated user.
:param user: Django user object (authenticated)
:returns: Proper name of user
:rtype: str | None
.. note::
If user is not authenticated, we do not know any name.
If user has no first name, we give up (no name prefix logic).
"""
name = None

if has_proper_name(user):
if bool(user.first_name):
name = user.first_name
if bool(user.last_name):
name = name + ' ' + user.last_name

return name
Loading

0 comments on commit 3deb6b4

Please sign in to comment.