clone cms with menus, make menus standalone app

This commit is contained in:
bruks 2026-09-30 15:27:04 +01:00
commit afb096b7a3
1153 changed files with 318897 additions and 37 deletions

View file

@ -0,0 +1,108 @@
from datetime import datetime, timedelta
from django.utils import timezone
from cms.plugin_base import CMSPluginBase
class NoCachePlugin(CMSPluginBase):
name = 'NoCache'
module = 'Test'
render_plugin = True
cache = False
render_template = "plugins/nocache.html"
def render(self, context, instance, placeholder):
context['now'] = datetime.now().microsecond
return context
class LegacyCachePlugin(CMSPluginBase):
name = 'NoCache'
module = 'Test'
render_plugin = True
# NOTE: We have both the old mechanism...
cache = False
render_template = "plugins/nocache.html"
# And the new...
def get_cache_expiration(self, request, instance, placeholder):
"""Content is only valid until for 30 seconds."""
return 30
def render(self, context, instance, placeholder):
context['now'] = datetime.now().microsecond
return context
class TTLCacheExpirationPlugin(CMSPluginBase):
name = 'TTLCacheExpiration'
module = 'Test'
render_plugin = True
render_template = "plugins/nocache.html"
def get_cache_expiration(self, request, instance, placeholder):
"""Content is only valid for the next 50 seconds."""
return 50
def render(self, context, instance, placeholder):
context['now'] = datetime.now().microsecond
return context
class TimeDeltaCacheExpirationPlugin(CMSPluginBase):
name = 'DateTimeCacheExpiration'
module = 'Test'
render_plugin = True
render_template = "plugins/nocache.html"
def get_cache_expiration(self, request, instance, placeholder):
"""Content is only valid for the next 45 seconds."""
return timedelta(seconds=45)
def render(self, context, instance, placeholder):
context['now'] = datetime.now().microsecond
return context
class DateTimeCacheExpirationPlugin(CMSPluginBase):
name = 'DateTimeCacheExpiration'
module = 'Test'
render_plugin = True
render_template = "plugins/nocache.html"
def get_cache_expiration(self, request, instance, placeholder):
"""Content is only valid until 40 seconds from now."""
now = timezone.now()
return now + timedelta(seconds=40)
def render(self, context, instance, placeholder):
context['now'] = datetime.now().microsecond
return context
class VaryCacheOnPlugin(CMSPluginBase):
name = 'DateTimeCacheExpiration'
module = 'Test'
render_plugin = True
render_template = "plugins/nocache.html"
def get_vary_cache_on(self, request, instance, placeholder):
return ['country-code', ]
def render(self, context, instance, placeholder):
request = context.get('request')
country_code = request.headers.get('Country-Code') or "any"
context['now'] = country_code
return context
class SekizaiPlugin(CMSPluginBase):
name = 'WITH SEki'
module = 'Test'
render_plugin = True
render_template = "plugins/sekizai.html"
def render(self, context, instance, placeholder):
context['now'] = datetime.now().microsecond
return context

View file

@ -0,0 +1,18 @@
from django.utils.translation import gettext as _
from cms.models.pluginmodel import CMSPlugin
from cms.plugin_base import CMSPluginBase
from cms.plugin_pool import plugin_pool
class ExtraContextPlugin(CMSPluginBase):
model = CMSPlugin
name = _("Extra Context")
render_template = "extra_context_plugin.html"
admin_preview = False
def render(self, context, instance, placeholder):
return context
plugin_pool.register_plugin(ExtraContextPlugin)

View file

@ -0,0 +1,30 @@
from cms.plugin_base import CMSPluginBase
from cms.plugin_pool import plugin_pool
from .models import Link
class LinkPlugin(CMSPluginBase):
model = Link
name = 'Link'
text_enabled = True
allow_children = True
render_template = 'pluginapp/link/link.html'
def render(self, context, instance, placeholder):
context['link'] = instance.get_link()
return super().render(context, instance, placeholder)
class BuggyPlugin(CMSPluginBase):
name = 'Bugs Bunny'
render_template = 'pluginapp/link/bugs.html'
def render(self, context, instance, placeholder):
context["result"] = 1 / 0 # Finally we'd find out, won't we?
return super().render(context, instance, placeholder)
plugin_pool.register_plugin(LinkPlugin)
plugin_pool.register_plugin(BuggyPlugin)

View file

@ -0,0 +1,23 @@
from django.db import models
from cms.models import CMSPlugin
class Link(CMSPlugin):
name = models.CharField(
verbose_name='Display name',
max_length=255,
)
external_link = models.URLField(
verbose_name='External link',
max_length=2040,
)
def __str__(self):
return self.name or str(self.pk)
def get_short_description(self):
return f'{self.name} ({self.get_link()})'
def get_link(self):
return self.external_link

View file

@ -0,0 +1,10 @@
from django.contrib import admin
from django.contrib.admin import ModelAdmin
from cms.test_utils.project.pluginapp.plugins.manytomany_rel.models import (
Article,
Section,
)
admin.site.register(Section, ModelAdmin)
admin.site.register(Article, ModelAdmin)

View file

@ -0,0 +1,70 @@
from django.contrib import admin
from django.utils.translation import gettext as _
from cms.plugin_base import CMSPluginBase
from cms.plugin_pool import plugin_pool
from cms.test_utils.project.pluginapp.plugins.manytomany_rel.models import (
Article,
ArticlePluginModel,
PluginModelWithFKFromModel,
PluginModelWithM2MToModel,
)
class ArticlePlugin(CMSPluginBase):
model = ArticlePluginModel
name = _("Articles")
render_template = "articles.html"
admin_preview = False
filter_horizontal = ['sections']
def render(self, context, instance, placeholder):
article_qs = Article.objects.filter(section__in=instance.sections.all())
context.update({'instance': instance,
'article_qs': article_qs,
'placeholder': placeholder})
return context
class ArticlePluginAdmin(admin.ModelAdmin):
filter_horizontal = ['sections']
class ArticleDynamicTemplatePlugin(CMSPluginBase):
model = ArticlePluginModel
name = _("Articles")
admin_preview = False
def get_render_template(self, context, instance, placeholder):
if instance.title == 'custom template':
return "articles_custom.html"
else:
return "articles.html"
def render(self, context, instance, placeholder):
article_qs = Article.objects.filter(section__in=instance.sections.all())
context.update({'instance': instance,
'article_qs': article_qs,
'placeholder': placeholder})
return context
###
class PluginWithFKFromModel(CMSPluginBase):
model = PluginModelWithFKFromModel
render_template = "articles.html"
class PluginWithM2MToModel(CMSPluginBase):
model = PluginModelWithM2MToModel
render_template = "articles.html"
plugin_pool.register_plugin(ArticlePlugin)
plugin_pool.register_plugin(ArticleDynamicTemplatePlugin)
plugin_pool.register_plugin(PluginWithM2MToModel)
plugin_pool.register_plugin(PluginWithFKFromModel)
# Used to test integrity of plugin form
admin.site.register(ArticlePluginModel, ArticlePluginAdmin)

View file

@ -0,0 +1,56 @@
from django.db import models
from cms.models import CMSPlugin
class Article(models.Model):
title = models.CharField(max_length=50)
section = models.ForeignKey('Section', on_delete=models.CASCADE)
def __str__(self):
return f"{self.title} -- {self.section}"
class Section(models.Model):
name = models.CharField(max_length=50)
def __str__(self):
return self.name
class ArticlePluginModel(CMSPlugin):
title = models.CharField(max_length=50)
sections = models.ManyToManyField('Section')
def __str__(self):
return self.title
def copy_relations(self, oldinstance):
self.sections.set(oldinstance.sections.all())
class FKModel(models.Model):
fk_field = models.ForeignKey('PluginModelWithFKFromModel', on_delete=models.CASCADE)
class M2MTargetModel(models.Model):
title = models.CharField(max_length=50)
class PluginModelWithFKFromModel(CMSPlugin):
title = models.CharField(max_length=50)
def copy_relations(self, oldinstance):
# Like suggested in the docs
for associated_item in oldinstance.fkmodel_set.all():
associated_item.pk = None
associated_item.fk_field = self
associated_item.save()
class PluginModelWithM2MToModel(CMSPlugin):
m2m_field = models.ManyToManyField(M2MTargetModel)
def copy_relations(self, oldinstance):
# Like suggested in the docs
self.m2m_field.set(oldinstance.m2m_field.all())

View file

@ -0,0 +1,50 @@
from cms.plugin_base import CMSPluginBase
from cms.plugin_pool import plugin_pool
from .models import (
TestPluginModel,
TestPluginModel2,
TestPluginModel3,
TestPluginModel4,
TestPluginModel5,
)
class TestPlugin(CMSPluginBase):
model = TestPluginModel
render_template = 'cms/content.html'
plugin_pool.register_plugin(TestPlugin)
class TestPlugin2(CMSPluginBase):
model = TestPluginModel2
render_template = 'cms/content.html'
plugin_pool.register_plugin(TestPlugin2)
class TestPlugin3(CMSPluginBase):
model = TestPluginModel3
render_template = 'cms/content.html'
plugin_pool.register_plugin(TestPlugin3)
class TestPlugin4(CMSPluginBase):
model = TestPluginModel4
render_template = 'cms/content.html'
plugin_pool.register_plugin(TestPlugin4)
class TestPlugin5(CMSPluginBase):
model = TestPluginModel5
render_template = 'cms/content.html'
plugin_pool.register_plugin(TestPlugin5)

View file

@ -0,0 +1,27 @@
from cms.models.pluginmodel import CMSPlugin
class TestPluginModel(CMSPlugin):
pass
class TestPluginModel2(CMSPlugin):
class Meta:
db_table = 'meta_testpluginmodel2'
app_label = 'meta'
class TestPluginModel3(CMSPlugin):
class Meta:
app_label = 'one_thing'
class TestPluginModel4(CMSPlugin):
class Meta:
db_table = 'or_another_4'
class TestPluginModel5(CMSPlugin):
class Meta:
app_label = 'one_thing'
db_table = 'or_another_5'

View file

@ -0,0 +1,41 @@
from cms.api import add_plugin
from cms.plugin_base import CMSPluginBase
from cms.plugin_pool import plugin_pool
from .forms import MultiColumnForm
from .models import MultiColumns
class MultiColumnPlugin(CMSPluginBase):
model = MultiColumns
module = "Multi Columns"
name = "Multi Columns"
render_template = 'pluginapp/multicolumn/multicolumn.html'
allow_children = True
child_classes = ["ColumnPlugin"]
form = MultiColumnForm
def save_model(self, request, obj, form, change):
response = super().save_model(
request, obj, form, change
)
for x in range(int(form.cleaned_data['create'])):
add_plugin(
placeholder=obj.placeholder,
plugin_type=ColumnPlugin.__name__,
language=obj.language,
target=obj,
)
return response
class ColumnPlugin(CMSPluginBase):
module = "Multi Columns"
name = "Column"
render_template = 'pluginapp/multicolumn/column.html'
parent_classes = ["MultiColumnPlugin"]
allow_children = True
plugin_pool.register_plugin(MultiColumnPlugin)
plugin_pool.register_plugin(ColumnPlugin)

View file

@ -0,0 +1,29 @@
from django import forms
from .models import MultiColumns
class MultiColumnForm(forms.ModelForm):
NUM_COLUMNS = (
(0, "0"),
(1, "1"),
(2, "2"),
(3, "3"),
(4, "4"),
(5, "5"),
(6, "6"),
(7, "7"),
(8, "8"),
(9, "9"),
(10, "10"),
)
create = forms.ChoiceField(
choices=NUM_COLUMNS,
label="Create Columns",
help_text="Create this number of columns"
)
class Meta:
model = MultiColumns
exclude = ('page', 'position', 'placeholder', 'language', 'plugin_type')

View file

@ -0,0 +1,11 @@
from cms.models import CMSPlugin
class MultiColumns(CMSPlugin):
"""
A plugin that has sub Column classes
"""
def __str__(self):
plugins = self.child_plugin_instances or []
return f"{len(plugins)} columns"

View file

@ -0,0 +1,39 @@
from cms.api import add_plugin
from cms.plugin_base import CMSPluginBase
from cms.plugin_pool import plugin_pool
from .forms import MultiWrapForm
class MultiWrapPlugin(CMSPluginBase):
module = "Multi Wraps"
name = "Multi Wrap"
render_template = 'pluginapp/multiwrap/multiwrap.html'
allow_children = True
child_classes = ["WrapPlugin"]
form = MultiWrapForm
def save_model(self, request, obj, form, change):
response = super().save_model(
request, obj, form, change
)
for x in range(int(form.cleaned_data['create'])):
add_plugin(
placeholder=obj.placeholder,
plugin_type=WrapPlugin.__name__,
language=obj.language,
target=obj,
)
return response
class WrapPlugin(CMSPluginBase):
module = "Multi Wraps"
name = "Wrap"
render_template = 'pluginapp/multiwrap/wrap.html'
parent_classes = ["MultiWrapPlugin"]
allow_children = True
plugin_pool.register_plugin(MultiWrapPlugin)
plugin_pool.register_plugin(WrapPlugin)

View file

@ -0,0 +1,29 @@
from django import forms
from cms.models import CMSPlugin
class MultiWrapForm(forms.ModelForm):
NUM_WRAPS = (
(0, "0"),
(1, "1"),
(2, "2"),
(3, "3"),
(4, "4"),
(5, "5"),
(6, "6"),
(7, "7"),
(8, "8"),
(9, "9"),
(10, "10"),
)
create = forms.ChoiceField(
choices=NUM_WRAPS,
label="Create Wraps",
help_text="Create this number of wraps"
)
class Meta:
model = CMSPlugin
exclude = ('page', 'position', 'placeholder', 'language', 'plugin_type')

View file

@ -0,0 +1,11 @@
from cms.plugin_base import CMSPluginBase
from cms.plugin_pool import plugin_pool
class NoCustomModel(CMSPluginBase):
name = 'NoCustomModel'
render_plugin = True
render_template = "plugins/no_custom_model.html"
plugin_pool.register_plugin(NoCustomModel)

View file

@ -0,0 +1,52 @@
from cms.plugin_base import CMSPluginBase
from cms.plugin_pool import plugin_pool
from . import models
class RevDescUnalteredP(CMSPluginBase):
model = models.UnalteredPM
render_template = 'cms/content.html'
plugin_pool.register_plugin(RevDescUnalteredP)
class RevDescNoRelNmeP(CMSPluginBase):
model = models.NoRelNmePM
render_template = 'cms/content.html'
plugin_pool.register_plugin(RevDescNoRelNmeP)
class RevDescNoRelQNmeP(CMSPluginBase):
model = models.NoRelQNmePM
render_template = 'cms/content.html'
plugin_pool.register_plugin(RevDescNoRelQNmeP)
class RevDescCustomRelQNmeP(CMSPluginBase):
model = models.CustomRelQNmePM
render_template = 'cms/content.html'
plugin_pool.register_plugin(RevDescCustomRelQNmeP)
class RevDescCustomRelNmeP(CMSPluginBase):
model = models.CustomRelNmePM
render_template = 'cms/content.html'
plugin_pool.register_plugin(RevDescCustomRelNmeP)
class RevDescCustomRelNmeAndRelQNmeP(CMSPluginBase):
model = models.CustomRelNmeAndRelQNmePM
render_template = 'cms/content.html'
plugin_pool.register_plugin(RevDescCustomRelNmeAndRelQNmeP)

View file

@ -0,0 +1,57 @@
from django.db import models
from cms.models import CMSPlugin
# sorry for the cryptic names. But we were hitting max lengths on Django 1.6
# and 1.7 with the too long names otherwise.
class UnalteredPM(CMSPlugin):
title = models.CharField(max_length=50)
search_fields = ['title']
class NoRelNmePM(CMSPlugin):
cmsplugin_ptr = models.OneToOneField(CMSPlugin, related_name='+', parent_link=True, on_delete=models.CASCADE)
title = models.CharField(max_length=50)
search_fields = ['title']
class NoRelQNmePM(CMSPlugin):
cmsplugin_ptr = models.OneToOneField(CMSPlugin, related_query_name='+', parent_link=True, on_delete=models.CASCADE)
title = models.CharField(max_length=50)
search_fields = ['title']
class CustomRelQNmePM(CMSPlugin):
cmsplugin_ptr = models.OneToOneField(
CMSPlugin,
related_query_name='reldesc_custom_relqn',
parent_link=True,
on_delete=models.CASCADE
)
title = models.CharField(max_length=50)
search_fields = ['title']
class CustomRelNmePM(CMSPlugin):
cmsplugin_ptr = models.OneToOneField(
CMSPlugin,
related_name='reldesc_custom_reln',
parent_link=True,
on_delete=models.CASCADE
)
title = models.CharField(max_length=50)
search_fields = ['title']
class CustomRelNmeAndRelQNmePM(CMSPlugin):
cmsplugin_ptr = models.OneToOneField(
CMSPlugin,
related_name='reldesc_custom_reln2',
related_query_name='reldesc_custom_relqn2',
parent_link=True,
on_delete=models.CASCADE
)
title = models.CharField(max_length=50)
search_fields = ['title']

View file

@ -0,0 +1,29 @@
from cms.plugin_base import CMSPluginBase
from cms.plugin_pool import plugin_pool
from .models import Style
class StylePlugin(CMSPluginBase):
model = Style
name = 'Style'
render_template = 'pluginapp/style/style.html'
allow_children = True
fieldsets = (
(None, {
'fields': (
'label',
('class_name', 'tag_type'),
)
}),
('Advanced settings', {
'classes': ('collapse',),
'fields': (
'additional_classes',
),
}),
)
plugin_pool.register_plugin(StylePlugin)

View file

@ -0,0 +1,64 @@
from django.db import models
from cms.models import CMSPlugin
CLASS_CHOICES = ['container', 'content', 'teaser']
CLASS_CHOICES = tuple((entry, entry) for entry in CLASS_CHOICES)
TAG_CHOICES = [
'div', 'article', 'section', 'header', 'footer', 'aside', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'
]
TAG_CHOICES = tuple((entry, entry) for entry in TAG_CHOICES)
class Style(CMSPlugin):
"""
Renders a given ``TAG_CHOICES`` element with additional attributes
"""
label = models.CharField(
verbose_name='Label',
blank=True,
max_length=255,
help_text='Overrides the display name in the structure mode.',
)
tag_type = models.CharField(
verbose_name='Tag type',
choices=TAG_CHOICES,
default=TAG_CHOICES[0][0],
max_length=255,
)
class_name = models.CharField(
verbose_name='Class name',
choices=CLASS_CHOICES,
default=CLASS_CHOICES[0][0],
blank=True,
max_length=255,
)
additional_classes = models.CharField(
verbose_name='Additional classes',
blank=True,
max_length=255,
)
def __str__(self):
return self.label or self.tag_type or str(self.pk)
def get_short_description(self):
# display format:
# Style label <tag> .list.of.classes #id
display = []
classes = []
if self.label:
display.append(self.label)
if self.tag_type:
display.append(f'<{self.tag_type}>')
if self.class_name:
classes.append(self.class_name)
if self.additional_classes:
classes.extend(item.strip() for item in self.additional_classes.split(',') if item.strip())
display.append('.{}'.format('.'.join(classes)))
return ' '.join(display)
def get_additional_classes(self):
return ' '.join(item.strip() for item in self.additional_classes.split(',') if item.strip())

View file

@ -0,0 +1,35 @@
from cms.plugin_base import CMSPluginBase
class NonExisitngRenderTemplate(CMSPluginBase):
name = 'SubTest'
module = 'Test'
render_template = 'i_do_not_exist.html'
allow_children = True
class NoSubPluginRender(CMSPluginBase):
name = 'SubSubTest'
module = 'Test'
render_template = 'plugins/sub_empty_plugin.html'
allow_children = True
class NoRender(CMSPluginBase):
name = 'norender'
class NoRenderButChildren(CMSPluginBase):
name = 'norender2'
render_plugin = False
allow_children = True
class DynTemplate(CMSPluginBase):
name = 'norender3'
render_plugin = True
def get_render_template(self):
raise KeyError('asd')
render_template = property(get_render_template)