From cac615af85977ab412bc41a9aab02dcbfd3ddb09 Mon Sep 17 00:00:00 2001 From: Iacopo Spalletti Date: Wed, 10 Feb 2016 10:06:40 +0100 Subject: [PATCH 01/16] Use all_languages_column from parler 1.5, bump deps accordingly --- djangocms_blog/admin.py | 4 +++- setup.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/djangocms_blog/admin.py b/djangocms_blog/admin.py index c2fa423..99df7c8 100755 --- a/djangocms_blog/admin.py +++ b/djangocms_blog/admin.py @@ -25,6 +25,7 @@ except ImportError: class BlogCategoryAdmin(EnhancedModelAdminMixin, ModelAppHookConfig, TranslatableAdmin): + def get_prepopulated_fields(self, request, obj=None): app_config_default = self._app_config_select(request, obj) if app_config_default is None and request.method == 'GET': @@ -41,7 +42,8 @@ class PostAdmin(PlaceholderAdminMixin, FrontendEditableAdminMixin, ModelAppHookConfig, TranslatableAdmin): form = PostAdminForm list_display = [ - 'title', 'author', 'date_published', 'app_config', 'languages', 'date_published_end' + 'title', 'author', 'date_published', 'app_config', 'all_languages_column', + 'date_published_end' ] list_filter = ('app_config',) date_hierarchy = 'date_published' diff --git a/setup.py b/setup.py index 8f92519..ee00b55 100755 --- a/setup.py +++ b/setup.py @@ -27,7 +27,7 @@ setup( ], include_package_data=True, install_requires=[ - 'django-parler>=1.2', + 'django-parler>=1.5', 'django-cms>3.0.11', 'django-taggit>=0.12.2', 'django-filer', From 9e315d47216c6c95a8956fce386a37e6646c67e0 Mon Sep 17 00:00:00 2001 From: Iacopo Spalletti Date: Wed, 10 Feb 2016 10:10:09 +0100 Subject: [PATCH 02/16] Edit history --- HISTORY.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/HISTORY.rst b/HISTORY.rst index 5149e65..0bcac81 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -8,6 +8,7 @@ History * Make categories non required * Fix tests with parler>=1.6 +* Use all_languages_column to admin 0.6.3 (2015-12-22) ++++++++++++++++++ From 9bc5e5c4e660b31234fe1006a3fa794a288b6c99 Mon Sep 17 00:00:00 2001 From: Iacopo Spalletti Date: Wed, 10 Feb 2016 21:56:28 +0100 Subject: [PATCH 03/16] Update README --- README.rst | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index f21450d..76cfb03 100644 --- a/README.rst +++ b/README.rst @@ -190,12 +190,17 @@ suited for your deployment. `Auto setup `_: * Create a new django CMS page - * Go to Advanced settings and select Blog from the Application selector and - create an application configuration; + * Go to **Advanced settings** and select Blog from the **Application** selector and + create an **Application configuration**; * Eventually customise the Application instance name; * Publish the page * Restart the project instance to properly load blog urls. +.. warning:: After adding the apphook to the page you **cannot** change the **Instance Namspace** + field for the defined **AppHokConfig**; if you want to change it, create a new one + with the correct namespace, go in the CMS page **Advanced settings** and switch to the + new **Application configuration** + * Add and edit blog by creating them in the admin or using the toolbar, and the use the `django CMS frontend editor `_ to edit the blog content: From fcc9d3ace1e8476bef1863a47651e80a0b1595e6 Mon Sep 17 00:00:00 2001 From: Iacopo Spalletti Date: Thu, 11 Feb 2016 19:25:05 +0100 Subject: [PATCH 04/16] add publish button to the toolbar --- HISTORY.rst | 3 ++- djangocms_blog/admin.py | 26 +++++++++++++++++++++++++- djangocms_blog/cms_toolbar.py | 16 ++++++++++++++++ 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 0bcac81..b7f7856 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,12 +3,13 @@ History ------- -0.6.4 (unreleased) +0.7.0 (unreleased) ++++++++++++++++++ * Make categories non required * Fix tests with parler>=1.6 * Use all_languages_column to admin +* Add publish button 0.6.3 (2015-12-22) ++++++++++++++++++ diff --git a/djangocms_blog/admin.py b/djangocms_blog/admin.py index 99df7c8..d811d73 100755 --- a/djangocms_blog/admin.py +++ b/djangocms_blog/admin.py @@ -7,9 +7,12 @@ from aldryn_apphooks_config.admin import BaseAppHookConfig, ModelAppHookConfig from cms.admin.placeholderadmin import FrontendEditableAdminMixin, PlaceholderAdminMixin from django import forms from django.conf import settings +from django.conf.urls import url from django.contrib import admin +from django.core.urlresolvers import reverse, resolve +from django.http import HttpResponseRedirect from django.utils.six import callable -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import ugettext_lazy as _, get_language_from_request from parler.admin import TranslatableAdmin from .cms_appconfig import BlogConfig @@ -73,6 +76,27 @@ class PostAdmin(PlaceholderAdminMixin, FrontendEditableAdminMixin, 'default_published': 'publish' } + def get_urls(self): + urls = [ + url(r'^publish/([0-9]+)/$', self.admin_site.admin_view(self.publish_post), + name='djangocms_blog_publish_article'), + ] + urls.extend(super(PostAdmin, self).get_urls()) + return urls + + def publish_post(self, request, pk): + language = get_language_from_request(request, check_path=True) + try: + post = Post.objects.get(pk=int(pk)) + post.publish = True + post.save() + return HttpResponseRedirect(post.get_absolute_url(language)) + except Exception: + try: + return HttpResponseRedirect(request.META['HTTP_REFERER']) + except KeyError: + return HttpResponseRedirect(reverse('djangocms_blog:posts-latest')) + def languages(self, obj): return ','.join(obj.get_available_languages()) diff --git a/djangocms_blog/cms_toolbar.py b/djangocms_blog/cms_toolbar.py index b5d5d85..9892ed5 100644 --- a/djangocms_blog/cms_toolbar.py +++ b/djangocms_blog/cms_toolbar.py @@ -3,6 +3,8 @@ from __future__ import absolute_import, print_function, unicode_literals from cms.toolbar_base import CMSToolbar from cms.toolbar_pool import toolbar_pool +from cms.utils.i18n import force_language +from cms.utils.urlutils import admin_reverse, add_url_parameters from django.core.urlresolvers import reverse from django.utils.translation import override, ugettext_lazy as _ @@ -33,6 +35,19 @@ class BlogToolbar(CMSToolbar): 'admin:djangocms_blog_post_change', args=(current_post.pk,)), active=True) + def add_publish_button(self): + current_post = getattr(self.request, get_setting('CURRENT_POST_IDENTIFIER'), None) + if (self.toolbar.edit_mode and current_post and + not current_post.publish and + self.request.user.has_perm('djangocms_blog.change_post') + ): # pragma: no cover # NOQA + classes = ['cms-btn-action', 'blog-publish'] + title = _('Publish {0} now').format(current_post.app_config.object_name) + + url = admin_reverse('djangocms_blog_publish_article', args=(current_post.pk,)) + + self.toolbar.add_button(title, url=url, extra_classes=classes, side=self.toolbar.RIGHT) + def post_template_populate(self): current_post = getattr(self.request, get_setting('CURRENT_POST_IDENTIFIER'), None) if current_post and self.request.user.has_perm('djangocms_blog.change_post'): # pragma: no cover # NOQA @@ -52,3 +67,4 @@ class BlogToolbar(CMSToolbar): menu.remove_item(pagetags) except ImportError: pass + self.add_publish_button() From 33d2371647fc86071484b081506cd9c8a6133335 Mon Sep 17 00:00:00 2001 From: Iacopo Spalletti Date: Thu, 11 Feb 2016 23:02:20 +0100 Subject: [PATCH 05/16] Cleanup code --- djangocms_blog/admin.py | 4 ++-- djangocms_blog/cms_toolbar.py | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/djangocms_blog/admin.py b/djangocms_blog/admin.py index d811d73..fc68837 100755 --- a/djangocms_blog/admin.py +++ b/djangocms_blog/admin.py @@ -9,10 +9,10 @@ from django import forms from django.conf import settings from django.conf.urls import url from django.contrib import admin -from django.core.urlresolvers import reverse, resolve +from django.core.urlresolvers import reverse from django.http import HttpResponseRedirect from django.utils.six import callable -from django.utils.translation import ugettext_lazy as _, get_language_from_request +from django.utils.translation import get_language_from_request, ugettext_lazy as _ from parler.admin import TranslatableAdmin from .cms_appconfig import BlogConfig diff --git a/djangocms_blog/cms_toolbar.py b/djangocms_blog/cms_toolbar.py index 9892ed5..00a3157 100644 --- a/djangocms_blog/cms_toolbar.py +++ b/djangocms_blog/cms_toolbar.py @@ -3,8 +3,7 @@ from __future__ import absolute_import, print_function, unicode_literals from cms.toolbar_base import CMSToolbar from cms.toolbar_pool import toolbar_pool -from cms.utils.i18n import force_language -from cms.utils.urlutils import admin_reverse, add_url_parameters +from cms.utils.urlutils import admin_reverse from django.core.urlresolvers import reverse from django.utils.translation import override, ugettext_lazy as _ From 1b42655d97824ad9d7f953bc26e40e33a2e88575 Mon Sep 17 00:00:00 2001 From: Iacopo Spalletti Date: Fri, 12 Feb 2016 07:42:58 +0100 Subject: [PATCH 06/16] Add toolbar test for publish button --- tests/test_toolbar.py | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/tests/test_toolbar.py b/tests/test_toolbar.py index 90b6ccb..e3bc069 100644 --- a/tests/test_toolbar.py +++ b/tests/test_toolbar.py @@ -1,8 +1,9 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import, print_function, unicode_literals -from cms.toolbar.items import ModalItem +from cms.toolbar.items import ButtonList, ModalItem from django.core.urlresolvers import reverse +from django.utils.encoding import force_text from djangocms_blog.models import BLOG_CURRENT_POST_IDENTIFIER @@ -20,9 +21,39 @@ class ToolbarTest(BaseTest): pages = self.get_pages() request = self.get_page_request(pages[0], self.user, r'/en/blog/', edit=True) setattr(request, BLOG_CURRENT_POST_IDENTIFIER, posts[0]) + + posts[0].publish = False + posts[0].save() toolbar = CMSToolbar(request) + toolbar.populate() + toolbar.post_template_populate() toolbar.get_left_items() blog_menu = toolbar.menus['djangocms_blog'] self.assertEqual(len(blog_menu.find_items(ModalItem, url=reverse('admin:djangocms_blog_post_changelist'))), 1) self.assertEqual(len(blog_menu.find_items(ModalItem, url=reverse('admin:djangocms_blog_post_add'))), 1) self.assertEqual(len(blog_menu.find_items(ModalItem, url=reverse('admin:djangocms_blog_post_change', args=(posts[0].pk,)))), 1) + + # Publish button only appears if current post is unpublished + right = toolbar.get_right_items() + buttons = sum([item.buttons for item in right if isinstance(item, ButtonList)], []) + self.assertTrue([button for button in buttons if force_text(button.name) == 'Publish Blog now']) + + # Publish button does not appears if current post is published + posts[0].publish = True + posts[0].save() + toolbar = CMSToolbar(request) + toolbar.populate() + toolbar.post_template_populate() + right = toolbar.get_right_items() + buttons = sum([item.buttons for item in right if isinstance(item, ButtonList)], []) + self.assertFalse([button for button in buttons if force_text(button.name) == 'Publish Blog now']) + + # Publish button does not appears if other posts but the current one are unpublished + posts[1].publish = True + posts[1].save() + toolbar = CMSToolbar(request) + toolbar.populate() + toolbar.post_template_populate() + right = toolbar.get_right_items() + buttons = sum([item.buttons for item in right if isinstance(item, ButtonList)], []) + self.assertFalse([button for button in buttons if force_text(button.name) == 'Publish Blog now']) From 094525326ffc4cbc7aff38239c67f7e739f1dd18 Mon Sep 17 00:00:00 2001 From: Iacopo Spalletti Date: Fri, 12 Feb 2016 07:55:10 +0100 Subject: [PATCH 07/16] Add admin test for publish view --- tests/test_models.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/test_models.py b/tests/test_models.py index fcce681..ab8977d 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -34,6 +34,8 @@ class AdminTest(BaseTest): admin.autodiscover() def test_admin_post_views(self): + self.get_pages() + post_admin = admin.site._registry[Post] request = self.get_page_request('/', self.user, r'/en/blog/', edit=False) @@ -50,6 +52,28 @@ class AdminTest(BaseTest): self.assertContains(response, '') self.assertContains(response, '' % self.app_config_1.pk) + # Test for publish view + post.publish = False + post.save() + response = post_admin.publish_post(request, str(post.pk)) + # Redirects to current post + self.assertEqual(response.status_code, 302) + self.assertEqual(response['Location'], post.get_absolute_url()) + post = self.reload_model(post) + # post is publshed + self.assertTrue(post.publish) + + # Non-existing post is redirected to posts list + response = post_admin.publish_post(request, str('1000000')) + self.assertEqual(response.status_code, 302) + self.assertEqual(response['Location'], reverse('djangocms_blog:posts-latest')) + + # unless a referer is set + request.META['HTTP_REFERER'] = '/' + response = post_admin.publish_post(request, str('1000000')) + self.assertEqual(response.status_code, 302) + self.assertEqual(response['Location'], '/') + def test_admin_blogconfig_views(self): post_admin = admin.site._registry[BlogConfig] request = self.get_page_request('/', self.user, r'/en/blog/', edit=False) From 34039bea6fdc5bd3dd6e992cd4dd023b7df8e453 Mon Sep 17 00:00:00 2001 From: Iacopo Spalletti Date: Fri, 12 Feb 2016 08:02:23 +0100 Subject: [PATCH 08/16] Add docstrings --- djangocms_blog/admin.py | 21 ++++++++++++++++++--- djangocms_blog/cms_toolbar.py | 3 +++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/djangocms_blog/admin.py b/djangocms_blog/admin.py index fc68837..e48f3f6 100755 --- a/djangocms_blog/admin.py +++ b/djangocms_blog/admin.py @@ -77,6 +77,9 @@ class PostAdmin(PlaceholderAdminMixin, FrontendEditableAdminMixin, } def get_urls(self): + """ + Customize the modeladmin urls + """ urls = [ url(r'^publish/([0-9]+)/$', self.admin_site.admin_view(self.publish_post), name='djangocms_blog_publish_article'), @@ -85,6 +88,12 @@ class PostAdmin(PlaceholderAdminMixin, FrontendEditableAdminMixin, return urls def publish_post(self, request, pk): + """ + Admin view to publish a single post + :param request: request + :param pk: primary key of the post to publish + :return: Redirect to the post itself (if found) or fallback urls + """ language = get_language_from_request(request, check_path=True) try: post = Post.objects.get(pk=int(pk)) @@ -97,9 +106,6 @@ class PostAdmin(PlaceholderAdminMixin, FrontendEditableAdminMixin, except KeyError: return HttpResponseRedirect(reverse('djangocms_blog:posts-latest')) - def languages(self, obj): - return ','.join(obj.get_available_languages()) - def formfield_for_dbfield(self, db_field, **kwargs): field = super(PostAdmin, self).formfield_for_dbfield(db_field, **kwargs) if db_field.name == 'meta_description': @@ -111,6 +117,12 @@ class PostAdmin(PlaceholderAdminMixin, FrontendEditableAdminMixin, return field def get_fieldsets(self, request, obj=None): + """ + Customize the fieldsets according to the app settings + :param request: request + :param obj: post + :return: fieldsets configuration + """ app_config_default = self._app_config_select(request, obj) if app_config_default is None and request.method == 'GET': return super(PostAdmin, self).get_fieldsets(request, obj) @@ -156,6 +168,9 @@ class BlogConfigAdmin(BaseAppHookConfig, TranslatableAdmin): @property def declared_fieldsets(self): + """ + Fieldsets configuration + """ return [ (None, { 'fields': ('type', 'namespace', 'app_title', 'object_name') diff --git a/djangocms_blog/cms_toolbar.py b/djangocms_blog/cms_toolbar.py index 00a3157..7dfd2cc 100644 --- a/djangocms_blog/cms_toolbar.py +++ b/djangocms_blog/cms_toolbar.py @@ -35,6 +35,9 @@ class BlogToolbar(CMSToolbar): active=True) def add_publish_button(self): + """ + Adds the publish button to the toolbar if the current post is unpublished + """ current_post = getattr(self.request, get_setting('CURRENT_POST_IDENTIFIER'), None) if (self.toolbar.edit_mode and current_post and not current_post.publish and From 701454bdd235e2e562e72563322757d148ff7d27 Mon Sep 17 00:00:00 2001 From: Tadas Dailyda Date: Mon, 15 Feb 2016 12:13:36 +0200 Subject: [PATCH 09/16] fix migration 0007 to use swappable dependency --- djangocms_blog/migrations/0007_auto_20150719_0933.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/djangocms_blog/migrations/0007_auto_20150719_0933.py b/djangocms_blog/migrations/0007_auto_20150719_0933.py index 7b62d62..2594331 100644 --- a/djangocms_blog/migrations/0007_auto_20150719_0933.py +++ b/djangocms_blog/migrations/0007_auto_20150719_0933.py @@ -1,18 +1,20 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import models, migrations -import filer.fields.image -import django.utils.timezone -from django.conf import settings -import djangocms_text_ckeditor.fields import django.db.models.deletion +import django.utils.timezone +import djangocms_text_ckeditor.fields +import filer.fields.image +from django.conf import settings +from django.db import migrations, models +from filer.settings import FILER_IMAGE_MODEL class Migration(migrations.Migration): dependencies = [ ('djangocms_blog', '0006_auto_20150214_1907'), + migrations.swappable_dependency(FILER_IMAGE_MODEL), ] operations = [ From 678bd1ab9b806bdf6e7e0139209f02e62b97c085 Mon Sep 17 00:00:00 2001 From: Tadas Dailyda Date: Mon, 15 Feb 2016 13:53:06 +0200 Subject: [PATCH 10/16] remove data migration from 0010, create 0014 with optional data migration (does nothing if already migrated) --- .../migrations/0010_auto_20150923_1151.py | 29 ------------- .../migrations/0014_auto_20160215_1331.py | 43 +++++++++++++++++++ 2 files changed, 43 insertions(+), 29 deletions(-) create mode 100644 djangocms_blog/migrations/0014_auto_20160215_1331.py diff --git a/djangocms_blog/migrations/0010_auto_20150923_1151.py b/djangocms_blog/migrations/0010_auto_20150923_1151.py index 1b541d3..fa01b5f 100644 --- a/djangocms_blog/migrations/0010_auto_20150923_1151.py +++ b/djangocms_blog/migrations/0010_auto_20150923_1151.py @@ -4,37 +4,9 @@ from __future__ import unicode_literals import aldryn_apphooks_config.fields import app_data.fields import djangocms_text_ckeditor.fields -from cms.models import Page -from cms.utils.i18n import get_language_list from django.db import models, migrations -def forwards(apps, schema_editor): - BlogConfig = apps.get_model('djangocms_blog', 'BlogConfig') - BlogConfigTranslation = apps.get_model('djangocms_blog', 'BlogConfigTranslation') - Post = apps.get_model('djangocms_blog', 'Post') - BlogCategory = apps.get_model('djangocms_blog', 'BlogCategory') - GenericBlogPlugin = apps.get_model('djangocms_blog', 'GenericBlogPlugin') - LatestPostsPlugin = apps.get_model('djangocms_blog', 'LatestPostsPlugin') - AuthorEntriesPlugin = apps.get_model('djangocms_blog', 'AuthorEntriesPlugin') - config = None - for page in Page.objects.drafts().filter(application_urls='BlogApp'): - config = BlogConfig.objects.create(namespace=page.application_namespace) - for lang in get_language_list(): - title = page.get_title(lang) - translation = BlogConfigTranslation.objects.create(language_code=lang, master_id=config.pk, app_title=title) - if config: - for model in (Post, BlogCategory, GenericBlogPlugin, LatestPostsPlugin, AuthorEntriesPlugin): - for item in model.objects.all(): - item.app_config = config - item.save() - - -def backwards(apps, schema_editor): - # No need for backward data migration - pass - - class Migration(migrations.Migration): dependencies = [ @@ -120,5 +92,4 @@ class Migration(migrations.Migration): name='sites', field=models.ManyToManyField(to='sites.Site', help_text='Select sites in which to show the post. If none is set it will be visible in all the configured sites.', blank=True, verbose_name='Site(s)'), ), - migrations.RunPython(forwards, backwards) ] diff --git a/djangocms_blog/migrations/0014_auto_20160215_1331.py b/djangocms_blog/migrations/0014_auto_20160215_1331.py new file mode 100644 index 0000000..fc0a231 --- /dev/null +++ b/djangocms_blog/migrations/0014_auto_20160215_1331.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from cms.models import Page +from cms.utils.i18n import get_language_list +from django.db import migrations, models + + +def forwards(apps, schema_editor): + BlogConfig = apps.get_model('djangocms_blog', 'BlogConfig') + BlogConfigTranslation = apps.get_model('djangocms_blog', 'BlogConfigTranslation') + Post = apps.get_model('djangocms_blog', 'Post') + BlogCategory = apps.get_model('djangocms_blog', 'BlogCategory') + GenericBlogPlugin = apps.get_model('djangocms_blog', 'GenericBlogPlugin') + LatestPostsPlugin = apps.get_model('djangocms_blog', 'LatestPostsPlugin') + AuthorEntriesPlugin = apps.get_model('djangocms_blog', 'AuthorEntriesPlugin') + config = None + for page in Page.objects.drafts().filter(application_urls='BlogApp'): + config, created = BlogConfig.objects.get_or_create(namespace=page.application_namespace) + if not BlogConfigTranslation.objects.exists(): + for lang in get_language_list(): + title = page.get_title(lang) + translation = BlogConfigTranslation.objects.create(language_code=lang, master_id=config.pk, app_title=title) + if config: + for model in (Post, BlogCategory, GenericBlogPlugin, LatestPostsPlugin, AuthorEntriesPlugin): + for item in model.objects.filter(app_config__isnull=True): + item.app_config = config + item.save() + + +def backwards(apps, schema_editor): + # No need for backward data migration + pass + +class Migration(migrations.Migration): + + dependencies = [ + ('djangocms_blog', '0013_auto_20160201_2235'), + ] + + operations = [ + migrations.RunPython(forwards, backwards), + ] From 453adeb5bdf4c032fe1a9438ad4d254c04e2d24a Mon Sep 17 00:00:00 2001 From: Tadas Dailyda Date: Mon, 15 Feb 2016 14:55:12 +0200 Subject: [PATCH 11/16] also fix migration 0001 and 0004, be aware that FILER_IMAGE_MODEL is False by default --- djangocms_blog/migrations/0001_initial.py | 8 ++++++-- djangocms_blog/migrations/0004_auto_20150108_1435.py | 10 +++++++--- djangocms_blog/migrations/0007_auto_20150719_0933.py | 6 ++++-- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/djangocms_blog/migrations/0001_initial.py b/djangocms_blog/migrations/0001_initial.py index 3e993ba..e581f14 100644 --- a/djangocms_blog/migrations/0001_initial.py +++ b/djangocms_blog/migrations/0001_initial.py @@ -8,13 +8,17 @@ import filer.fields.image import meta_mixin.models import taggit_autosuggest.managers from django.conf import settings -from django.db import models, migrations +from django.db import migrations, models +from filer.settings import FILER_IMAGE_MODEL + +ACTUAL_FILER_IMAGE_MODEL = FILER_IMAGE_MODEL or 'filer.Image' class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), + migrations.swappable_dependency(ACTUAL_FILER_IMAGE_MODEL), ('cms', '__latest__'), ('taggit', '__latest__'), ('filer', '__latest__'), @@ -90,7 +94,7 @@ class Migration(migrations.Migration): ('author', models.ForeignKey(related_name='djangocms_blog_post_author', verbose_name='Author', blank=True, to=settings.AUTH_USER_MODEL, null=True)), ('categories', models.ManyToManyField(related_name='blog_posts', verbose_name='category', to='djangocms_blog.BlogCategory')), ('content', cms.models.fields.PlaceholderField(slotname='post_content', editable=False, to='cms.Placeholder', null=True)), - ('main_image', filer.fields.image.FilerImageField(related_name='djangocms_blog_post_image', verbose_name='Main image', blank=True, to='filer.Image', null=True)), + ('main_image', filer.fields.image.FilerImageField(related_name='djangocms_blog_post_image', verbose_name='Main image', blank=True, to=ACTUAL_FILER_IMAGE_MODEL, null=True)), ('main_image_full', models.ForeignKey(related_name='djangocms_blog_post_full', verbose_name='Main image full', blank=True, to='cmsplugin_filer_image.ThumbnailOption', null=True)), ('main_image_thumbnail', models.ForeignKey(related_name='djangocms_blog_post_thumbnail', verbose_name='Main image thumbnail', blank=True, to='cmsplugin_filer_image.ThumbnailOption', null=True)), ('tags', taggit_autosuggest.managers.TaggableManager(to='taggit.Tag', through='taggit.TaggedItem', blank=True, help_text='A comma-separated list of tags.', verbose_name='Tags')), diff --git a/djangocms_blog/migrations/0004_auto_20150108_1435.py b/djangocms_blog/migrations/0004_auto_20150108_1435.py index 8bfa4b4..79e727f 100644 --- a/djangocms_blog/migrations/0004_auto_20150108_1435.py +++ b/djangocms_blog/migrations/0004_auto_20150108_1435.py @@ -1,14 +1,18 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import models, migrations -import filer.fields.image import django.db.models.deletion +import filer.fields.image +from django.db import migrations, models +from filer.settings import FILER_IMAGE_MODEL + +ACTUAL_FILER_IMAGE_MODEL = FILER_IMAGE_MODEL or 'filer.Image' class Migration(migrations.Migration): dependencies = [ + migrations.swappable_dependency(ACTUAL_FILER_IMAGE_MODEL), ('djangocms_blog', '0003_auto_20141201_2252'), ] @@ -16,7 +20,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='post', name='main_image', - field=filer.fields.image.FilerImageField(related_name='djangocms_blog_post_image', on_delete=django.db.models.deletion.SET_NULL, verbose_name='Main image', blank=True, to='filer.Image', null=True), + field=filer.fields.image.FilerImageField(related_name='djangocms_blog_post_image', on_delete=django.db.models.deletion.SET_NULL, verbose_name='Main image', blank=True, to=ACTUAL_FILER_IMAGE_MODEL, null=True), preserve_default=True, ), migrations.AlterField( diff --git a/djangocms_blog/migrations/0007_auto_20150719_0933.py b/djangocms_blog/migrations/0007_auto_20150719_0933.py index 2594331..b305433 100644 --- a/djangocms_blog/migrations/0007_auto_20150719_0933.py +++ b/djangocms_blog/migrations/0007_auto_20150719_0933.py @@ -9,12 +9,14 @@ from django.conf import settings from django.db import migrations, models from filer.settings import FILER_IMAGE_MODEL +ACTUAL_FILER_IMAGE_MODEL = FILER_IMAGE_MODEL or 'filer.Image' + class Migration(migrations.Migration): dependencies = [ ('djangocms_blog', '0006_auto_20150214_1907'), - migrations.swappable_dependency(FILER_IMAGE_MODEL), + migrations.swappable_dependency(ACTUAL_FILER_IMAGE_MODEL), ] operations = [ @@ -101,7 +103,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='post', name='main_image', - field=filer.fields.image.FilerImageField(on_delete=django.db.models.deletion.SET_NULL, blank=True, verbose_name='main image', to='filer.Image', related_name='djangocms_blog_post_image', null=True), + field=filer.fields.image.FilerImageField(on_delete=django.db.models.deletion.SET_NULL, blank=True, verbose_name='main image', to=ACTUAL_FILER_IMAGE_MODEL, related_name='djangocms_blog_post_image', null=True), preserve_default=True, ), migrations.AlterField( From dc24c12dee5bf66bf9b6d9bc21911e1f6a078b25 Mon Sep 17 00:00:00 2001 From: Iacopo Spalletti Date: Mon, 15 Feb 2016 19:37:57 +0100 Subject: [PATCH 12/16] Add migrations to .checkignore --- .checkignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.checkignore b/.checkignore index 658579c..8005a6e 100644 --- a/.checkignore +++ b/.checkignore @@ -1,3 +1,4 @@ tests/* docs/* djangocms_blog/south_migrations/* +djangocms_blog/migrations/* From c71fb4e534eb15a9ce6993d1c273819b45699409 Mon Sep 17 00:00:00 2001 From: Iacopo Spalletti Date: Mon, 15 Feb 2016 19:44:32 +0100 Subject: [PATCH 13/16] Update history --- HISTORY.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/HISTORY.rst b/HISTORY.rst index b7f7856..76ae54f 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -10,6 +10,7 @@ History * Fix tests with parler>=1.6 * Use all_languages_column to admin * Add publish button +* Fix issues in migrations. Thanks @skirsdeda 0.6.3 (2015-12-22) ++++++++++++++++++ From 2588f6c020210a5cd1620e558c0c3129372f0d01 Mon Sep 17 00:00:00 2001 From: Iacopo Spalletti Date: Mon, 15 Feb 2016 20:33:17 +0100 Subject: [PATCH 14/16] Fix selecting current menu item according to menu layout --- HISTORY.rst | 1 + djangocms_blog/menu.py | 54 ++++++++++++++++++++++++++++++++++++++++-- tests/test_menu.py | 31 ++++++++++++++++++++++++ 3 files changed, 84 insertions(+), 2 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 76ae54f..6fda271 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -11,6 +11,7 @@ History * Use all_languages_column to admin * Add publish button * Fix issues in migrations. Thanks @skirsdeda +* Fix selecting current menu item according to menu layout 0.6.3 (2015-12-22) ++++++++++++++++++ diff --git a/djangocms_blog/menu.py b/djangocms_blog/menu.py index 8d7920f..22660a8 100644 --- a/djangocms_blog/menu.py +++ b/djangocms_blog/menu.py @@ -1,21 +1,34 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import, print_function, unicode_literals +from cms.apphook_pool import apphook_pool from cms.menu_bases import CMSAttachMenu +from django.core.urlresolvers import resolve from django.db.models.signals import post_delete, post_save from django.utils.translation import get_language_from_request, ugettext_lazy as _ -from menus.base import NavigationNode +from menus.base import Modifier, NavigationNode from menus.menu_pool import menu_pool from .cms_appconfig import BlogConfig from .models import BlogCategory, Post -from .settings import MENU_TYPE_CATEGORIES, MENU_TYPE_COMPLETE, MENU_TYPE_POSTS +from .settings import MENU_TYPE_CATEGORIES, MENU_TYPE_COMPLETE, MENU_TYPE_POSTS, get_setting class BlogCategoryMenu(CMSAttachMenu): + """ + Main menu class + + Handles all types of blog menu + """ name = _('Blog menu') def get_nodes(self, request): + """ + Generates the nodelist + + :param request: + :return: list of nodes + """ nodes = [] language = get_language_from_request(request, check_path=True) @@ -75,7 +88,44 @@ class BlogCategoryMenu(CMSAttachMenu): menu_pool.register_menu(BlogCategoryMenu) +class BlogNavModifier(Modifier): + """ + This navigation modifier makes sure that when + a particular blog post is viewed, + a corresponding category is selected in menu + """ + def modify(self, request, nodes, namespace, root_id, post_cut, breadcrumb): + app = None + config = None + if getattr(request, 'current_page', None) and request.current_page.application_urls: + app = apphook_pool.get_apphook(request.current_page.application_urls) + + if app and app.app_config: + namespace = resolve(request.path).namespace + config = app.get_config(namespace) + if config and config.menu_structure != MENU_TYPE_CATEGORIES: + return nodes + if post_cut: + return nodes + current_post = getattr(request, get_setting('CURRENT_POST_IDENTIFIER'), None) + category = None + if current_post and current_post.__class__ == Post: + category = current_post.categories.first() + if not category: + return nodes + + for node in nodes: + if '%s-%s' % (category.__class__.__name__, category.pk) == node.id: + node.selected = True + return nodes + +menu_pool.register_modifier(BlogNavModifier) + + def clear_menu_cache(**kwargs): + """ + Empty menu cache when saving categories + """ menu_pool.clear(all=True) post_save.connect(clear_menu_cache, sender=BlogCategory) diff --git a/tests/test_menu.py b/tests/test_menu.py index 0f9f046..83980d2 100644 --- a/tests/test_menu.py +++ b/tests/test_menu.py @@ -29,6 +29,7 @@ class MenuTest(BaseTest): self.cats.append(cat) activate('en') + menu_pool.clear(all=True) menu_pool.discover_menus() # All cms menu modifiers should be removed from menu_pool.modifiers # so that they do not interfere with our menu nodes @@ -139,6 +140,10 @@ class MenuTest(BaseTest): (PostDetailView, 'slug', posts[0], posts[0].categories.first()), (CategoryEntriesView, 'category', self.cats[2], self.cats[2]) ) + menu_pool.clear(all=True) + cache.clear() + self.app_config_1.app_data.config.menu_structure = MENU_TYPE_COMPLETE + self.app_config_1.save() for view_cls, kwarg, obj, cat in tests: request = self.get_page_request(pages[1], self.user, path=obj.get_absolute_url()) with smart_override('en'): @@ -149,6 +154,7 @@ class MenuTest(BaseTest): view_obj.app_config = self.app_config_1 view_obj.kwargs = {kwarg: obj.slug} view_obj.get(request) + view_obj.get_context_data() # check if selected menu node points to cat nodes = menu_pool.get_nodes(request) found = False @@ -158,3 +164,28 @@ class MenuTest(BaseTest): found = True break self.assertTrue(found) + + menu_pool.clear(all=True) + cache.clear() + self.app_config_1.app_data.config.menu_structure = MENU_TYPE_CATEGORIES + self.app_config_1.save() + for view_cls, kwarg, obj, cat in tests: + request = self.get_page_request(pages[1], self.user, path=obj.get_absolute_url()) + with smart_override('en'): + with switch_language(obj, 'en'): + view_obj = view_cls() + view_obj.request = request + view_obj.namespace, view_obj.config = get_app_instance(request) + view_obj.app_config = self.app_config_1 + view_obj.kwargs = {kwarg: obj.slug} + view_obj.get(request) + view_obj.get_context_data() + # check if selected menu node points to cat + nodes = menu_pool.get_nodes(request) + found = False + for node in nodes: + if node.selected: + self.assertEqual(node.url, cat.get_absolute_url()) + found = True + break + self.assertTrue(found) From e5a070bc94c45e35c56f507cc3ba6997049591d0 Mon Sep 17 00:00:00 2001 From: Iacopo Spalletti Date: Mon, 15 Feb 2016 20:37:07 +0100 Subject: [PATCH 15/16] Fix string formatting --- djangocms_blog/menu.py | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/djangocms_blog/menu.py b/djangocms_blog/menu.py index 22660a8..6be7aad 100644 --- a/djangocms_blog/menu.py +++ b/djangocms_blog/menu.py @@ -53,9 +53,12 @@ class BlogCategoryMenu(CMSAttachMenu): node = NavigationNode( category.name, category.get_absolute_url(), - '%s-%s' % (category.__class__.__name__, category.pk), - ('%s-%s' % (category.__class__.__name__, category.parent.id) if category.parent - else None) + '{0}-{1}'.format(category.__class__.__name__, category.pk), + ( + '{0}-{1}'.format( + category.__class__.__name__, category.parent.id + ) if category.parent else None + ) ) nodes.append(node) @@ -70,10 +73,10 @@ class BlogCategoryMenu(CMSAttachMenu): if categories_menu: category = post.categories.first() if category: - parent = '%s-%s' % (category.__class__.__name__, category.pk) - post_id = '%s-%s' % (post.__class__.__name__, post.pk), + parent = '{0}-{1}'.format(category.__class__.__name__, category.pk) + post_id = '{0}-{1}'.format(post.__class__.__name__, post.pk), else: - post_id = '%s-%s' % (post.__class__.__name__, post.pk), + post_id = '{0}-{1}'.format(post.__class__.__name__, post.pk), if post_id: node = NavigationNode( post.get_title(), @@ -95,6 +98,16 @@ class BlogNavModifier(Modifier): a corresponding category is selected in menu """ def modify(self, request, nodes, namespace, root_id, post_cut, breadcrumb): + """ + Actual modifier function + :param request: request + :param nodes: complete list of nodes + :param namespace: Menu namespace + :param root_id: eventual root_id + :param post_cut: flag for modifier stage + :param breadcrumb: flag for modifier stage + :return: nodeslist + """ app = None config = None if getattr(request, 'current_page', None) and request.current_page.application_urls: @@ -115,7 +128,7 @@ class BlogNavModifier(Modifier): return nodes for node in nodes: - if '%s-%s' % (category.__class__.__name__, category.pk) == node.id: + if '{0}-{1}'.format(category.__class__.__name__, category.pk) == node.id: node.selected = True return nodes From f79b6471c436132f6ac118257445f7c64128b824 Mon Sep 17 00:00:00 2001 From: Iacopo Spalletti Date: Mon, 15 Feb 2016 23:16:34 +0100 Subject: [PATCH 16/16] Fix test --- tests/test_menu.py | 35 +++++++++++++++++++---------------- tests/test_models.py | 2 +- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/tests/test_menu.py b/tests/test_menu.py index 83980d2..8247368 100644 --- a/tests/test_menu.py +++ b/tests/test_menu.py @@ -140,14 +140,16 @@ class MenuTest(BaseTest): (PostDetailView, 'slug', posts[0], posts[0].categories.first()), (CategoryEntriesView, 'category', self.cats[2], self.cats[2]) ) - menu_pool.clear(all=True) - cache.clear() self.app_config_1.app_data.config.menu_structure = MENU_TYPE_COMPLETE self.app_config_1.save() for view_cls, kwarg, obj, cat in tests: - request = self.get_page_request(pages[1], self.user, path=obj.get_absolute_url()) with smart_override('en'): with switch_language(obj, 'en'): + request = self.get_page_request( + pages[1], self.user, path=obj.get_absolute_url() + ) + cache.clear() + menu_pool.clear(all=True) view_obj = view_cls() view_obj.request = request view_obj.namespace, view_obj.config = get_app_instance(request) @@ -157,22 +159,22 @@ class MenuTest(BaseTest): view_obj.get_context_data() # check if selected menu node points to cat nodes = menu_pool.get_nodes(request) - found = False + found = [] for node in nodes: if node.selected: - self.assertEqual(node.url, obj.get_absolute_url()) - found = True - break - self.assertTrue(found) + found.append(node.get_absolute_url()) + self.assertTrue(obj.get_absolute_url() in found) - menu_pool.clear(all=True) - cache.clear() self.app_config_1.app_data.config.menu_structure = MENU_TYPE_CATEGORIES self.app_config_1.save() for view_cls, kwarg, obj, cat in tests: - request = self.get_page_request(pages[1], self.user, path=obj.get_absolute_url()) with smart_override('en'): with switch_language(obj, 'en'): + request = self.get_page_request( + pages[1], self.user, path=obj.get_absolute_url() + ) + cache.clear() + menu_pool.clear(all=True) view_obj = view_cls() view_obj.request = request view_obj.namespace, view_obj.config = get_app_instance(request) @@ -182,10 +184,11 @@ class MenuTest(BaseTest): view_obj.get_context_data() # check if selected menu node points to cat nodes = menu_pool.get_nodes(request) - found = False + found = [] for node in nodes: if node.selected: - self.assertEqual(node.url, cat.get_absolute_url()) - found = True - break - self.assertTrue(found) + found.append(node.get_absolute_url()) + self.assertTrue(cat.get_absolute_url() in found) + + self.app_config_1.app_data.config.menu_structure = MENU_TYPE_COMPLETE + self.app_config_1.save() diff --git a/tests/test_models.py b/tests/test_models.py index ab8977d..689e2ae 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -91,7 +91,7 @@ class AdminTest(BaseTest): for fieldname in BlogConfigForm.base_fields: self.assertContains(response, 'id="id_config-%s"' % fieldname) self.assertContains(response, '') - self.assertContains(response, '') + self.assertContains(response, 'sample_app') def test_admin_category_views(self): post_admin = admin.site._registry[BlogCategory]