Skip to content

Commit

Permalink
Static Alias Placeholder functionality (django CMS Static Placeholder…
Browse files Browse the repository at this point in the history
… replacement) (#59)

* Add a new identifier field

* Created a tag and a start to a suite of tests for the feature

* Updated the implementation to create a default category and create an alias if one doesn't yet exist for the static code

* Added versioning integration with a default user

* Added a template and versioning tests, highlights some weaknesses in the previous configuration

* Added the latest version of the model migration
  • Loading branch information
Aiky30 authored Jul 28, 2020
1 parent 13efa5e commit a28d766
Show file tree
Hide file tree
Showing 14 changed files with 492 additions and 18 deletions.
1 change: 1 addition & 0 deletions djangocms_alias/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ def save_model(self, request, obj, form, change):
class AliasAdmin(admin.ModelAdmin):
list_display = ['name', 'category']
fields = ('category',)
readonly_fields = ('static_code', 'site')

def get_urls(self):
return urlpatterns + super().get_urls()
Expand Down
4 changes: 2 additions & 2 deletions djangocms_alias/cms_menus.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ class AliasDisableMenu(Modifier):

def modify(self, request, nodes, namespace, root_id, post_cut, breadcrumb):
if (
request.toolbar.app_name == PLUGIN_URL_NAME_PREFIX or
isinstance(request.toolbar.obj, AliasContent)
request.toolbar.app_name == PLUGIN_URL_NAME_PREFIX
or isinstance(request.toolbar.obj, AliasContent)
):
return []
return nodes
Expand Down
2 changes: 2 additions & 0 deletions djangocms_alias/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@
SET_ALIAS_POSITION_URL_NAME = '{}_set_alias_position'.format(PLUGIN_URL_NAME_PREFIX) # noqa: E501
SELECT2_ALIAS_URL_NAME = '{}_select2'.format(PLUGIN_URL_NAME_PREFIX)
USAGE_ALIAS_URL_NAME = '{}_alias_usage'.format(PLUGIN_URL_NAME_PREFIX)

DEFAULT_STATIC_ALIAS_CATEGORY_NAME = "Static Alias"
6 changes: 5 additions & 1 deletion djangocms_alias/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,11 @@ def save(self):


class CreateAliasWizardForm(forms.Form):
name = forms.CharField(required=True, widget=AdminTextInputWidget())
name = forms.CharField(
label=_('Name'),
required=True,
widget=AdminTextInputWidget()
)
category = forms.ModelChoiceField(
queryset=Category.objects.all(),
required=True,
Expand Down
40 changes: 40 additions & 0 deletions djangocms_alias/migrations/0002_auto_20200723_1424.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Generated by Django 2.2.13 on 2020-07-23 14:24

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


class Migration(migrations.Migration):

dependencies = [
('sites', '0002_alter_domain_unique'),
('djangocms_alias', '0001_initial'),
]

operations = [
migrations.AddField(
model_name='alias',
name='creation_method',
field=models.CharField(blank=True, choices=[('template', 'by template'), ('code', 'by code')], default='code', max_length=20, verbose_name='creation_method'),
),
migrations.AddField(
model_name='alias',
name='site',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='sites.Site'),
),
migrations.AddField(
model_name='alias',
name='static_code',
field=models.CharField(blank=True, help_text='To render the alias in templates.', max_length=255, null=True, verbose_name='static code'),
),
migrations.AlterField(
model_name='categorytranslation',
name='master',
field=parler.fields.TranslationsForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='translations', to='djangocms_alias.Category'),
),
migrations.AlterUniqueTogether(
name='alias',
unique_together={('static_code', 'site')},
),
]
25 changes: 25 additions & 0 deletions djangocms_alias/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from collections import defaultdict

from django.conf import settings
from django.contrib.sites.models import Site
from django.db import models, transaction
from django.db.models import F, Q
from django.utils.encoding import force_text
Expand Down Expand Up @@ -68,6 +69,16 @@ def get_absolute_url(self):


class Alias(models.Model):
CREATION_BY_TEMPLATE = 'template'
CREATION_BY_CODE = 'code'
CREATION_METHODS = (
(CREATION_BY_TEMPLATE, _('by template')),
(CREATION_BY_CODE, _('by code')),
)
creation_method = models.CharField(
verbose_name=_('creation_method'), choices=CREATION_METHODS,
default=CREATION_BY_CODE, max_length=20, blank=True,
)
category = models.ForeignKey(
Category,
verbose_name=_('category'),
Expand All @@ -78,11 +89,20 @@ class Alias(models.Model):
verbose_name=_('position'),
default=0,
)
static_code = models.CharField(
verbose_name=_('static code'),
max_length=255,
blank=True,
null=True,
help_text=_('To render the alias in templates.')
)
site = models.ForeignKey(Site, on_delete=models.CASCADE, null=True, blank=True)

class Meta:
verbose_name = _('alias')
verbose_name_plural = _('aliases')
ordering = ['position']
unique_together = (('static_code', 'site'),) # Only restrict instances that have a site specified

def __init__(self, *args, **kwargs):
self._plugins_cache = {}
Expand Down Expand Up @@ -161,8 +181,13 @@ def get_content(self, language=None, show_draft_content=False):
).filter(language=language)

if show_draft_content and is_versioning_enabled():
from djangocms_versioning.constants import DRAFT, PUBLISHED
from djangocms_versioning.helpers import remove_published_where

# Ensure that we are getting the latest valid content, the top most version can become
# archived with a previous version re-published
qs = remove_published_where(qs)
qs = qs.filter(Q(versions__state=DRAFT) | Q(versions__state=PUBLISHED)).order_by('-versions__created')

self._content_cache[language] = qs.first()
return self._content_cache[language]
Expand Down
120 changes: 119 additions & 1 deletion djangocms_alias/templatetags/djangocms_alias_tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,22 @@

from django import template

from cms.templatetags.cms_tags import PlaceholderOptions
from cms.toolbar.utils import get_toolbar_from_request
from cms.utils import get_current_site, get_language_from_request
from cms.utils.i18n import get_default_language_for_site
from cms.utils.placeholder import validate_placeholder_name
from cms.utils.urlutils import add_url_parameters, admin_reverse

from ..constants import USAGE_ALIAS_URL_NAME
from classytags.arguments import Argument, MultiValueArgument
from classytags.core import Tag

from ..constants import (
DEFAULT_STATIC_ALIAS_CATEGORY_NAME,
USAGE_ALIAS_URL_NAME,
)
from ..models import Alias, AliasContent, Category
from ..utils import is_versioning_enabled


register = template.Library()
Expand Down Expand Up @@ -40,3 +52,109 @@ def render_alias(context, instance, editable=False):
)
return content or ''
return ''


@register.tag
class StaticAlias(Tag):
"""
This template node is used to render Alias contents and is designed to be a
replacement for the CMS Static Placeholder.
eg: {% static_alias "identifier_text" %}
eg: {% static_alias "identifier_text" site %}
Keyword arguments:
static_code -- the unique identifier of the Alias
site -- If site is supplied an Alias instance will be created per site.
"""
name = 'static_alias'
options = PlaceholderOptions(
Argument('static_code', resolve=False),
MultiValueArgument('extra_bits', required=False, resolve=False),
blocks=[
('endstatic_alias', 'nodelist'),
],
)

def _get_alias(self, request, static_code, extra_bits):
alias_filter_kwargs = {
'static_code': static_code,
}
# Site
current_site = get_current_site()
if 'site' in extra_bits:
alias_filter_kwargs['site'] = current_site
else:
alias_filter_kwargs['site_id__isnull'] = True

# Try and find an Alias to render
alias = Alias.objects.filter(**alias_filter_kwargs).first()
# If there is no alias found we need to create one
if not alias:

# If versioning is enabled we can only create the records with a logged in user / staff member
if is_versioning_enabled() and not request.user.is_authenticated:
return None

language = get_default_language_for_site(current_site)
# Parlers get_or_create doesn't work well with translations, so we must perform our own get or create
default_category = Category.objects.filter(translations__name=DEFAULT_STATIC_ALIAS_CATEGORY_NAME).first()
if not default_category:
default_category = Category.objects.create(name=DEFAULT_STATIC_ALIAS_CATEGORY_NAME)

alias_creation_kwargs = {
'static_code': static_code,
'creation_method': Alias.CREATION_BY_TEMPLATE
}
# Site
if 'site' in extra_bits:
alias_creation_kwargs['site'] = current_site

alias = Alias.objects.create(category=default_category, **alias_creation_kwargs)
alias_content = AliasContent.objects.create(
alias=alias,
name=static_code,
language=language,
)

if is_versioning_enabled():
from djangocms_versioning.models import Version

Version.objects.create(content=alias_content, created_by=request.user)

return alias

def render_tag(self, context, static_code, extra_bits, nodelist=None):
request = context.get('request')

if not static_code or not request:
# an empty string was passed in or the variable is not available in the context
if nodelist:
return nodelist.render(context)
return ''

validate_placeholder_name(static_code)

toolbar = get_toolbar_from_request(request)
renderer = toolbar.get_content_renderer()
alias = self._get_alias(request, static_code, extra_bits)

if not alias:
return ''

# Get draft contents in edit or preview mode?
get_draft_content = False
if toolbar.edit_mode_active or toolbar.preview_mode_active:
get_draft_content = True

language = get_language_from_request(request)
placeholder = alias.get_placeholder(language=language, show_draft_content=get_draft_content)

if placeholder:
content = renderer.render_placeholder(
placeholder=placeholder,
context=context,
nodelist=nodelist,
)
return content
return ''
4 changes: 2 additions & 2 deletions djangocms_alias/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,8 +233,8 @@ def render_replace_response(request, new_plugins, source_placeholder=None,
@transaction.atomic
def set_alias_position_view(request):
if (
not request.user.is_staff or
not request.user.has_perm('djangocms_alias.change_alias')
not request.user.is_staff
or not request.user.has_perm('djangocms_alias.change_alias')
):
raise PermissionDenied

Expand Down
5 changes: 5 additions & 0 deletions test_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@
'TEMPLATE_DIRS': [
os.path.join('tests', 'templates'),
],
'CMS_TEMPLATES': (
("fullwidth.html", "Fullwidth"),
("page.html", "Normal page"),
('static_alias.html', 'Static Alias Template'),
),
'PARLER_LANGUAGES': {
1: [
{
Expand Down
5 changes: 4 additions & 1 deletion tests/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ def _get_draft_page_placeholder(self):
page_content = create_title(self.language, 'Draft Page', self.page, created_by=self.superuser)
return page_content.get_placeholders().get(slot='content')

def _create_alias(self, plugins=None, name='test alias', category=None, position=0, language=None, published=True):
def _create_alias(self, plugins=None, name='test alias', category=None, position=0,
language=None, published=True, static_code="", site=None):
if language is None:
language = self.language
if category is None:
Expand All @@ -81,6 +82,8 @@ def _create_alias(self, plugins=None, name='test alias', category=None, position
alias = AliasModel.objects.create(
category=category,
position=position,
static_code=static_code,
site=site,
)
alias_content = AliasContent.objects.create(
alias=alias,
Expand Down
13 changes: 13 additions & 0 deletions tests/templates/static_alias.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{% extends "base.html" %}
{% load cms_tags djangocms_alias_tags %}

{% block title %}{% page_attribute 'title' %}{% endblock title %}

{% block content %}
<h2>Static Alias tags</h2>


{% static_alias "template_example_global_alias_code" %}
{% endblock content %}


Loading

0 comments on commit a28d766

Please sign in to comment.