Merge pull request #153 from nephila/feature/auto_setup

Auto setup
This commit is contained in:
Iacopo Spalletti 2015-10-18 12:30:01 +02:00
commit ec319bd286
6 changed files with 301 additions and 96 deletions

View file

@ -52,6 +52,19 @@ Supported django CMS versions:
on South anymore; please install it separately if using this
application on Django 1.6.
Features
--------
* Placeholder content editing
* Frontend editing using django CMS 3.0 frontend editor
* Multilingual support using django-parler
* Support for Twitter cards, Open Graph and Google+ snippets meta tags
* Optional support for simpler TextField-based content editing
* Multisite support (posts can be visible in one or more Django sites on the
same project)
* Per-Apphook configuration
* Per-Apphook templates set
* Auto Apphook setup
Quickstart
----------
@ -91,6 +104,7 @@ Please, refer to each application documentation on details.
* django-filer: http://django-filer.readthedocs.org
* django-meta: https://github.com/nephila/django-meta#installation
* django-meta-mixin: https://github.com/nephila/django-meta-mixin#installation
* django-parler: http://django-parler.readthedocs.org/en/latest/quickstart.html#configuration
* django-taggit-autosuggest: https://bitbucket.org/fabian/django-taggit-autosuggest
@ -115,13 +129,14 @@ suited for your deployment.
META_SITE_PROTOCOL = 'http'
META_USE_SITES = True
* If you are using Django 1.7+, be aware than ``filer`` < 0.9.10, ``cmsplugin_filer``
and ``django-cms`` < 3.1 currently requires you to setup ``MIGRATION_MODULES`` in settings::
* If you are using Django 1.7+, be aware than ``filer`` < 0.9.10,
``cmsplugin_filer`` and ``django-cms`` < 3.1 currently requires you to
setup ``MIGRATION_MODULES`` in settings::
MIGRATION_MODULES = {
'cms': 'cms.migrations_django', # only for django CMS 3.0
'menus': 'menus.migrations_django', # only for django CMS 3.0
'filer': 'filer.migrations_django', # only for django filer 0.9.9 and below
'filer': 'filer.migrations_django', # only for django filer up to 0.9.9
'cmsplugin_filer_image': 'cmsplugin_filer_image.migrations_django',
}
@ -145,10 +160,12 @@ suited for your deployment.
url(r'^taggit_autosuggest/', include('taggit_autosuggest.urls')),
* To start your blog you need to use `AppHooks from django CMS <http://django-cms.readthedocs.org/en/support-3.0.x/how_to/apphooks.html>`_
to add the blog to a django CMS page:
to add the blog to a django CMS page; this step is not required when using
**Auto setup** (see below):
* Create a new django CMS page
* Go to Advanced settings and select Blog from the Application selector;
* 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.
@ -160,7 +177,8 @@ suited for your deployment.
* Create a new blog entry in django admin backend or from the toolbar
* Click on "view on site" button to view the post detail page
* Edit the post via djangocms frontend by adding / editing plugins
* Publish the blog post by flagging the "Publish" switch in the blog post admin
* Publish the blog post by flagging the "Publish" switch in the blog post
admin
Configurable permalinks
+++++++++++++++++++++++
@ -172,9 +190,10 @@ Blog comes with four different styles of permalinks styles:
* Category: ``CATEGORY/SLUG``
* Just slug: ``SLUG``
As all the styles are loaded in the urlconf, the latter two does not allow to have CMS pages
beneath the page the blog is attached to. If you want to do this, you have to override the default
urlconfs by setting somethik like the following in the project settings::
As all the styles are loaded in the urlconf, the latter two does not allow
to have CMS pages beneath the page the blog is attached to. If you want to
do this, you have to override the default urlconfs by setting something
like the following in the project settings::
BLOG_PERMALINK_URLS = {
'full_date': r'^(?P<year>\d{4})/(?P<month>\d{1,2})/(?P<day>\d{1,2})/(?P<slug>\w[-\w]*)/$',
@ -188,22 +207,135 @@ And change ``post/`` with the desired prefix.
Templates
+++++++++
To ease the template customisations a ``djangocms_blog/base.html`` template is used by all the blog templates;
the templates itself extends a ``base.html`` template; content is pulled in the ``content`` block.
If you need to define a different base template, or if your base template does not defines a ``content`` block,
copy in your template directory ``djangocms_blog/base.html`` and customise it according to your
needs; the other application templates will use the newly created template and will ignore the bundled one.
To ease the template customisations a ``djangocms_blog/base.html`` template is
used by all the blog templates; the templates itself extends a ``base.html``
template; content is pulled in the ``content`` block.
If you need to define a different base template, or if your base template does
not defines a ``content`` block, copy in your template directory
``djangocms_blog/base.html`` and customise it according to your needs; the
other application templates will use the newly created base template and
will ignore the bundled one.
Features
--------
Templates set
+++++++++++++
By using **Apphook configuration** you can define a different templates set.
To use this feature provide a directory name in **Template prefix** field in
the **Apphook configuration** admin (in *Layout* section): it will be the
root of your custom templates set.
Auto setup
++++++++++
``djangocms_blog`` can install and configue itself if it does not find any
attached instance of itself.
This feature is enable by default and will create:
* a ``BlogConfig`` with default values
* a ``Blog`` CMS page and will attach ``djangocms_blog`` instance to it
* a **home page** is no, home is found.
All the items will be created in every language configured for the website
and the pages will be published. If not using **aldryn-apphook-reload** or
**django CMS 3.2** auto-reload middleware you are required to reload the
project instance after this.
This will only work for the current website as detected by
``Site.objects.get_current()``.
The auto setup is execute once for each server start but it will skip any
action if a ``BlogConfig`` instance is found.
Global Settings
---------------
* BLOG_IMAGE_THUMBNAIL_SIZE: Size of the main image when shown on the post
lists; it's a dictionary with ``size``, ``crop`` and ``upscale`` keys;
(default: ``{'size': '120x120', 'crop': True,'upscale': False}``)
* BLOG_IMAGE_FULL_SIZE: Size of the main image when shown on the post
detail; it's a dictionary with ``size``, ``crop`` and ``upscale`` keys;
(default: ``{'size': '640x120', 'crop': True,'upscale': False}``)
* BLOG_PAGINATION: Number of post per page; (default: ``10``)
* BLOG_LATEST_POSTS: Default number of post in the **Latest post** plugin;
(default: ``5'')
* BLOG_POSTS_LIST_TRUNCWORDS_COUNT: Default number of words shown for
abstract in the post list; (default: ``100``)
* BLOG_TYPE: Generic type for the post object; (default: ``Article``)
* BLOG_TYPES: Choices of available blog types;
(default: to ``META_OBJECT_TYPES`` defined in `django-meta-mixin settings`_)
* BLOG_FB_TYPE: Open Graph type for the post object; (default: ``Article``)
* BLOG_FB_TYPES: Choices of available blog types;
(default: to ``META_FB_TYPES`` defined in `django-meta-mixin settings`_)
* BLOG_FB_APPID: Facebook Application ID
* BLOG_FB_PROFILE_ID: Facebook profile ID of the post author
* BLOG_FB_PUBLISHER: Facebook URL of the blog publisher
* BLOG_FB_AUTHOR_URL: Facebook profile URL of the post author
* BLOG_FB_AUTHOR: Facebook profile URL of the post author
* BLOG_TWITTER_TYPE: Twitter Card type for the post object;
(default: ``Summary``)
* BLOG_TWITTER_TYPES: Choices of available blog types for twitter;
(default: to ``META_TWITTER_TYPES`` defined in `django-meta-mixin settings`_)
* BLOG_TWITTER_SITE: Twitter account of the site
* BLOG_TWITTER_AUTHOR: Twitter account of the post author
* BLOG_GPLUS_TYPE: Google+ Snippet type for the post object;
(default: ``Blog``)
* BLOG_GPLUS_TYPES: Choices of available blog types for twitter;
(default: to ``META_GPLUS_TYPES`` defined in `django-meta-mixin settings`_)
* BLOG_GPLUS_AUTHOR: Google+ account of the post author
* BLOG_ENABLE_COMMENTS: Whether to enable comments by default on posts;
while ``djangocms_blog`` does not ship any comment system, this flag
can be used to control the chosen comments framework; (default: ``True``)
* BLOG_USE_ABSTRACT: Use an abstract field for the post; if ``False``
no abstract field is available for every post; (default: ``True``)
* BLOG_USE_PLACEHOLDER: Post content is managed via placeholder;
if ``False`` a simple HTMLField is used; (default: ``True``)
* BLOG_MULTISITE: Add support for multisite setup; (default: ``True``)
* BLOG_MENU_TYPE: Structure of the Blog menu;
(default: ``Posts and Categories``)
* BLOG_AUTHOR_DEFAULT: Use a default if not specified; if set to ``True`` the
current user is set as the default author, if set to ``False`` no default
author is set, if set to a string the user with the provided username is
used; (default: ``True``)
* BLOG_DEFAULT_PUBLISHED: If posts are marked as published by default;
(default: ``False``)
* BLOG_AVAILABLE_PERMALINK_STYLES: Choices of permalinks styles;
* BLOG_PERMALINK_URLS: URLConf corresponding to
BLOG_AVAILABLE_PERMALINK_STYLES;
* BLOG_AUTO_SETUP: Enable the blog **Auto setup** feature; (default: ``True``)
* BLOG_AUTO_HOME_TITLE: Title of the home page created by **Auto setup**;
(default: ``Home``)
* BLOG_AUTO_BLOG_TITLE: Title of the blog page created by **Auto setup**;
(default: ``Blog``)
* BLOG_AUTO_APP_TITLE: Title of the ``BlogConfig`` instance created by
**Auto setup**; (default: ``Blog``)
Per-Apphook settings
--------------------
* application title: Free text title that can be used as title in templates;
* Post published by default: Per-Apphook setting for BLOG_DEFAULT_PUBLISHED;
* Permalink structure: Per-Apphook setting for
BLOG_AVAILABLE_PERMALINK_STYLES;
* Use placeholder and plugins for article body: Per-Apphook setting for
BLOG_USE_PLACEHOLDER;
* Use abstract field: Per-Apphook setting for BLOG_USE_ABSTRACT;
* Set author: Per-Apphook setting for BLOG_AUTHOR_DEFAULT;
* Paginate sizePer-Apphook setting for BLOG_PAGINATION;
* Template prefix: Alternative directory to load the blog templates from;
* Menu structure: Per-Apphook setting for BLOG_MENU_TYPE
* Object type: Per-Apphook setting for BLOG_TYPE
* Facebook type: Per-Apphook setting for BLOG_FB_TYPE
* Facebook application ID: Per-Apphook setting for BLOG_FB_APP_ID
* Facebook profile ID: Per-Apphook setting for BLOG_FB_PROFILE_ID
* Facebook page URL: Per-Apphook setting for BLOG_FB_PUBLISHER
* Facebook author URL: Per-Apphook setting for BLOG_AUTHOR_URL
* Facebook author: Per-Apphook setting for BLOG_AUTHOR
* Twitter type: Per-Apphook setting for BLOG_TWITTER_TYPE
* Twitter site handle: Per-Apphook setting for BLOG_TWITTER_SITE
* Twitter author handle: Per-Apphook setting for BLOG_TWITTER_AUTHOR
* Google+ type: Per-Apphook setting for BLOG_GPLUS_TYPE
* Google+ author name: Per-Apphook setting for BLOG_GPLUS_AUTHOR
* Placeholder content editing
* Frontend editing using django CMS 3.0 frontend editor
* Multilingual support using django-parler
* Support for Twitter cards, Open Graph and Google+ snippets meta tags
* Optional support for simpler TextField-based content editing
* Multisite support (posts can be visible in one or more Django sites on the same project)
* Per-apphook configuration
Import from Wordpress
+++++++++++++++++++++
@ -212,77 +344,11 @@ If you want to import content from existing wordpress blog, check
https://pypi.python.org/pypi/the-real-django-wordpress and
this gist https://gist.github.com/yakky/11336204 as a base.
Global Settings
---------------
* BLOG_IMAGE_THUMBNAIL_SIZE: Size of the main image when shown on the post lists;
it's a dictionary with ``size``, ``crop`` and ``upscale`` keys;
(default: ``{'size': '120x120', 'crop': True,'upscale': False}``)
* BLOG_IMAGE_FULL_SIZE: Size of the main image when shown on the post detail;
it's a dictionary with ``size``, ``crop`` and ``upscale`` keys;
(default: ``{'size': '640x120', 'crop': True,'upscale': False}``)
* BLOG_PAGINATION: Number of post per page; (default: 10)
* BLOG_LATEST_POSTS: Default number of post in the **Latest post** plugin; (default: 5)
* BLOG_POSTS_LIST_TRUNCWORDS_COUNT: Default number of words shown for abstract in the post list; (default: 100)
* BLOG_TYPE: Generic type for the post object; (default: Article)
* BLOG_TYPES: Choices of available blog types; (default: Article, Website)
* BLOG_FB_TYPE: Open Graph type for the post object; (default: Article)
* BLOG_FB_TYPES: Choices of available blog types; (default: Article, Website)
* BLOG_FB_APPID: Facebook Application ID
* BLOG_FB_PROFILE_ID: Facebook profile ID of the post author
* BLOG_FB_PUBLISHER: Facebook URL of the blog publisher
* BLOG_FB_AUTHOR_URL: Facebook profile URL of the post author
* BLOG_FB_AUTHOR: Facebook profile URL of the post author
* BLOG_TWITTER_TYPE: Twitter Card type for the post object; (default: Summary)
* BLOG_TWITTER_TYPES: Choices of available blog types for twitter; (default: Article, Website)
* BLOG_TWITTER_SITE: Twitter account of the site
* BLOG_TWITTER_AUTHOR: Twitter account of the post author
* BLOG_GPLUS_TYPE: Google+ Snippet type for the post object; (default: Blog)
* BLOG_GPLUS_TYPES: Choices of available blog types for twitter; (default: Article, Website)
* BLOG_GPLUS_AUTHOR: Google+ account of the post author
* BLOG_ENABLE_COMMENTS: Whether to enable comments by default on posts;
while ``djangocms_blog`` does not ship any comment system, this flag can be used
to control the chosen comments framework; (default: True)
* BLOG_USE_ABSTRACT: Use an abstract field for the post; if ``False`` no abstract field
is available for every post; (default: True)
* BLOG_USE_PLACEHOLDER: Post content is managed via placeholder; if ``False`` a
simple HTMLField is used; (default: True)
* BLOG_MULTISITE: Add support for multisite setup
* BLOG_MENU_TYPE: Structure of the Blog menu; (default: Posts and Categories)
* BLOG_AUTHOR_DEFAULT: Use a default if not specified; if set to ``True`` the
current user is set as the default author, if set to ``False`` no default
author is set, if set to a string the user with the provided username is
used; (default: True)
* BLOG_DEFAULT_PUBLISHED: If posts are marked as published by default; (default: False)
* BLOG_AVAILABLE_PERMALINK_STYLES: Choices of permalinks styles;
* BLOG_PERMALINK_URLS: URLConf corresponding to BLOG_AVAILABLE_PERMALINK_STYLES;
Per-Apphook settings
--------------------
* default_published: Per-apphook setting for BLOG_DEFAULT_PUBLISHED;
* Permalink structure: Per-apphook setting for BLOG_AVAILABLE_PERMALINK_STYLES;
* Use placeholder and plugins for article body: Per-apphook setting for BLOG_USE_PLACEHOLDER;
* Use abstract field: Per-apphook setting for BLOG_USE_ABSTRACT;
* Set author: Per-apphook setting for BLOG_AUTHOR_DEFAULT;
* Paginate sizePer-apphook setting for BLOG_PAGINATION;
* Template prefix: Alternative directory to load the blog templates from;
* Menu structure: Per-apphook setting for BLOG_MENU_TYPE
* Object type:Per-apphook setting for BLOG_TYPE
* Facebook type: Per-apphook setting for BLOG_FB_TYPE
* Facebook application ID: Per-apphook setting for BLOG_FB_APP_ID
* Facebook profile ID: Per-apphook setting for BLOG_FB_PROFILE_ID
* Facebook page URL: Per-apphook setting for BLOG_FB_PUBLISHER
* Facebook author URL: Per-apphook setting for BLOG_AUTHOR_URL
* Facebook author: Per-apphook setting for BLOG_AUTHOR
* Twitter type: Per-apphook setting for BLOG_TWITTER_TYPE
* Twitter site handle: Per-apphook setting for BLOG_TWITTER_SITE
* Twitter author handle: Per-apphook setting for BLOG_TWITTER_AUTHOR
* Google+ type: Per-apphook setting for BLOG_GPLUS_TYPE
* Google+ author name: Per-apphook setting for BLOG_GPLUS_AUTHOR
Known djangocms-blog websites
+++++++++++++++++++++++++++++
* http://nephila.co.uk/blog
* https://blog.ungleich.ch/
.. _django-meta-mixin settings: https://github.com/nephila/django-meta-mixin#settings

View file

@ -2,3 +2,5 @@
__author__ = 'Iacopo Spalletti'
__email__ = 'i.spalletti@nephila.it'
__version__ = '0.6.0.dev1'
default_app_config = 'djangocms_blog.apps.BlogAppConfig'

View file

@ -1,10 +1,65 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import, print_function, unicode_literals
from django.apps import AppConfig
from django.utils.translation import ugettext_lazy as _
try:
from django.apps import AppConfig
except ImportError:
class AppConfig(object):
pass
class PageMetaConfig(AppConfig):
name = 'djangocms_page_meta'
verbose_name = _('django CMS Page Meta')
class BlogAppConfig(AppConfig):
name = 'djangocms_blog'
verbose_name = _('django CMS Blog')
@staticmethod
def setup():
from cms.api import create_page, create_title
from cms.exceptions import NoHomeFound
from cms.models import Page
from cms.utils import get_language_list
from cms.utils.conf import get_templates
from django.utils.translation import override
from .cms_appconfig import BlogConfig
from .settings import get_setting
if get_setting('AUTO_SETUP'):
configs = BlogConfig.objects.all()
if not configs.exists():
config = BlogConfig.objects.create(namespace='Blog')
langs = get_language_list()
blog = None
for lang in langs:
with override(lang):
config.set_current_language(lang)
config.app_title = get_setting('AUTO_APP_TITLE')
config.save()
default_template = get_templates()[0][0]
try:
home = Page.objects.get_home()
except NoHomeFound:
home = None
if not home:
home = create_page(
get_setting('AUTO_HOME_TITLE'), language=lang,
template=default_template, in_navigation=True, published=True
)
elif lang not in home.get_languages():
create_title(
language=lang, title=get_setting('AUTO_HOME_TITLE'), page=home
)
home.publish(lang)
if not blog:
blog = create_page(
get_setting('AUTO_BLOG_TITLE'), language=lang, apphook='BlogApp',
apphook_namespace=config.namespace, parent=home,
template=default_template, in_navigation=True, published=True
)
else:
create_title(
language=lang, title=get_setting('AUTO_BLOG_TITLE'), page=blog
)
blog.publish(lang)

View file

@ -74,5 +74,10 @@ def get_setting(name):
'BLOG_DEFAULT_PUBLISHED': getattr(settings, 'BLOG_DEFAULT_PUBLISHED', False),
'BLOG_AVAILABLE_PERMALINK_STYLES': getattr(settings, 'BLOG_AVAILABLE_PERMALINK_STYLES', PERMALINKS), # NOQA
'BLOG_PERMALINK_URLS': getattr(settings, 'BLOG_PERMALINK_URLS', PERMALINKS_URLS),
'BLOG_AUTO_SETUP': getattr(settings, 'BLOG_AUTO_SETUP', True),
'BLOG_AUTO_HOME_TITLE': getattr(settings, 'BLOG_AUTO_HOME_TITLE', 'Home'),
'BLOG_AUTO_BLOG_TITLE': getattr(settings, 'BLOG_AUTO_BLOG_TITLE', 'Blog'),
'BLOG_AUTO_APP_TITLE': getattr(settings, 'BLOG_AUTO_APP_TITLE', 'Blog'),
}
return default['BLOG_%s' % name]

View file

@ -3,6 +3,7 @@ from __future__ import absolute_import, print_function, unicode_literals
from django.conf.urls import patterns, url
from .apps import BlogAppConfig
from .feeds import LatestEntriesFeed, TagFeed
from .settings import get_setting
from .views import (
@ -42,3 +43,5 @@ urlpatterns = patterns(
url(r'^tag/(?P<tag>[-\w]+)/feed/$',
TagFeed(), name='posts-tagged-feed'),
]
BlogAppConfig.setup()

74
tests/test_setup.py Normal file
View file

@ -0,0 +1,74 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import, print_function, unicode_literals
import sys
from cms.api import create_page, create_title
from cms.models import Page
from cms.utils import get_language_list
from django.utils.translation import override
from djangocms_blog.cms_appconfig import BlogConfig
from . import BaseTest
class SetupTest(BaseTest):
@classmethod
def setUpClass(cls):
super(BaseTest, cls).setUpClass()
def test_setup_from_url(self):
# Tests starts with no page and no config
self.assertFalse(Page.objects.exists())
self.assertFalse(BlogConfig.objects.exists())
# importing urls triggers the auto setup
from djangocms_blog import urls # NOQA
# Home and blog, published and draft
self.assertEqual(Page.objects.count(), 4)
self.assertEqual(BlogConfig.objects.count(), 1)
def setUp(self):
self.reload_urlconf()
delete = [
'djangocms_blog',
'djangocms_blog.urls',
]
for module in delete:
del sys.modules[module]
def test_setup_filled(self):
# Tests starts with no page and no config
self.assertFalse(Page.objects.exists())
self.assertFalse(BlogConfig.objects.exists())
langs = get_language_list()
home = None
for lang in langs:
with override(lang):
if not home:
home = create_page(
'a new home', language=lang,
template='page.html', in_navigation=True, published=True
)
else:
create_title(
language=lang, title='a new home', page=home
)
home.publish(lang)
# importing urls triggers the auto setup
from djangocms_blog import urls # NOQA
# Home and blog, published and draft
self.assertEqual(Page.objects.count(), 4)
self.assertEqual(BlogConfig.objects.count(), 1)
home = Page.objects.get_home()
for lang in langs:
self.assertEqual(home.get_title(lang), 'a new home')