Skip to content

Commit

Permalink
Merge 1a38922 into d183828
Browse files Browse the repository at this point in the history
  • Loading branch information
fsbraun committed Dec 16, 2021
2 parents d183828 + 1a38922 commit cb385eb
Show file tree
Hide file tree
Showing 25 changed files with 470 additions and 74 deletions.
1 change: 1 addition & 0 deletions changes/694.feature
@@ -0,0 +1 @@
Add content hub functionality by adding a CategoryListView and apphook configuration for urls
43 changes: 33 additions & 10 deletions djangocms_blog/admin.py
Expand Up @@ -85,14 +85,34 @@ def queryset(self, request, queryset):
raise admin.options.IncorrectLookupParameters(e)


class BlogCategoryAdmin(ModelAppHookConfig, TranslatableAdmin):
class BlogCategoryAdmin(FrontendEditableAdminMixin, ModelAppHookConfig, TranslatableAdmin):
form = CategoryAdminForm
list_display = [
"name",
"parent",
"app_config",
"all_languages_column",
"priority",
]
fieldsets = (
(None, {
"fields": ("parent", "app_config", "name", "meta_description")
}),
(
_("Info"),
{
"fields": ("abstract", "priority",),
"classes": ("collapse",),
},
),
(
_("Images"),
{
"fields": ("main_image", "main_image_thumbnail", "main_image_full"),
"classes": ("collapse",),
},
),
)

def get_prepopulated_fields(self, request, obj=None):
app_config_default = self._app_config_select(request, obj)
Expand Down Expand Up @@ -122,7 +142,7 @@ class PostAdmin(PlaceholderAdminMixin, FrontendEditableAdminMixin, ModelAppHookC
if apps.is_installed("djangocms_blog.liveblog"):
actions += ["enable_liveblog", "disable_liveblog"]
_fieldsets = [
(None, {"fields": ["title", "subtitle", "slug", "publish", ["categories", "app_config"]]}),
(None, {"fields": ["title", "subtitle", "slug", ["publish", "pinned"], ["categories", "app_config"]]}),
# left empty for sites, author and related fields
(None, {"fields": [[]]}),
(
Expand Down Expand Up @@ -381,14 +401,10 @@ def get_fieldsets(self, request, obj=None):

fsets = deepcopy(self._fieldsets)
related_posts = []
if config:
abstract = bool(config.use_abstract)
placeholder = bool(config.use_placeholder)
related = bool(config.use_related)
else:
abstract = get_setting("USE_ABSTRACT")
placeholder = get_setting("USE_PLACEHOLDER")
related = get_setting("USE_RELATED")
abstract = bool(getattr(config, "use_abstract", get_setting("USE_ABSTRACT")))
placeholder = bool(getattr(config, "use_placeholder", get_setting("USE_PLACEHOLDER")))
related = getattr(config, "use_related", get_setting("USE_RELATED"))
related = bool(int(related)) if isinstance(related, str) and related.isnumeric() else bool(related)
if related:
related_posts = self._get_available_posts(config)
if abstract:
Expand Down Expand Up @@ -480,6 +496,7 @@ def get_fieldsets(self, request, obj=None):
_("Layout"),
{
"fields": (
"config.urlconf",
"config.paginate_by",
"config.url_patterns",
"config.template_prefix",
Expand Down Expand Up @@ -550,6 +567,12 @@ def save_model(self, request, obj, form, change):
from menus.menu_pool import menu_pool

menu_pool.clear(all=True)
"""
Reload urls when changing url config
"""
if "config.urlconf" in form.changed_data:
from cms.signals.apphook import trigger_restart
trigger_restart()
return super().save_model(request, obj, form, change)


Expand Down
27 changes: 25 additions & 2 deletions djangocms_blog/cms_appconfig.py
Expand Up @@ -83,8 +83,24 @@ class BlogConfigForm(AppDataForm):
label=_("Use abstract field"), required=False, initial=get_setting("USE_ABSTRACT")
)
#: Enable related posts (default: :ref:`USE_RELATED <USE_RELATED>`)
use_related = forms.BooleanField(
label=_("Enable related posts"), required=False, initial=get_setting("USE_RELATED")
use_related = forms.ChoiceField(
label=_("Enable related posts"),
required=False,
initial=int(get_setting("USE_RELATED")),
choices=(
(0, _("No")),
(1, _("Yes, from this blog config")),
(2, _("Yes, from this site")),
),
)
#: Adjust urlconf (default: :ref:`USE_RELATED <USE_RELATED>`)
urlconf = forms.ChoiceField(
label=_("URL config"),
required=False,
initial=get_setting("URLCONF") if isinstance(get_setting("URLCONF"), str) else get_setting("URLCONF")[0][0],
choices=(
[(get_setting("URLCONF"), "---")] if isinstance(get_setting("URLCONF"), str) else get_setting("URLCONF")
),
)
#: Set author by default (default: :ref:`AUTHOR_DEFAULT <AUTHOR_DEFAULT>`)
set_author = forms.BooleanField(
Expand Down Expand Up @@ -207,5 +223,12 @@ class BlogConfigForm(AppDataForm):
help_text=_("Emits a desktop notification -if enabled- when editing a published post"),
)

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
"""Remove urlconf from form if no apphook-based url config is enabled"""
if isinstance(get_setting("URLCONF"), str):
self.fields["urlconf"].widget = forms.HiddenInput()
self.fields["urlconf"].label = "" # Admin otherwise displays label for hidden field


setup_config(BlogConfigForm, BlogConfig)
8 changes: 6 additions & 2 deletions djangocms_blog/cms_apps.py
Expand Up @@ -11,7 +11,7 @@
@apphook_pool.register
class BlogApp(AutoCMSAppMixin, CMSConfigApp):
name = _("Blog")
_urls = [get_setting("URLCONF")]
_urls = [get_setting("URLCONF") if isinstance(get_setting("URLCONF"), str) else get_setting("URLCONF")[0][0]]
app_name = "djangocms_blog"
app_config = BlogConfig
_menus = [BlogCategoryMenu]
Expand All @@ -28,7 +28,11 @@ class BlogApp(AutoCMSAppMixin, CMSConfigApp):
}

def get_urls(self, page=None, language=None, **kwargs):
return [get_setting("URLCONF")]
urlconf = get_setting("URLCONF")
if page is None or not page.application_namespace or isinstance(urlconf, str):
return [urlconf] # Single urlconf
return [getattr(self.app_config.objects.get(namespace=page.application_namespace), "urlconf",
get_setting("URLCONF")[0][0])] # Default if no urlconf is configured

@property
def urls(self):
Expand Down
11 changes: 11 additions & 0 deletions djangocms_blog/forms.py
Expand Up @@ -106,6 +106,15 @@ def available_categories(self):
return qs.namespace(self.app_config.namespace).active_translations()
return qs

@cached_property
def available_related_posts(self):
qs = Post.objects
if self.app_config:
qs = qs.active_translations()
if self.app_config.use_related == "1":
qs = qs.namespace(self.app_config.namespace)
return qs

def _post_clean_translation(self, translation):
# This is a quickfix for https://github.com/django-parler/django-parler/issues/236
# which needs to be fixed in parler
Expand All @@ -131,6 +140,8 @@ def __init__(self, *args, **kwargs):
if self.app_config and self.app_config.url_patterns == PERMALINK_TYPE_CATEGORY:
self.fields["categories"].required = True
self.fields["categories"].queryset = self.available_categories
if "related" in self.fields:
self.fields["related"].queryset = self.available_related_posts

if "app_config" in self.fields:
# Don't allow app_configs to be added here. The correct way to add an
Expand Down
49 changes: 49 additions & 0 deletions djangocms_blog/migrations/0040_auto_20211128_1503.py
@@ -0,0 +1,49 @@
# Generated by Django 3.0.14 on 2021-11-28 14:03

import django.db.models.deletion
import djangocms_text_ckeditor.fields
import filer.fields.image
from django.conf import settings
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
# ('filer', '0014_folder_permission_choices'),
# migrations.swappable_dependency(settings.FILER_IMAGE_MODEL),
('djangocms_blog', '0039_auto_20200331_2227'),
]

operations = [
migrations.AddField(
model_name='blogcategory',
name='main_image',
field=filer.fields.image.FilerImageField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='djangocms_category_image', to=settings.FILER_IMAGE_MODEL, verbose_name='main image'),
),
migrations.AddField(
model_name='blogcategory',
name='main_image_full',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='djangocms_category_full', to='filer.ThumbnailOption', verbose_name='main image full'),
),
migrations.AddField(
model_name='blogcategory',
name='main_image_thumbnail',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='djangocms_category_thumbnail', to='filer.ThumbnailOption', verbose_name='main image thumbnail'),
),
migrations.AddField(
model_name='blogcategory',
name='priority',
field=models.IntegerField(blank=True, null=True, verbose_name='priority'),
),
migrations.AddField(
model_name='blogcategorytranslation',
name='abstract',
field=djangocms_text_ckeditor.fields.HTMLField(blank=True, default='', verbose_name='abstract'),
),
migrations.AddField(
model_name='post',
name='pinned',
field=models.IntegerField(blank=True, null=True, verbose_name='priority'),
),
]
32 changes: 32 additions & 0 deletions djangocms_blog/migrations/0041_auto_20211214_1137.py
@@ -0,0 +1,32 @@
# Generated by Django 3.0.14 on 2021-12-14 10:37

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


class Migration(migrations.Migration):

dependencies = [
('djangocms_blog', '0040_auto_20211128_1503'),
]

operations = [
migrations.AlterModelOptions(
name='blogcategory',
options={'ordering': (django.db.models.expressions.OrderBy(django.db.models.expressions.F('priority'), nulls_last=True),), 'verbose_name': 'blog category', 'verbose_name_plural': 'blog categories'},
),
migrations.AlterModelOptions(
name='post',
options={'get_latest_by': 'date_published', 'ordering': (django.db.models.expressions.OrderBy(django.db.models.expressions.F('pinned'), nulls_last=True), '-date_published', '-date_created'), 'verbose_name': 'blog article', 'verbose_name_plural': 'blog articles'},
),
migrations.AlterField(
model_name='blogcategorytranslation',
name='slug',
field=models.SlugField(allow_unicode=True, blank=True, max_length=752, verbose_name='slug'),
),
migrations.AlterField(
model_name='post',
name='pinned',
field=models.IntegerField(blank=True, help_text='Pinned posts are shown in ascending order before unpinned ones. Leave blank for regular order by date.', null=True, verbose_name='pinning priority'),
),
]
57 changes: 53 additions & 4 deletions djangocms_blog/models.py
Expand Up @@ -8,6 +8,7 @@
from django.contrib.sites.shortcuts import get_current_site
from django.core.cache import cache
from django.db import models
from django.db.models import F
from django.db.models.signals import post_save, pre_delete
from django.dispatch import receiver
from django.urls import reverse
Expand All @@ -33,6 +34,7 @@
BLOG_CURRENT_POST_IDENTIFIER = get_setting("CURRENT_POST_IDENTIFIER")
BLOG_CURRENT_NAMESPACE = get_setting("CURRENT_NAMESPACE")
BLOG_PLUGIN_TEMPLATE_FOLDERS = get_setting("PLUGIN_TEMPLATE_FOLDERS")
BLOG_ALLOW_UNICODE_SLUGS = get_setting("ALLOW_UNICODE_SLUGS")


thumbnail_model = "{}.{}".format(ThumbnailOption._meta.app_label, ThumbnailOption.__name__)
Expand Down Expand Up @@ -87,7 +89,7 @@ def get_full_url(self):

class BlogCategory(BlogMetaMixin, TranslatableModel):
"""
Blog category
Blog category allows to structure content in a hierarchy of categories.
"""

parent = models.ForeignKey(
Expand All @@ -96,12 +98,43 @@ class BlogCategory(BlogMetaMixin, TranslatableModel):
date_created = models.DateTimeField(_("created at"), auto_now_add=True)
date_modified = models.DateTimeField(_("modified at"), auto_now=True)
app_config = AppHookConfigField(BlogConfig, null=True, verbose_name=_("app. config"))
priority = models.IntegerField(_("priority"), blank=True, null=True)
main_image = FilerImageField(
verbose_name=_("main image"),
blank=True,
null=True,
on_delete=models.SET_NULL,
related_name="djangocms_category_image",
)
main_image_thumbnail = models.ForeignKey(
thumbnail_model,
verbose_name=_("main image thumbnail"),
related_name="djangocms_category_thumbnail",
on_delete=models.SET_NULL,
blank=True,
null=True,
)
main_image_full = models.ForeignKey(
thumbnail_model,
verbose_name=_("main image full"),
related_name="djangocms_category_full",
on_delete=models.SET_NULL,
blank=True,
null=True,
)

translations = TranslatedFields(
name=models.CharField(_("name"), max_length=752),
slug=models.SlugField(_("slug"), max_length=752, blank=True, db_index=True),
slug=models.SlugField(
_("slug"),
max_length=752,
blank=True,
db_index=True,
allow_unicode=BLOG_ALLOW_UNICODE_SLUGS,
),
meta_description=models.TextField(verbose_name=_("category meta description"), blank=True, default=""),
meta={"unique_together": (("language_code", "slug"),)},
abstract=HTMLField(_("abstract"), blank=True, default="", configuration="BLOG_ABSTRACT_CKEDITOR"),
)

objects = AppHookConfigTranslatableManager()
Expand Down Expand Up @@ -130,6 +163,7 @@ class BlogCategory(BlogMetaMixin, TranslatableModel):
class Meta:
verbose_name = _("blog category")
verbose_name_plural = _("blog categories")
ordering = (F("priority").asc(nulls_last=True), )

def descendants(self):
children = []
Expand All @@ -141,8 +175,14 @@ def descendants(self):

@cached_property
def linked_posts(self):
"""returns all linked posts in the same appconfig namespace"""
return self.blog_posts.namespace(self.app_config.namespace)

@cached_property
def pinned_posts(self):
"""returns all linked posts which have a pinned value of at least 1"""
return self.linked_posts.filter(pinned__gt=0)

@cached_property
def count(self):
return self.linked_posts.published().count()
Expand Down Expand Up @@ -204,6 +244,9 @@ class Post(KnockerModel, BlogMetaMixin, TranslatableModel):
date_published = models.DateTimeField(_("published since"), null=True, blank=True)
date_published_end = models.DateTimeField(_("published until"), null=True, blank=True)
date_featured = models.DateTimeField(_("featured date"), null=True, blank=True)
pinned = models.IntegerField(_("pinning priority"), blank=True, null=True,
help_text=_("Pinned posts are shown in ascending order before unpinned ones. "
"Leave blank for regular order by date."))
publish = models.BooleanField(_("publish"), default=False)
categories = models.ManyToManyField(
"djangocms_blog.BlogCategory", verbose_name=_("category"), related_name="blog_posts", blank=True
Expand Down Expand Up @@ -248,7 +291,13 @@ class Post(KnockerModel, BlogMetaMixin, TranslatableModel):

translations = TranslatedFields(
title=models.CharField(_("title"), max_length=752),
slug=models.SlugField(_("slug"), max_length=752, blank=True, db_index=True, allow_unicode=True),
slug=models.SlugField(
_("slug"),
max_length=752,
blank=True,
db_index=True,
allow_unicode=BLOG_ALLOW_UNICODE_SLUGS,
),
subtitle=models.CharField(verbose_name=_("subtitle"), max_length=767, blank=True, default=""),
abstract=HTMLField(_("abstract"), blank=True, default="", configuration="BLOG_ABSTRACT_CKEDITOR"),
meta_description=models.TextField(verbose_name=_("post meta description"), blank=True, default=""),
Expand Down Expand Up @@ -309,7 +358,7 @@ class Post(KnockerModel, BlogMetaMixin, TranslatableModel):
class Meta:
verbose_name = _("blog article")
verbose_name_plural = _("blog articles")
ordering = ("-date_published", "-date_created")
ordering = (F("pinned").asc(nulls_last=True), "-date_published", "-date_created")
get_latest_by = "date_published"

def __str__(self):
Expand Down

0 comments on commit cb385eb

Please sign in to comment.