diff --git a/.travis.yml b/.travis.yml index c4c516f..44158b3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,7 @@ language: python sudo: false python: + - 3.5 - 3.4 - 3.3 - 2.7 @@ -12,16 +13,16 @@ python: env: matrix: - - DJANGO='django16' CMS='cms30' - - DJANGO='django16' CMS='cms31' - - DJANGO='django16' CMS='cms32' - - DJANGO='django17' CMS='cms30' - - DJANGO='django17' CMS='cms31' - - DJANGO='django17' CMS='cms32' - - DJANGO='django18' CMS='cms31' - - DJANGO='django18' CMS='cms32' - TOXENV='pep8' - TOXENV='isort' + - DJANGO='django18' CMS='cms32' + - DJANGO='django18' CMS='cms31' + - DJANGO='django17' CMS='cms32' + - DJANGO='django17' CMS='cms31' + - DJANGO='django17' CMS='cms30' + - DJANGO='django16' CMS='cms32' + - DJANGO='django16' CMS='cms31' + - DJANGO='django16' CMS='cms30' # command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors @@ -31,12 +32,17 @@ install: - "if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then export PYVER=py27; fi" - "if [[ $TRAVIS_PYTHON_VERSION == '3.3' ]]; then export PYVER=py33; fi" - "if [[ $TRAVIS_PYTHON_VERSION == '3.4' ]]; then export PYVER=py34; fi" + - "if [[ $TRAVIS_PYTHON_VERSION == '3.5' ]]; then export PYVER=py35; fi" - "if [[ ${DJANGO}z != 'z' ]]; then export TOXENV=$PYVER-$DJANGO-$CMS; fi" # command to run tests, e.g. python setup.py test script: COMMAND='coverage run' tox -e$TOXENV -after_success: coveralls +before_install: + - pip install codecov +after_success: + - codecov + - coveralls matrix: exclude: @@ -52,6 +58,10 @@ matrix: env: TOXENV='pep8' - python: 3.3 env: TOXENV='isort' + - python: 3.4 + env: TOXENV='pep8' + - python: 3.4 + env: TOXENV='isort' - python: 2.6 env: DJANGO='django17' CMS='cms30' - python: 2.6 @@ -62,6 +72,18 @@ matrix: env: DJANGO='django18' CMS='cms31' - python: 2.6 env: DJANGO='django18' CMS='cms32' + - python: 3.5 + env: DJANGO='django16' CMS='cms30' + - python: 3.5 + env: DJANGO='django16' CMS='cms31' + - python: 3.5 + env: DJANGO='django16' CMS='cms32' + - python: 3.5 + env: DJANGO='django17' CMS='cms30' + - python: 3.5 + env: DJANGO='django17' CMS='cms31' + - python: 3.5 + env: DJANGO='django17' CMS='cms32' allow_failures: - python: 2.6 @@ -84,3 +106,5 @@ matrix: env: DJANGO='django17' CMS='cms32' - python: 3.4 env: DJANGO='django18' CMS='cms32' + - python: 3.5 + env: DJANGO='django18' CMS='cms32' diff --git a/README.rst b/README.rst index 42db8ee..2fe56f6 100644 --- a/README.rst +++ b/README.rst @@ -2,28 +2,34 @@ djangocms-blog ============== +.. image:: https://img.shields.io/pypi/v/djangocms-blog.svg?style=flat-square + :target: https://pypi.python.org/pypi/djangocms-blog + :alt: Latest PyPI version -.. image:: https://img.shields.io/pypi/v/djangocms-blog.svg - :target: https://pypi.python.org/pypi/djangocms-blog - :alt: Latest PyPI version +.. image:: https://img.shields.io/pypi/dm/djangocms-blog.svg?style=flat-square + :target: https://pypi.python.org/pypi/djangocms-blog + :alt: Monthly downloads -.. image:: https://img.shields.io/travis/nephila/djangocms-blog.svg - :target: https://travis-ci.org/nephila/djangocms-blog - :alt: Latest Travis CI build status +.. image:: https://img.shields.io/pypi/pyversions/djangocms-blog.svg?style=flat-square + :target: https://pypi.python.org/pypi/djangocms-blog + :alt: Python versions -.. image:: https://img.shields.io/pypi/dm/djangocms-blog.svg - :target: https://pypi.python.org/pypi/djangocms-blog - :alt: Monthly downloads +.. image:: https://img.shields.io/travis/nephila/djangocms-blog.svg?style=flat-square + :target: https://travis-ci.org/nephila/djangocms-blog + :alt: Latest Travis CI build status -.. image:: https://coveralls.io/repos/nephila/djangocms-blog/badge.png - :target: https://coveralls.io/r/nephila/djangocms-blog - :alt: Test coverage +.. image:: https://img.shields.io/coveralls/nephila/djangocms-blog/master.svg?style=flat-square + :target: https://coveralls.io/r/nephila/djangocms-blog?branch=master + :alt: Test coverage -.. image:: https://codeclimate.com/github/nephila/djangocms-blog/badges/gpa.svg +.. image:: https://img.shields.io/codecov/c/github/nephila/djangocms-blog/master.svg?style=flat-square + :target: https://codecov.io/github/nephila/djangocms-blog + :alt: Test coverage + +.. image:: https://codeclimate.com/github/nephila/djangocms-blog/badges/gpa.svg?style=flat-square :target: https://codeclimate.com/github/nephila/djangocms-blog :alt: Code Climate - A djangoCMS 3 blog application. Supported Django versions: @@ -46,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 ---------- @@ -85,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 @@ -94,7 +114,7 @@ Quick hint The following are minimal defaults to get the blog running; they may not be suited for your deployment. -* Add the following settings to your project:: +* Add the following settings to your project:: SOUTH_MIGRATION_MODULES = { 'easy_thumbnails': 'easy_thumbnails.south_migrations', @@ -108,17 +128,18 @@ 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', } - + Please check `django CMS installation `_, `cmsplugin-filer README `_ @@ -138,11 +159,13 @@ suited for your deployment. url(r'^taggit_autosuggest/', include('taggit_autosuggest.urls')), -* To start your blog you need to use `AppHooks from django CMS `_ - to add the blog to a django CMS page: - +* To start your blog you need to use `AppHooks from django CMS `_ + 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. @@ -150,30 +173,169 @@ suited for your deployment. * 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: - + * 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 ++++++++++++++++++++++++ + +Blog comes with four different styles of permalinks styles: + +* Full date: ``YYYY/MM/DD/SLUG`` +* Year / Month: ``YYYY/MM/SLUG`` +* 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 something +like the following in the project settings:: + + BLOG_PERMALINK_URLS = { + 'full_date': r'^(?P\d{4})/(?P\d{1,2})/(?P\d{1,2})/(?P\w[-\w]*)/$', + 'short_date': r'^(?P\d{4})/(?P\d{1,2})/(?P\w[-\w]*)/$', + 'category': r'^post/(?P\w[-\w]*)/(?P\w[-\w]*)/$', + 'slug': r'^post/(?P\w[-\w]*)/$', + } + +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) Import from Wordpress +++++++++++++++++++++ @@ -182,49 +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. - -Settings --------- -* 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_PLACEHOLDER: Post content is managed via placeholder; if ``False`` a - simple HTMLField is used; (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_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_MULTISITE: Add support for multisite setup -* 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) - -Social media tags settings -++++++++++++++++++++++++++ -* BLOG_TYPE: Generic type for the post object; (default: Article) -* BLOG_FB_TYPE: Open Graph type for the post object; (default: Article) -* 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_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_AUTHOR: Google+ account of the post 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 diff --git a/cms_helper.py b/cms_helper.py index ef6b154..667833d 100755 --- a/cms_helper.py +++ b/cms_helper.py @@ -18,6 +18,8 @@ HELPER_SETTINGS = dict( 'cmsplugin_filer_image', 'taggit', 'taggit_autosuggest', + 'aldryn_apphooks_config', + 'tests.test_utils', ], LANGUAGE_CODE='en', LANGUAGES=( @@ -71,6 +73,9 @@ HELPER_SETTINGS = dict( MIGRATION_MODULES={ 'cmsplugin_filer_image': 'cmsplugin_filer_image.migrations_django', }, + CMS_TEMPLATES=( + ('blog.html', 'Blog template'), + ), META_SITE_PROTOCOL='http', META_SITE_DOMAIN='example.com', META_USE_OG_PROPERTIES=True, diff --git a/djangocms_blog/__init__.py b/djangocms_blog/__init__.py index 335edb1..74b54bb 100644 --- a/djangocms_blog/__init__.py +++ b/djangocms_blog/__init__.py @@ -1,5 +1,6 @@ -#!/usr/bin/env python # -*- coding: utf-8 -*- __author__ = 'Iacopo Spalletti' __email__ = 'i.spalletti@nephila.it' -__version__ = '0.5.1.dev1' +__version__ = '0.6.0.dev1' + +default_app_config = 'djangocms_blog.apps.BlogAppConfig' diff --git a/djangocms_blog/admin.py b/djangocms_blog/admin.py index 70189fe..05380a6 100755 --- a/djangocms_blog/admin.py +++ b/djangocms_blog/admin.py @@ -3,13 +3,17 @@ from __future__ import absolute_import, print_function, unicode_literals from copy import deepcopy +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.contrib import admin from django.contrib.auth import get_user_model +from django.utils.translation import ugettext_lazy as _ from parler.admin import TranslatableAdmin +from .cms_appconfig import BlogConfig +from .forms import PostAdminForm from .models import BlogCategory, Post from .settings import get_setting @@ -20,27 +24,33 @@ except ImportError: pass -class BlogCategoryAdmin(EnhancedModelAdminMixin, TranslatableAdmin): +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': + return {} return {'slug': ('name',)} class Media: css = { - 'all': ('%sdjangocms_blog/css/%s' % (settings.STATIC_URL, - 'djangocms_blog_admin.css'),) + 'all': ('%sdjangocms_blog/css/%s' % (settings.STATIC_URL, 'djangocms_blog_admin.css'),) } -class PostAdmin(EnhancedModelAdminMixin, FrontendEditableAdminMixin, - PlaceholderAdminMixin, TranslatableAdmin): - list_display = ['title', 'author', 'date_published', 'date_published_end'] +class PostAdmin(PlaceholderAdminMixin, FrontendEditableAdminMixin, + ModelAppHookConfig, TranslatableAdmin): + form = PostAdminForm + list_display = [ + 'title', 'author', 'date_published', 'app_config', 'languages', 'date_published_end' + ] + list_filter = ('app_config',) date_hierarchy = 'date_published' raw_id_fields = ['author'] frontend_editable_fields = ('title', 'abstract', 'post_text') enhance_exclude = ('main_image', 'tags') _fieldsets = [ (None, { - 'fields': [('title', 'categories', 'publish')] + 'fields': [('title', 'categories', 'publish', 'app_config')] }), ('Info', { 'fields': (['slug', 'tags'], @@ -57,6 +67,13 @@ class PostAdmin(EnhancedModelAdminMixin, FrontendEditableAdminMixin, }), ] + app_config_values = { + 'default_published': 'publish' + } + + 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': @@ -68,11 +85,25 @@ class PostAdmin(EnhancedModelAdminMixin, FrontendEditableAdminMixin, return field def get_fieldsets(self, request, obj=None): + 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) + if not obj: + config = app_config_default + else: + config = obj.app_config + fsets = deepcopy(self._fieldsets) - if get_setting('USE_ABSTRACT'): - fsets[0][1]['fields'].append('abstract') - if not get_setting('USE_PLACEHOLDER'): - fsets[0][1]['fields'].append('post_text') + if config: + if config.use_abstract: + fsets[0][1]['fields'].append('abstract') + if not config.use_placeholder: + fsets[0][1]['fields'].append('post_text') + else: + if get_setting('USE_ABSTRACT'): + fsets[0][1]['fields'].append('abstract') + if not get_setting('USE_PLACEHOLDER'): + fsets[0][1]['fields'].append('post_text') if get_setting('MULTISITE'): fsets[1][1]['fields'][0].append('sites') if request.user.is_superuser: @@ -83,7 +114,7 @@ class PostAdmin(EnhancedModelAdminMixin, FrontendEditableAdminMixin, return {'slug': ('title',)} def save_model(self, request, obj, form, change): - if not obj.author_id and get_setting('AUTHOR_DEFAULT'): + if not obj.author_id and obj.app_config.set_author: if get_setting('AUTHOR_DEFAULT') is True: user = request.user else: @@ -93,10 +124,63 @@ class PostAdmin(EnhancedModelAdminMixin, FrontendEditableAdminMixin, class Media: css = { - 'all': ('%sdjangocms_blog/css/%s' % (settings.STATIC_URL, - 'djangocms_blog_admin.css'),) + 'all': ('%sdjangocms_blog/css/%s' % (settings.STATIC_URL, 'djangocms_blog_admin.css'),) } +class BlogConfigAdmin(BaseAppHookConfig, TranslatableAdmin): + + @property + def declared_fieldsets(self): + return [ + (None, { + 'fields': ('type', 'namespace', 'app_title') + }), + ('Generic', { + 'fields': ( + 'config.default_published', 'config.use_placeholder', 'config.use_abstract', + 'config.set_author', + ) + }), + ('Layout', { + 'fields': ( + 'config.paginate_by', 'config.url_patterns', 'config.template_prefix', + 'config.menu_structure', + ), + 'classes': ('collapse',) + }), + ('Meta', { + 'fields': ( + 'config.object_type', + ) + }), + ('Open Graph', { + 'fields': ( + 'config.og_type', 'config.og_app_id', 'config.og_profile_id', + 'config.og_publisher', 'config.og_author_url', 'config.og_author', + ), + 'description': _( + 'You can provide plain strings, Post model attribute or method names' + ) + }), + ('Twitter', { + 'fields': ( + 'config.twitter_type', 'config.twitter_site', 'config.twitter_author', + ), + 'description': _( + 'You can provide plain strings, Post model attribute or method names' + ) + }), + ('Google+', { + 'fields': ( + 'config.gplus_type', 'config.gplus_author', + ), + 'description': _( + 'You can provide plain strings, Post model attribute or method names' + ) + }), + ] + admin.site.register(BlogCategory, BlogCategoryAdmin) admin.site.register(Post, PostAdmin) +admin.site.register(BlogConfig, BlogConfigAdmin) diff --git a/djangocms_blog/apps.py b/djangocms_blog/apps.py new file mode 100644 index 0000000..3da17b3 --- /dev/null +++ b/djangocms_blog/apps.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import, print_function, unicode_literals + +from django.utils.translation import ugettext_lazy as _ + +try: + from django.apps import AppConfig +except ImportError: + class AppConfig(object): + pass + + +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) diff --git a/djangocms_blog/cms_app.py b/djangocms_blog/cms_app.py index 508c665..5209555 100644 --- a/djangocms_blog/cms_app.py +++ b/djangocms_blog/cms_app.py @@ -1,17 +1,19 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import, print_function, unicode_literals -from cms.app_base import CMSApp +from aldryn_apphooks_config.app_base import CMSConfigApp from cms.apphook_pool import apphook_pool from django.utils.translation import ugettext_lazy as _ +from .cms_appconfig import BlogConfig from .menu import BlogCategoryMenu -class BlogApp(CMSApp): +class BlogApp(CMSConfigApp): name = _('Blog') urls = ['djangocms_blog.urls'] app_name = 'djangocms_blog' + app_config = BlogConfig menus = [BlogCategoryMenu] apphook_pool.register(BlogApp) diff --git a/djangocms_blog/cms_appconfig.py b/djangocms_blog/cms_appconfig.py new file mode 100644 index 0000000..5c0927b --- /dev/null +++ b/djangocms_blog/cms_appconfig.py @@ -0,0 +1,110 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import, print_function, unicode_literals + +from aldryn_apphooks_config.models import AppHookConfig +from aldryn_apphooks_config.utils import setup_config +from app_data import AppDataForm +from django import forms +from django.db import models +from django.utils.translation import ugettext_lazy as _ +from parler.models import TranslatableModel, TranslatedFields + +from .settings import MENU_TYPE_COMPLETE, get_setting + + +class BlogConfig(TranslatableModel, AppHookConfig): + """ + Adds some translatable, per-app-instance fields. + """ + translations = TranslatedFields( + app_title=models.CharField(_('application title'), max_length=234), + ) + + def get_app_title(self): + return getattr(self, 'app_title', _('untitled')) + + +class BlogConfigForm(AppDataForm): + default_published = forms.BooleanField( + label=_('Post published by default'), required=False, + initial=get_setting('DEFAULT_PUBLISHED') + ) + url_patterns = forms.ChoiceField( + label=_('Permalink structure'), required=False, + initial=get_setting('AVAILABLE_PERMALINK_STYLES')[0][0], + choices=get_setting('AVAILABLE_PERMALINK_STYLES') + ) + use_placeholder = forms.BooleanField( + label=_('Use placeholder and plugins for article body'), required=False, + initial=get_setting('USE_PLACEHOLDER') + ) + use_abstract = forms.BooleanField( + label=_('Use abstract field'), required=False, + initial=get_setting('USE_ABSTRACT') + ) + set_author = forms.BooleanField( + label=_('Set author'), required=False, help_text=_('Set author by default'), + initial=get_setting('AUTHOR_DEFAULT') + ) + paginate_by = forms.IntegerField( + label=_('Paginate size'), required=False, initial=get_setting('PAGINATION'), + help_text=_('When paginating list views, how many articles per page?') + ) + template_prefix = forms.CharField( + label=_('Template prefix'), required=False, initial='', + help_text=_('Alternative directory to load the blog templates from') + ) + menu_structure = forms.ChoiceField( + label=_('Menu structure'), required=True, + choices=get_setting('MENU_TYPES'), initial=MENU_TYPE_COMPLETE, + help_text=_('Structure of the django CMS menu') + ) + object_type = forms.ChoiceField( + label=_('Object type'), required=False, + choices=get_setting('TYPES'), initial=get_setting('TYPE') + ) + og_type = forms.ChoiceField( + label=_('Facebook type'), required=False, + choices=get_setting('FB_TYPES'), initial=get_setting('FB_TYPE') + ) + og_app_id = forms.CharField( + max_length=200, label=_('Facebook application ID'), required=False, + initial=get_setting('FB_PROFILE_ID') + ) + og_profile_id = forms.CharField( + max_length=200, label=_('Facebook profile ID'), required=False, + initial=get_setting('FB_PROFILE_ID') + ) + og_publisher = forms.CharField( + max_length=200, label=_('Facebook page URL'), required=False, + initial=get_setting('FB_PUBLISHER') + ) + og_author_url = forms.CharField( + max_length=200, label=_('Facebook author URL'), required=False, + initial=get_setting('FB_AUTHOR_URL') + ) + og_author = forms.CharField( + max_length=200, label=_('Facebook author'), required=False, + initial=get_setting('FB_AUTHOR') + ) + twitter_type = forms.ChoiceField( + label=_('Twitter type'), required=False, + choices=get_setting('TWITTER_TYPES'), initial=get_setting('TWITTER_TYPE') + ) + twitter_site = forms.CharField( + max_length=200, label=_('Twitter site handle'), required=False, + initial=get_setting('TWITTER_SITE') + ) + twitter_author = forms.CharField( + max_length=200, label=_('Twitter author handle'), required=False, + initial=get_setting('TWITTER_AUTHOR') + ) + gplus_type = forms.ChoiceField( + label=_('Google+ type'), required=False, + choices=get_setting('GPLUS_TYPES'), initial=get_setting('GPLUS_TYPE') + ) + gplus_author = forms.CharField( + max_length=200, label=_('Google+ author name'), required=False, + initial=get_setting('GPLUS_AUTHOR') + ) +setup_config(BlogConfigForm, BlogConfig) diff --git a/djangocms_blog/cms_plugins.py b/djangocms_blog/cms_plugins.py index 42ce80a..aab204a 100644 --- a/djangocms_blog/cms_plugins.py +++ b/djangocms_blog/cms_plugins.py @@ -1,32 +1,39 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import, print_function, unicode_literals -from cms.models.pluginmodel import CMSPlugin +import os.path + from cms.plugin_base import CMSPluginBase from cms.plugin_pool import plugin_pool from django.utils.translation import ugettext_lazy as _ from .forms import LatestEntriesForm -from .models import AuthorEntriesPlugin, BlogCategory, LatestPostsPlugin, Post +from .models import AuthorEntriesPlugin, BlogCategory, GenericBlogPlugin, LatestPostsPlugin, Post from .settings import get_setting class BlogPlugin(CMSPluginBase): module = 'Blog' + def get_render_template(self, context, instance, placeholder): + if instance.app_config.template_prefix: + return os.path.join(instance.app_config.template_prefix, self.base_render_template) + else: + return os.path.join('djangocms_blog', self.base_render_template) + class BlogLatestEntriesPlugin(BlogPlugin): """ Non cached plugin which returns the latest posts taking into account the user / toolbar state """ - render_template = 'djangocms_blog/plugins/latest_entries.html' name = _('Latest Blog Articles') model = LatestPostsPlugin form = LatestEntriesForm filter_horizontal = ('categories',) fields = ('latest_posts', 'tags', 'categories') cache = False + base_render_template = 'plugins/latest_entries.html' def render(self, context, instance, placeholder): context = super(BlogLatestEntriesPlugin, self).render(context, instance, placeholder) @@ -39,12 +46,12 @@ class BlogLatestEntriesPluginCached(BlogPlugin): """ Cached plugin which returns the latest published posts """ - render_template = 'djangocms_blog/plugins/latest_entries.html' name = _('Latest Blog Articles') model = LatestPostsPlugin form = LatestEntriesForm filter_horizontal = ('categories',) fields = ('latest_posts', 'tags', 'categories') + base_render_template = 'plugins/latest_entries.html' def render(self, context, instance, placeholder): context = super(BlogLatestEntriesPluginCached, self).render(context, instance, placeholder) @@ -57,8 +64,7 @@ class BlogAuthorPostsPlugin(BlogPlugin): module = _('Blog') name = _('Author Blog Articles') model = AuthorEntriesPlugin - form = LatestEntriesForm - render_template = 'djangocms_blog/plugins/authors.html' + base_render_template = 'plugins/authors.html' filter_horizontal = ['authors'] def render(self, context, instance, placeholder): @@ -70,36 +76,47 @@ class BlogAuthorPostsPlugin(BlogPlugin): class BlogTagsPlugin(BlogPlugin): module = _('Blog') name = _('Tags') - model = CMSPlugin - render_template = 'djangocms_blog/plugins/tags.html' + model = GenericBlogPlugin + base_render_template = 'plugins/tags.html' def render(self, context, instance, placeholder): context = super(BlogTagsPlugin, self).render(context, instance, placeholder) - context['tags'] = Post.objects.tag_cloud(queryset=Post.objects.published()) + qs = Post._default_manager + qs_post = qs + if instance.app_config: + qs_post = qs_post.namespace(instance.app_config.namespace) + context['tags'] = qs.tag_cloud(queryset=qs_post.published()) return context class BlogCategoryPlugin(BlogPlugin): module = _('Blog') name = _('Categories') - model = CMSPlugin - render_template = 'djangocms_blog/plugins/categories.html' + model = GenericBlogPlugin + base_render_template = 'plugins/categories.html' def render(self, context, instance, placeholder): context = super(BlogCategoryPlugin, self).render(context, instance, placeholder) - context['categories'] = BlogCategory.objects.all() + qs = BlogCategory._default_manager + if instance.app_config: + qs = qs.namespace(instance.app_config.namespace) + context['categories'] = qs return context class BlogArchivePlugin(BlogPlugin): module = _('Blog') name = _('Archive') - model = CMSPlugin - render_template = 'djangocms_blog/plugins/archive.html' + model = GenericBlogPlugin + base_render_template = 'plugins/archive.html' def render(self, context, instance, placeholder): context = super(BlogArchivePlugin, self).render(context, instance, placeholder) - context['dates'] = Post.objects.get_months(queryset=Post.objects.published()) + qs = Post._default_manager + qs_post = qs + if instance.app_config: + qs_post = qs.namespace(instance.app_config.namespace) + context['dates'] = qs.get_months(queryset=qs_post.published()) return context diff --git a/djangocms_blog/cms_toolbar.py b/djangocms_blog/cms_toolbar.py index e8b7a17..0cbddc6 100644 --- a/djangocms_blog/cms_toolbar.py +++ b/djangocms_blog/cms_toolbar.py @@ -4,9 +4,9 @@ from __future__ import absolute_import, print_function, unicode_literals from cms.toolbar_base import CMSToolbar from cms.toolbar_pool import toolbar_pool from django.core.urlresolvers import reverse -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import override, ugettext_lazy as _ -from .models import BLOG_CURRENT_POST_IDENTIFIER +from .models import BLOG_CURRENT_NAMESPACE, BLOG_CURRENT_POST_IDENTIFIER @toolbar_pool.register @@ -17,16 +17,21 @@ class BlogToolbar(CMSToolbar): if not self.request.user.has_perm('djangocms_blog.add_post'): return # pragma: no cover admin_menu = self.toolbar.get_or_create_menu('djangocms_blog', _('Blog')) - url = reverse('admin:djangocms_blog_post_changelist') - admin_menu.add_modal_item(_('Post list'), url=url) - url = reverse('admin:djangocms_blog_post_add') - admin_menu.add_modal_item(_('Add post'), url=url) + with override(self.current_lang): + url = reverse('admin:djangocms_blog_post_changelist') + admin_menu.add_modal_item(_('Post list'), url=url) + url = reverse('admin:djangocms_blog_post_add') + admin_menu.add_modal_item(_('Add post'), url=url) + current_config = getattr(self.request, BLOG_CURRENT_NAMESPACE, None) + if current_config: + url = reverse('admin:djangocms_blog_blogconfig_change', args=(current_config.pk,)) + admin_menu.add_modal_item(_('Edit configuration'), url=url) - current_post = getattr(self.request, BLOG_CURRENT_POST_IDENTIFIER, None) - if current_post and self.request.user.has_perm('djangocms_blog.change_post'): # pragma: no cover # NOQA - admin_menu.add_modal_item(_('Edit Post'), reverse( - 'admin:djangocms_blog_post_change', args=(current_post.pk,)), - active=True) + current_post = getattr(self.request, BLOG_CURRENT_POST_IDENTIFIER, None) + if current_post and self.request.user.has_perm('djangocms_blog.change_post'): # pragma: no cover # NOQA + admin_menu.add_modal_item(_('Edit Post'), reverse( + 'admin:djangocms_blog_post_change', args=(current_post.pk,)), + active=True) def post_template_populate(self): current_post = getattr(self.request, BLOG_CURRENT_POST_IDENTIFIER, None) diff --git a/djangocms_blog/feeds.py b/djangocms_blog/feeds.py index 0675c0d..7583018 100644 --- a/djangocms_blog/feeds.py +++ b/djangocms_blog/feeds.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import, print_function, unicode_literals +from aldryn_apphooks_config.utils import get_app_instance from django.contrib.sites.models import Site from django.contrib.syndication.views import Feed from django.core.urlresolvers import reverse @@ -12,14 +13,18 @@ from .settings import get_setting class LatestEntriesFeed(Feed): + def __call__(self, request, *args, **kwargs): + self.namespace, self.config = get_app_instance(request) + return super(LatestEntriesFeed, self).__call__(request, *args, **kwargs) + def link(self): - return reverse('djangocms_blog:posts-latest') + return reverse('%s:posts-latest' % self.namespace, current_app=self.namespace) def title(self): return _('Blog articles on %(site_name)s') % {'site_name': Site.objects.get_current().name} def items(self, obj=None): - return Post.objects.published().order_by('-date_published')[:10] + return Post.objects.namespace(self.namespace).published().order_by('-date_published')[:10] def item_title(self, item): return item.safe_translation_getter('title') diff --git a/djangocms_blog/forms.py b/djangocms_blog/forms.py index 9ffc323..c1be621 100644 --- a/djangocms_blog/forms.py +++ b/djangocms_blog/forms.py @@ -3,8 +3,11 @@ from __future__ import absolute_import, print_function, unicode_literals from django import forms from django.conf import settings +from parler.forms import TranslatableModelForm from taggit_autosuggest.widgets import TagAutoSuggest +from .models import BlogCategory, BlogConfig, Post + class LatestEntriesForm(forms.ModelForm): def __init__(self, *args, **kwargs): @@ -16,3 +19,29 @@ class LatestEntriesForm(forms.ModelForm): 'all': ('%sdjangocms_blog/css/%s' % (settings.STATIC_URL, 'djangocms_blog_admin.css'),) } + + +class PostAdminForm(TranslatableModelForm): + + class Meta: + model = Post + fields = '__all__' + + def __init__(self, *args, **kwargs): + super(PostAdminForm, self).__init__(*args, **kwargs) + + qs = BlogCategory.objects + + if getattr(self.instance, 'app_config_id', None): + qs = qs.namespace(self.instance.app_config.namespace) + elif 'initial' in kwargs and 'app_config' in kwargs['initial']: + config = BlogConfig.objects.get(pk=kwargs['initial']['app_config']) + qs = qs.namespace(config.namespace) + + if 'categories' in self.fields: + self.fields['categories'].queryset = qs + + if 'app_config' in self.fields: + # Don't allow app_configs to be added here. The correct way to add an + # apphook-config is to create an apphook on a cms Page. + self.fields['app_config'].widget.can_add_related = False diff --git a/djangocms_blog/locale/en/LC_MESSAGES/django.po b/djangocms_blog/locale/en/LC_MESSAGES/django.po index 8f44053..9752fd1 100644 --- a/djangocms_blog/locale/en/LC_MESSAGES/django.po +++ b/djangocms_blog/locale/en/LC_MESSAGES/django.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2014-11-30 12:50+0100\n" +"POT-Creation-Date: 2015-09-30 09:56+0200\n" "PO-Revision-Date: 2014-03-05 18:09+0100\n" "Last-Translator: Iacopo Spalletti\n" "Language-Team: Italian \n" @@ -17,189 +17,361 @@ msgstr "" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Lokalize 1.5\n" -#: cms_app.py:8 cms_plugins.py:32 cms_plugins.py:45 cms_plugins.py:56 -#: cms_plugins.py:67 cms_toolbar.py:18 +#: cms_app.py:13 cms_plugins.py:64 cms_plugins.py:77 cms_plugins.py:93 +#: cms_plugins.py:108 cms_toolbar.py:19 msgid "Blog" msgstr "Blog" -#: cms_plugins.py:20 +#: cms_appconfig.py:20 +msgid "application title" +msgstr "" + +#: cms_appconfig.py:24 +#, fuzzy +msgid "untitled" +msgstr "Title" + +#: cms_appconfig.py:29 +msgid "Post published by default" +msgstr "" + +#: cms_appconfig.py:33 +msgid "Permalink structure" +msgstr "" + +#: cms_appconfig.py:38 +msgid "Use placeholder and plugins for article body" +msgstr "" + +#: cms_appconfig.py:42 +msgid "Use abstract field" +msgstr "" + +#: cms_appconfig.py:46 +#, fuzzy +msgid "Set author" +msgstr "Author" + +#: cms_appconfig.py:46 +msgid "Set author by default" +msgstr "" + +#: cms_appconfig.py:50 +msgid "Paginate size" +msgstr "" + +#: cms_appconfig.py:51 +msgid "When paginating list views, how many articles per page?" +msgstr "" + +#: cms_appconfig.py:54 +msgid "Template prefix" +msgstr "" + +#: cms_appconfig.py:55 +msgid "Alternative directory to load the blog templates from" +msgstr "" + +#: cms_appconfig.py:58 +msgid "Object type" +msgstr "" + +#: cms_appconfig.py:62 +msgid "Facebook type" +msgstr "" + +#: cms_appconfig.py:66 +msgid "Facebook application ID" +msgstr "" + +#: cms_appconfig.py:69 +msgid "Facebook profile ID" +msgstr "" + +#: cms_appconfig.py:72 +msgid "Facebook page URL" +msgstr "" + +#: cms_appconfig.py:75 +msgid "Facebook author URL" +msgstr "" + +#: cms_appconfig.py:78 +msgid "Twitter type" +msgstr "" + +#: cms_appconfig.py:82 +msgid "Twitter site handle" +msgstr "" + +#: cms_appconfig.py:85 +msgid "Twitter author handle" +msgstr "" + +#: cms_appconfig.py:88 +msgid "Google+ type" +msgstr "" + +#: cms_appconfig.py:92 +msgid "Google+ author name" +msgstr "" + +#: cms_plugins.py:30 cms_plugins.py:49 msgid "Latest Blog Articles" msgstr "Latest Blog Articles" -#: cms_plugins.py:33 +#: cms_plugins.py:65 msgid "Author Blog Articles" msgstr "Author Blog Articles" -#: cms_plugins.py:46 templates/djangocms_blog/plugins/tags.html:4 +#: cms_plugins.py:78 templates/djangocms_blog/plugins/tags.html:3 msgid "Tags" msgstr "Tags" -#: cms_plugins.py:57 templates/djangocms_blog/plugins/categories.html:4 +#: cms_plugins.py:94 templates/djangocms_blog/plugins/categories.html:3 msgid "Categories" msgstr "Categories" -#: cms_plugins.py:68 templates/djangocms_blog/post_list.html:12 -#: templates/djangocms_blog/plugins/archive.html:4 +#: cms_plugins.py:109 templates/djangocms_blog/post_list.html:12 +#: templates/djangocms_blog/plugins/archive.html:3 msgid "Archive" msgstr "Archive" -#: cms_toolbar.py:20 +#: cms_toolbar.py:22 msgid "Post list" msgstr "Post list" -#: cms_toolbar.py:22 +#: cms_toolbar.py:24 msgid "Add post" msgstr "Add post" -#: cms_toolbar.py:26 +#: cms_toolbar.py:28 +msgid "Edit configuration" +msgstr "" + +#: cms_toolbar.py:32 msgid "Edit Post" msgstr "" -#: feeds.py:16 +#: feeds.py:24 #, python-format msgid "Blog articles on %(site_name)s" msgstr "Blog articles on %(site_name)s" -#: models.py:31 +#: menu.py:14 +#, fuzzy +msgid "Blog Category menu" +msgstr "blog category" + +#: models.py:34 msgid "parent" msgstr "parent" -#: models.py:33 +#: models.py:35 msgid "created at" msgstr "created at" -#: models.py:34 +#: models.py:36 msgid "modified at" msgstr "modified at" -#: models.py:37 +#: models.py:37 models.py:120 models.py:253 +msgid "app. config" +msgstr "" + +#: models.py:40 msgid "name" msgstr "name" -#: models.py:38 models.py:105 +#: models.py:41 models.py:124 msgid "slug" msgstr "slug" -#: models.py:45 +#: models.py:48 msgid "blog category" msgstr "blog category" -#: models.py:46 +#: models.py:49 msgid "blog categories" msgstr "blog categories" -#: models.py:70 -msgid "Author" +#: models.py:89 +#, fuzzy +msgid "author" msgstr "Author" -#: models.py:75 -msgid "Published Since" +#: models.py:92 +#, fuzzy +msgid "created" +msgstr "created at" + +#: models.py:93 +#, fuzzy +msgid "last modified" +msgstr "modified at" + +#: models.py:94 +#, fuzzy +msgid "published since" msgstr "Published Since" -#: models.py:77 -msgid "Published Until" +#: models.py:96 +#, fuzzy +msgid "published until" msgstr "Published Until" -#: models.py:79 -msgid "Publish" +#: models.py:98 +#, fuzzy +msgid "publish" msgstr "Publish" -#: models.py:80 +#: models.py:99 msgid "category" msgstr "category" -#: models.py:82 -msgid "Main image" +#: models.py:101 +#, fuzzy +msgid "main image" msgstr "Main image" -#: models.py:85 -msgid "Main image thumbnail" +#: models.py:105 +#, fuzzy +msgid "main image thumbnail" msgstr "Main image thumbnail" -#: models.py:89 -msgid "Main image full" +#: models.py:110 +#, fuzzy +msgid "main image full" msgstr "Main image full" -#: models.py:93 -msgid "Enable comments on post" +#: models.py:114 +msgid "enable comments on post" msgstr "" -#: models.py:96 +#: models.py:116 msgid "Site(s)" msgstr "" -#: models.py:98 +#: models.py:117 msgid "" "Select sites in which to show the post. If none is set it will be visible in " "all the configured sites." msgstr "" -#: models.py:104 -msgid "Title" +#: models.py:123 +#, fuzzy +msgid "title" msgstr "Title" -#: models.py:106 -msgid "Abstract" +#: models.py:125 +msgid "abstract" msgstr "" -#: models.py:107 -msgid "Post meta description" -msgstr "Post meta description" - -#: models.py:109 +#: models.py:126 #, fuzzy -msgid "Post meta keywords" +msgid "post meta description" msgstr "Post meta description" -#: models.py:111 +#: models.py:128 #, fuzzy -msgid "Post meta title" +msgid "post meta keywords" msgstr "Post meta description" -#: models.py:112 +#: models.py:130 +#, fuzzy +msgid "post meta title" +msgstr "Post meta description" + +#: models.py:131 msgid "used in title tag and social sharing" msgstr "" -#: models.py:115 -msgid "Text" -msgstr "Text" +#: models.py:134 +msgid "text" +msgstr "" -#: models.py:178 +#: models.py:170 msgid "blog article" msgstr "blog article" -#: models.py:179 +#: models.py:171 msgid "blog articles" msgstr "blog articles" -#: models.py:222 models.py:250 -msgid "Articles" -msgstr "Articles" +#: models.py:269 +msgid "generic blog plugin" +msgstr "" -#: models.py:223 +#: models.py:273 models.py:306 +#, fuzzy +msgid "articles" +msgstr "0 articles" + +#: models.py:274 msgid "The number of latests articles to be displayed." msgstr "The number of latests articles to be displayed." -#: models.py:225 +#: models.py:276 +msgid "filter by tag" +msgstr "" + +#: models.py:277 msgid "Show only the blog articles tagged with chosen tags." msgstr "Show only the blog articles tagged with chosen tags." -#: models.py:227 +#: models.py:280 +#, fuzzy +msgid "filter by category" +msgstr "blog category" + +#: models.py:281 msgid "Show only the blog articles tagged with chosen categories." msgstr "Show only the blog articles tagged with chosen categories." -#: models.py:246 templates/djangocms_blog/plugins/authors.html:3 -msgid "Authors" +#: models.py:285 +#, python-format +msgid "%s latest articles by tag" +msgstr "" + +#: models.py:302 +#, fuzzy +msgid "authors" msgstr "Authors" -#: models.py:251 +#: models.py:307 msgid "The number of author articles to be displayed." msgstr "The number of author articles to be displayed." -#: templates/djangocms_blog/post_detail.html:18 -#: templates/djangocms_blog/includes/blog_item.html:11 -msgid "by" -msgstr "by" +#: models.py:311 +#, python-format +msgid "%s latest articles by author" +msgstr "" + +#: settings.py:11 +#, fuzzy +msgid "Article" +msgstr "Articles" + +#: settings.py:12 +msgid "Website" +msgstr "" + +#: settings.py:17 +msgid "Full date" +msgstr "" + +#: settings.py:18 +msgid "Year / Month" +msgstr "" + +#: settings.py:19 templates/djangocms_blog/post_list.html:14 +msgid "Category" +msgstr "Category" + +#: settings.py:20 +#, fuzzy +msgid "Just slug" +msgstr "slug" #: templates/djangocms_blog/post_list.html:11 msgid "Articles by" @@ -209,16 +381,11 @@ msgstr "Articles by" msgid "Tag" msgstr "Tag" -#: templates/djangocms_blog/post_list.html:14 -msgid "Category" -msgstr "Category" - #: templates/djangocms_blog/post_list.html:21 -#: templates/djangocms_blog/plugins/archive.html:27 +#: templates/djangocms_blog/plugins/archive.html:26 #: templates/djangocms_blog/plugins/authors.html:15 -#: templates/djangocms_blog/plugins/categories.html:16 #: templates/djangocms_blog/plugins/latest_entries.html:7 -#: templates/djangocms_blog/plugins/tags.html:16 +#: templates/djangocms_blog/plugins/tags.html:15 msgid "No article found." msgstr "No article found." @@ -226,43 +393,59 @@ msgstr "No article found." msgid "Back" msgstr "Back" -#: templates/djangocms_blog/post_list.html:30 +#: templates/djangocms_blog/post_list.html:29 msgid "previous" msgstr "previous" -#: templates/djangocms_blog/post_list.html:33 +#: templates/djangocms_blog/post_list.html:32 msgid "Page" msgstr "Page" -#: templates/djangocms_blog/post_list.html:33 +#: templates/djangocms_blog/post_list.html:32 msgid "of" msgstr "of" -#: templates/djangocms_blog/post_list.html:36 +#: templates/djangocms_blog/post_list.html:35 msgid "next" msgstr "next" -#: templates/djangocms_blog/includes/blog_item.html:46 +#: templates/djangocms_blog/includes/blog_item.html:24 msgid "read more" msgstr "read more" -#: templates/djangocms_blog/plugins/archive.html:18 +#: templates/djangocms_blog/includes/blog_meta.html:6 +msgid "by" +msgstr "by" + +#: templates/djangocms_blog/plugins/archive.html:17 #: templates/djangocms_blog/plugins/authors.html:10 -#: templates/djangocms_blog/plugins/categories.html:11 -#: templates/djangocms_blog/plugins/tags.html:11 +#: templates/djangocms_blog/plugins/categories.html:10 +#: templates/djangocms_blog/plugins/tags.html:10 #, python-format msgid "1 article" msgid_plural "%(articles)s articles" msgstr[0] "1 article" msgstr[1] "%(articles)s articles" -#: templates/djangocms_blog/plugins/archive.html:19 +#: templates/djangocms_blog/plugins/archive.html:18 #: templates/djangocms_blog/plugins/authors.html:11 -#: templates/djangocms_blog/plugins/categories.html:12 -#: templates/djangocms_blog/plugins/tags.html:12 +#: templates/djangocms_blog/plugins/categories.html:11 +#: templates/djangocms_blog/plugins/tags.html:11 msgid "0 articles" msgstr "0 articles" +#: templates/djangocms_blog/plugins/authors.html:3 +msgid "Authors" +msgstr "Authors" + +#: templates/djangocms_blog/plugins/categories.html:15 +#, fuzzy +msgid "No categories found." +msgstr "No article found." + +#~ msgid "Text" +#~ msgstr "Text" + #~ msgid "blog post" #~ msgstr "blog post" diff --git a/djangocms_blog/managers.py b/djangocms_blog/managers.py index 75d670a..644fbc6 100644 --- a/djangocms_blog/managers.py +++ b/djangocms_blog/managers.py @@ -2,10 +2,12 @@ from __future__ import absolute_import, print_function, unicode_literals import django +from aldryn_apphooks_config.managers.parler import ( + AppHookConfigTranslatableManager, AppHookConfigTranslatableQueryset, +) from django.contrib.sites.models import Site from django.db import models from django.utils.timezone import now -from parler.managers import TranslatableQuerySet, TranslationManager try: from collections import Counter @@ -21,7 +23,7 @@ class TaggedFilterItem(object): o con gli stessi tag di un model o un queryset """ tags = self._taglist(other_model, queryset) - return self.get_queryset().filter(taglist__in=tags) + return self.get_queryset().filter(tags__in=tags).distinct() def _taglist(self, other_model=None, queryset=None): """ @@ -29,21 +31,21 @@ class TaggedFilterItem(object): o queryset passati come argomento """ from taggit.models import TaggedItem - filtro = None + filter = None if queryset is not None: - filtro = set() + filter = set() for item in queryset.all(): - filtro.update(item.tags.all()) - filtro = set([tag.id for tag in filtro]) + filter.update(item.tags.all()) + filter = set([tag.id for tag in filter]) elif other_model is not None: - filtro = set(TaggedItem.objects.filter( + filter = set(TaggedItem.objects.filter( content_type__model=other_model.__name__.lower() ).values_list('tag_id', flat=True)) tags = set(TaggedItem.objects.filter( content_type__model=self.model.__name__.lower() ).values_list('tag_id', flat=True)) - if filtro is not None: - tags = tags.intersection(filtro) + if filter is not None: + tags = tags.intersection(filter) return list(tags) def tag_list(self, other_model=None, queryset=None): @@ -76,7 +78,7 @@ class TaggedFilterItem(object): return sorted(tags, key=lambda x: -x.count) -class GenericDateQuerySet(TranslatableQuerySet): +class GenericDateQuerySet(AppHookConfigTranslatableQueryset): start_date_field = 'date_published' end_date_field = 'date_published_end' publish_field = 'publish' @@ -120,7 +122,7 @@ class GenericDateQuerySet(TranslatableQuerySet): return self.active_translations(language_code=language).on_site() -class GenericDateTaggedManager(TaggedFilterItem, TranslationManager): +class GenericDateTaggedManager(TaggedFilterItem, AppHookConfigTranslatableManager): use_for_related_fields = True queryset_class = GenericDateQuerySet diff --git a/djangocms_blog/menu.py b/djangocms_blog/menu.py index d28099f..6b4e883 100644 --- a/djangocms_blog/menu.py +++ b/djangocms_blog/menu.py @@ -3,66 +3,75 @@ from __future__ import absolute_import, print_function, unicode_literals from cms.menu_bases import CMSAttachMenu from django.db.models.signals import post_delete, post_save -from django.utils.translation import get_language, ugettext_lazy as _ -from menus.base import Modifier, NavigationNode +from django.utils.translation import get_language_from_request, ugettext_lazy as _ +from menus.base import NavigationNode from menus.menu_pool import menu_pool -from .models import BlogCategory +from .cms_appconfig import BlogConfig +from .models import BlogCategory, Post +from .settings import MENU_TYPE_CATEGORIES, MENU_TYPE_COMPLETE, MENU_TYPE_POSTS class BlogCategoryMenu(CMSAttachMenu): - name = _('Blog Category menu') + name = _('Blog menu') def get_nodes(self, request): nodes = [] - qs = BlogCategory.objects.translated(get_language()) - qs = qs.order_by('parent__id', 'translations__name') - for category in qs: - node = NavigationNode( - category.name, - category.get_absolute_url(), - category.pk, - category.parent_id - ) - nodes.append(node) + + language = get_language_from_request(request, check_path=True) + + categories_menu = False + posts_menu = False + config = False + if hasattr(self, 'instance') and self.instance: + config = BlogConfig.objects.get(namespace=self.instance.application_namespace) + if config.menu_structure in (MENU_TYPE_COMPLETE, MENU_TYPE_CATEGORIES): + categories_menu = True + if config.menu_structure in (MENU_TYPE_COMPLETE, MENU_TYPE_POSTS): + posts_menu = True + + if categories_menu: + categories = BlogCategory.objects + if config: + categories = categories.namespace(self.instance.application_namespace) + categories = categories.active_translations(language).distinct() + categories = categories.order_by('parent__id', 'translations__name') + for category in categories: + 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) + ) + nodes.append(node) + + if posts_menu: + posts = Post.objects + if hasattr(self, 'instance') and self.instance: + posts = posts.namespace(self.instance.application_namespace) + posts = posts.active_translations(language).distinct() + for post in posts: + if categories_menu: + category = post.categories.first() + parent = '%s-%s' % (category.__class__.__name__, category.pk) + post_id = '%s-%s' % (post.__class__.__name__, post.pk), + else: + parent = None + post_id = '%s-%s' % (post.__class__.__name__, post.pk), + node = NavigationNode( + post.get_title(), + post.get_absolute_url(language), + post_id, + parent + ) + nodes.append(node) + return nodes 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): - if post_cut: - return nodes - if not hasattr(request, 'toolbar'): - return nodes - models = ('djangocms_blog.post', 'djangocms_blog.blogcategory') - model = request.toolbar.get_object_model() - if model not in models: - return nodes - if model == 'djangocms_blog.blogcategory': - cat = request.toolbar.obj - else: - cat = request.toolbar.obj.categories.first() - if not cat: - return nodes - - for node in nodes: - if (node.namespace.startswith(BlogCategoryMenu.__name__) and - cat.pk == node.id): - node.selected = True - # no break here because django-cms maintains two menu structures - # for every apphook (attached to published page and draft page) - return nodes - -menu_pool.register_modifier(BlogNavModifier) - - def clear_menu_cache(**kwargs): menu_pool.clear(all=True) diff --git a/djangocms_blog/migrations/0010_auto_20150923_1151.py b/djangocms_blog/migrations/0010_auto_20150923_1151.py new file mode 100644 index 0000000..37a831e --- /dev/null +++ b/djangocms_blog/migrations/0010_auto_20150923_1151.py @@ -0,0 +1,124 @@ + #-*- coding: utf-8 -*- +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 = [ + ('cms', '__latest__'), + ('djangocms_blog', '0009_latestpostsplugin_tags_new'), + ] + + operations = [ + migrations.CreateModel( + name='BlogConfig', + fields=[ + ('id', models.AutoField(auto_created=True, verbose_name='ID', serialize=False, primary_key=True)), + ('type', models.CharField(verbose_name='type', max_length=100)), + ('namespace', models.CharField(default=None, verbose_name='instance namespace', unique=True, max_length=100)), + ('app_data', app_data.fields.AppDataField(editable=False, default='{}')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='BlogConfigTranslation', + fields=[ + ('id', models.AutoField(auto_created=True, verbose_name='ID', serialize=False, primary_key=True)), + ('language_code', models.CharField(db_index=True, verbose_name='Language', max_length=15)), + ('app_title', models.CharField(verbose_name='application title', max_length=234)), + ('master', models.ForeignKey(editable=False, to='djangocms_blog.BlogConfig', related_name='translations', null=True)), + ], + options={ + 'verbose_name': 'blog config Translation', + 'db_table': 'djangocms_blog_blogconfig_translation', + 'default_permissions': (), + 'db_tablespace': '', + 'managed': True, + }, + ), + migrations.CreateModel( + name='GenericBlogPlugin', + fields=[ + ('cmsplugin_ptr', models.OneToOneField(parent_link=True, serialize=False, primary_key=True, auto_created=True, to='cms.CMSPlugin')), + ('app_config', aldryn_apphooks_config.fields.AppHookConfigField(verbose_name='app. config', blank=True, to='djangocms_blog.BlogConfig', help_text='When selecting a value, the form is reloaded to get the updated default')), + ], + options={ + 'abstract': False, + }, + bases=('cms.cmsplugin',), + ), + migrations.AlterField( + model_name='posttranslation', + name='abstract', + field=djangocms_text_ckeditor.fields.HTMLField(default='', verbose_name='abstract', blank=True), + ), + migrations.AddField( + model_name='authorentriesplugin', + name='app_config', + field=aldryn_apphooks_config.fields.AppHookConfigField(default=None, blank=True, verbose_name='app. config', to='djangocms_blog.BlogConfig', help_text='When selecting a value, the form is reloaded to get the updated default'), + preserve_default=False, + ), + migrations.AddField( + model_name='blogcategory', + name='app_config', + field=aldryn_apphooks_config.fields.AppHookConfigField(default=None, verbose_name='app. config', to='djangocms_blog.BlogConfig', help_text='When selecting a value, the form is reloaded to get the updated default'), + preserve_default=False, + ), + migrations.AddField( + model_name='latestpostsplugin', + name='app_config', + field=aldryn_apphooks_config.fields.AppHookConfigField(default=None, blank=True, verbose_name='app. config', to='djangocms_blog.BlogConfig', help_text='When selecting a value, the form is reloaded to get the updated default'), + preserve_default=False, + ), + migrations.AddField( + model_name='post', + name='app_config', + field=aldryn_apphooks_config.fields.AppHookConfigField(default=None, verbose_name='app. config', to='djangocms_blog.BlogConfig', help_text='When selecting a value, the form is reloaded to get the updated default'), + preserve_default=False, + ), + migrations.AlterUniqueTogether( + name='blogconfigtranslation', + unique_together=set([('language_code', 'master')]), + ), + migrations.AlterField( + model_name='post', + 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/models.py b/djangocms_blog/models.py index f5deb6a..9ea7fc0 100644 --- a/djangocms_blog/models.py +++ b/djangocms_blog/models.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import, print_function, unicode_literals +from aldryn_apphooks_config.fields import AppHookConfigField +from aldryn_apphooks_config.managers.parler import AppHookConfigTranslatableManager from cms.models import CMSPlugin, PlaceholderField from django.conf import settings as dj_settings from django.core.urlresolvers import reverse @@ -13,14 +15,15 @@ from django.utils.translation import get_language, ugettext_lazy as _ from djangocms_text_ckeditor.fields import HTMLField from filer.fields.image import FilerImageField from meta_mixin.models import ModelMeta -from parler.managers import TranslationManager from parler.models import TranslatableModel, TranslatedFields from taggit_autosuggest.managers import TaggableManager +from .cms_appconfig import BlogConfig from .managers import GenericDateTaggedManager from .settings import get_setting BLOG_CURRENT_POST_IDENTIFIER = 'djangocms_post_current' +BLOG_CURRENT_NAMESPACE = 'djangocms_post_current_config' @python_2_unicode_compatible @@ -28,10 +31,12 @@ class BlogCategory(TranslatableModel): """ Blog category """ - parent = models.ForeignKey('self', verbose_name=_('parent'), null=True, - blank=True) + parent = models.ForeignKey('self', verbose_name=_('parent'), null=True, blank=True) 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') + ) translations = TranslatedFields( name=models.CharField(_('name'), max_length=255), @@ -39,7 +44,7 @@ class BlogCategory(TranslatableModel): meta={'unique_together': (('language_code', 'slug'),)} ) - objects = TranslationManager() + objects = AppHookConfigTranslatableManager() class Meta: verbose_name = _('blog category') @@ -47,16 +52,23 @@ class BlogCategory(TranslatableModel): @property def count(self): - return self.blog_posts.published().count() + return self.blog_posts.namespace(self.app_config.namespace).published().count() - def get_absolute_url(self): - lang = get_language() - if self.has_translation(lang): + def get_absolute_url(self, lang=None): + if not lang: + lang = get_language() + if self.has_translation(lang, ): slug = self.safe_translation_getter('slug', language_code=lang) - return reverse('djangocms_blog:posts-category', kwargs={'category': slug}) + return reverse( + '%s:posts-category' % self.app_config.namespace, + kwargs={'category': slug}, + current_app=self.app_config.namespace + ) # in case category doesn't exist in this language, gracefully fallback # to posts-latest - return reverse('djangocms_blog:posts-latest') + return reverse( + '%s:posts-latest' % self.app_config.namespace, current_app=self.app_config.namespace + ) def __str__(self): return self.safe_translation_getter('name') @@ -81,9 +93,9 @@ class Post(ModelMeta, TranslatableModel): date_created = models.DateTimeField(_('created'), auto_now_add=True) date_modified = models.DateTimeField(_('last modified'), auto_now=True) - date_published = models.DateTimeField(_('published Since'), + date_published = models.DateTimeField(_('published since'), default=timezone.now) - date_published_end = models.DateTimeField(_('published Until'), null=True, + date_published_end = models.DateTimeField(_('published until'), null=True, blank=True) publish = models.BooleanField(_('publish'), default=False) categories = models.ManyToManyField('djangocms_blog.BlogCategory', verbose_name=_('category'), @@ -104,10 +116,12 @@ class Post(ModelMeta, TranslatableModel): enable_comments = models.BooleanField(verbose_name=_('enable comments on post'), default=get_setting('ENABLE_COMMENTS')) sites = models.ManyToManyField('sites.Site', verbose_name=_('Site(s)'), blank=True, - null=True, help_text=_('Select sites in which to show the post. ' - u'If none is set it will be ' - u'visible in all the configured sites.')) + 'If none is set it will be ' + 'visible in all the configured sites.')) + app_config = AppHookConfigField( + BlogConfig, null=True, verbose_name=_('app. config') + ) translations = TranslatedFields( title=models.CharField(_('title'), max_length=255), @@ -132,23 +146,24 @@ class Post(ModelMeta, TranslatableModel): _metadata = { 'title': 'get_title', 'description': 'get_description', + 'keywords': 'get_keywords', 'og_description': 'get_description', 'twitter_description': 'get_description', 'gplus_description': 'get_description', - 'keywords': 'get_keywords', - 'locale': None, + 'locale': 'get_locale', 'image': 'get_image_full_url', - 'object_type': get_setting('TYPE'), - 'og_type': get_setting('FB_TYPE'), - 'og_app_id': get_setting('FB_APPID'), - 'og_profile_id': get_setting('FB_PROFILE_ID'), - 'og_publisher': get_setting('FB_PUBLISHER'), - 'og_author_url': get_setting('FB_AUTHOR_URL'), - 'twitter_type': get_setting('TWITTER_TYPE'), - 'twitter_site': get_setting('TWITTER_SITE'), - 'twitter_author': get_setting('TWITTER_AUTHOR'), - 'gplus_type': get_setting('GPLUS_TYPE'), - 'gplus_author': get_setting('GPLUS_AUTHOR'), + 'object_type': 'get_meta_attribute', + 'og_type': 'get_meta_attribute', + 'og_app_id': 'get_meta_attribute', + 'og_profile_id': 'get_meta_attribute', + 'og_publisher': 'get_meta_attribute', + 'og_author_url': 'get_meta_attribute', + 'og_author': 'get_meta_attribute', + 'twitter_type': 'get_meta_attribute', + 'twitter_site': 'get_meta_attribute', + 'twitter_author': 'get_meta_attribute', + 'gplus_type': 'get_meta_attribute', + 'gplus_author': 'get_meta_attribute', 'published_time': 'date_published', 'modified_time': 'date_modified', 'expiration_time': 'date_published_end', @@ -165,19 +180,49 @@ class Post(ModelMeta, TranslatableModel): def __str__(self): return self.safe_translation_getter('title') - def get_absolute_url(self): - kwargs = {'year': self.date_published.year, - 'month': '%02d' % self.date_published.month, - 'day': '%02d' % self.date_published.day, - 'slug': self.safe_translation_getter('slug', - language_code=get_language(), - any_language=True)} - return reverse('djangocms_blog:post-detail', kwargs=kwargs) + def get_absolute_url(self, lang=None): + if not lang: + lang = get_language() + category = self.categories.first() + kwargs = {} + urlconf = get_setting('PERMALINK_URLS')[self.app_config.url_patterns] + if '' in urlconf: + kwargs['year'] = self.date_published.year + if '' in urlconf: + kwargs['month'] = '%02d' % self.date_published.month + if '' in urlconf: + kwargs['day'] = '%02d' % self.date_published.day + if '' in urlconf: + kwargs['slug'] = self.safe_translation_getter('slug', language_code=lang, any_language=True) # NOQA + if '' in urlconf: + kwargs['category'] = category.safe_translation_getter('slug', language_code=lang, any_language=True) # NOQA + return reverse('%s:post-detail' % self.app_config.namespace, kwargs=kwargs) - def save(self, *args, **kwargs): - if not self.slug and self.title: - self.slug = slugify(self.title) - super(Post, self).save(*args, **kwargs) + def get_meta_attribute(self, param): + """ + Retrieves django-meta attributes from apphook config instance + :param param: django-meta attribute passed as key + """ + attr = None + value = getattr(self.app_config, param) + if value: + attr = getattr(self, value, None) + if attr is not None: + if callable(attr): + try: + data = attr(param) + except TypeError: + data = attr() + else: + data = attr + else: + data = value + return data + + def save_translation(self, translation, *args, **kwargs): + if not translation.slug and translation.title: + translation.slug = slugify(translation.title) + super(Post, self).save_translation(translation, *args, **kwargs) def get_title(self): title = self.safe_translation_getter('meta_title', any_language=True) @@ -188,6 +233,9 @@ class Post(ModelMeta, TranslatableModel): def get_keywords(self): return self.safe_translation_getter('meta_keywords').strip().split(',') + def get_locale(self): + return self.get_current_language() + def get_description(self): description = self.safe_translation_getter('meta_description', any_language=True) if not description: @@ -224,23 +272,28 @@ class Post(ModelMeta, TranslatableModel): @python_2_unicode_compatible class BasePostPlugin(CMSPlugin): + app_config = AppHookConfigField( + BlogConfig, null=True, verbose_name=_('app. config'), blank=True + ) class Meta: abstract = True def post_queryset(self, request=None): language = get_language() - posts = Post._default_manager.active_translations(language_code=language) + posts = Post._default_manager + if self.app_config: + posts = posts.namespace(self.app_config.namespace) + posts = posts.active_translations(language_code=language) if not request or not getattr(request, 'toolbar', False) or not request.toolbar.edit_mode: posts = posts.published() return posts def __str__(self): - return force_text(self.latest_posts) + return _('generic blog plugin') class LatestPostsPlugin(BasePostPlugin): - latest_posts = models.IntegerField(_('articles'), default=get_setting('LATEST_POSTS'), help_text=_('The number of latests ' u'articles to be displayed.')) @@ -292,6 +345,16 @@ class AuthorEntriesPlugin(BasePostPlugin): authors = self.authors.all() for author in authors: author.count = 0 - if author.djangocms_blog_post_author.filter(publish=True).exists(): - author.count = author.djangocms_blog_post_author.filter(publish=True).count() + qs = author.djangocms_blog_post_author + if self.app_config: + qs = qs.namespace(self.app_config.namespace) + count = qs.filter(publish=True).count() + if count: + author.count = count return authors + + +class GenericBlogPlugin(BasePostPlugin): + + class Meta: + abstract = False diff --git a/djangocms_blog/settings.py b/djangocms_blog/settings.py index 6676154..f43cd97 100644 --- a/djangocms_blog/settings.py +++ b/djangocms_blog/settings.py @@ -1,11 +1,35 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import, print_function, unicode_literals +MENU_TYPE_COMPLETE = 'complete' +MENU_TYPE_CATEGORIES = 'categories' +MENU_TYPE_POSTS = 'posts' +MENU_TYPE_NONE = 'none' + def get_setting(name): from django.conf import settings + from django.utils.translation import ugettext_lazy as _ from meta_mixin import settings as meta_settings + PERMALINKS = ( + ('full_date', _('Full date')), + ('short_date', _('Year / Month')), + ('category', _('Category')), + ('slug', _('Just slug')), + ) + PERMALINKS_URLS = { + 'full_date': r'^(?P\d{4})/(?P\d{1,2})/(?P\d{1,2})/(?P\w[-\w]*)/$', + 'short_date': r'^(?P\d{4})/(?P\d{1,2})/(?P\w[-\w]*)/$', + 'category': r'^(?P\w[-\w]*)/(?P\w[-\w]*)/$', + 'slug': r'^(?P\w[-\w]*)/$', + } + MENU_TYPES = ( + (MENU_TYPE_COMPLETE, _('Categories and posts')), + (MENU_TYPE_CATEGORIES, _('Categories only')), + (MENU_TYPE_POSTS, _('Posts only')), + (MENU_TYPE_NONE, _('None')), + ) default = { 'BLOG_IMAGE_THUMBNAIL_SIZE': getattr(settings, 'BLOG_IMAGE_THUMBNAIL_SIZE', { 'size': '120x120', @@ -19,38 +43,41 @@ def get_setting(name): 'upscale': False }), - 'BLOG_TAGCLOUD_MIN': getattr(settings, 'BLOG_TAGCLOUD_MIN', 1), - 'BLOG_TAGCLOUD_MAX': getattr(settings, 'BLOG_TAGCLOUD_MAX', 10), 'BLOG_PAGINATION': getattr(settings, 'BLOG_PAGINATION', 10), 'BLOG_LATEST_POSTS': getattr(settings, 'BLOG_LATEST_POSTS', 5), - 'BLOG_POSTS_LIST_TRUNCWORDS_COUNT': getattr(settings, - 'BLOG_POSTS_LIST_TRUNCWORDS_COUNT', - 100), + 'BLOG_POSTS_LIST_TRUNCWORDS_COUNT': getattr( + settings, 'BLOG_POSTS_LIST_TRUNCWORDS_COUNT', 100 + ), + 'BLOG_MENU_TYPE': MENU_TYPES, + 'BLOG_MENU_TYPES': MENU_TYPES, 'BLOG_TYPE': getattr(settings, 'BLOG_TYPE', 'Article'), + 'BLOG_TYPES': meta_settings.OBJECT_TYPES, 'BLOG_FB_TYPE': getattr(settings, 'BLOG_FB_TYPE', 'Article'), - 'BLOG_FB_APPID': getattr(settings, 'BLOG_FB_APPID', - meta_settings.FB_APPID), - 'BLOG_FB_PROFILE_ID': getattr(settings, 'BLOG_FB_PROFILE_ID', - meta_settings.FB_PROFILE_ID), - 'BLOG_FB_PUBLISHER': getattr(settings, 'BLOG_FB_PUBLISHER', - meta_settings.FB_PUBLISHER), - 'BLOG_FB_AUTHOR_URL': getattr(settings, 'BLOG_FB_AUTHOR_URL', - 'get_author_url'), - 'BLOG_FB_AUTHOR': getattr(settings, 'BLOG_FB_AUTHOR', - 'get_author_name'), - 'BLOG_TWITTER_TYPE': getattr(settings, 'BLOG_TWITTER_TYPE', 'Summary'), - 'BLOG_TWITTER_SITE': getattr(settings, 'BLOG_TWITTER_SITE', - meta_settings.TWITTER_SITE), - 'BLOG_TWITTER_AUTHOR': getattr(settings, 'BLOG_TWITTER_AUTHOR', - 'get_author_twitter'), - 'BLOG_GPLUS_TYPE': getattr(settings, 'BLOG_GPLUS_SCOPE_CATEGORY', - 'Blog'), - 'BLOG_GPLUS_AUTHOR': getattr(settings, 'BLOG_GPLUS_AUTHOR', - 'get_author_gplus'), + 'BLOG_FB_TYPES': getattr(settings, 'BLOG_FB_TYPES', meta_settings.FB_TYPES), + 'BLOG_FB_APPID': getattr(settings, 'BLOG_FB_APPID', meta_settings.FB_APPID), + 'BLOG_FB_PROFILE_ID': getattr(settings, 'BLOG_FB_PROFILE_ID', meta_settings.FB_PROFILE_ID), + 'BLOG_FB_PUBLISHER': getattr(settings, 'BLOG_FB_PUBLISHER', meta_settings.FB_PUBLISHER), + 'BLOG_FB_AUTHOR_URL': getattr(settings, 'BLOG_FB_AUTHOR_URL', 'get_author_url'), + 'BLOG_FB_AUTHOR': getattr(settings, 'BLOG_FB_AUTHOR', 'get_author_name'), + 'BLOG_TWITTER_TYPE': getattr(settings, 'BLOG_TWITTER_TYPE', 'summary'), + 'BLOG_TWITTER_TYPES': getattr(settings, 'BLOG_TWITTER_TYPES', meta_settings.TWITTER_TYPES), + 'BLOG_TWITTER_SITE': getattr(settings, 'BLOG_TWITTER_SITE', meta_settings.TWITTER_SITE), + 'BLOG_TWITTER_AUTHOR': getattr(settings, 'BLOG_TWITTER_AUTHOR', 'get_author_twitter'), + 'BLOG_GPLUS_TYPE': getattr(settings, 'BLOG_GPLUS_TYPE', 'Blog'), + 'BLOG_GPLUS_TYPES': getattr(settings, 'BLOG_GPLUS_TYPES', meta_settings.GPLUS_TYPES), + 'BLOG_GPLUS_AUTHOR': getattr(settings, 'BLOG_GPLUS_AUTHOR', 'get_author_gplus'), 'BLOG_ENABLE_COMMENTS': getattr(settings, 'BLOG_ENABLE_COMMENTS', True), 'BLOG_USE_ABSTRACT': getattr(settings, 'BLOG_USE_ABSTRACT', True), 'BLOG_USE_PLACEHOLDER': getattr(settings, 'BLOG_USE_PLACEHOLDER', True), 'BLOG_MULTISITE': getattr(settings, 'BLOG_MULTISITE', True), 'BLOG_AUTHOR_DEFAULT': getattr(settings, 'BLOG_AUTHOR_DEFAULT', True), + '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] diff --git a/djangocms_blog/south_migrations/0014_auto__add_genericblogplugin__add_blogconfig__add_blogconfigtranslation.py b/djangocms_blog/south_migrations/0014_auto__add_genericblogplugin__add_blogconfig__add_blogconfigtranslation.py new file mode 100644 index 0000000..ecd1300 --- /dev/null +++ b/djangocms_blog/south_migrations/0014_auto__add_genericblogplugin__add_blogconfig__add_blogconfigtranslation.py @@ -0,0 +1,285 @@ +# -*- coding: utf-8 -*- +from south.utils import datetime_utils as datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding model 'GenericBlogPlugin' + db.create_table(u'djangocms_blog_genericblogplugin', ( + (u'cmsplugin_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['cms.CMSPlugin'], unique=True, primary_key=True)), + ('app_config', self.gf('aldryn_apphooks_config.fields.AppHookConfigField')(to=orm['djangocms_blog.BlogConfig'], null=True, blank=True)), + )) + db.send_create_signal(u'djangocms_blog', ['GenericBlogPlugin']) + + # Adding model 'BlogConfig' + db.create_table(u'djangocms_blog_blogconfig', ( + (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('type', self.gf('django.db.models.fields.CharField')(max_length=100)), + ('namespace', self.gf('django.db.models.fields.CharField')(default=None, unique=True, max_length=100)), + ('app_data', self.gf('app_data.fields.AppDataField')(default='{}')), + )) + db.send_create_signal(u'djangocms_blog', ['BlogConfig']) + + # Adding model 'BlogConfigTranslation' + db.create_table(u'djangocms_blog_blogconfig_translation', ( + (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('language_code', self.gf('django.db.models.fields.CharField')(max_length=15, db_index=True)), + ('app_title', self.gf('django.db.models.fields.CharField')(max_length=234)), + (u'master', self.gf('django.db.models.fields.related.ForeignKey')(related_name='translations', null=True, to=orm['djangocms_blog.BlogConfig'])), + )) + db.send_create_signal(u'djangocms_blog', ['BlogConfigTranslation']) + + # Adding unique constraint on 'BlogConfigTranslation', fields ['language_code', u'master'] + db.create_unique(u'djangocms_blog_blogconfig_translation', ['language_code', u'master_id']) + + # Adding field 'BlogCategory.app_config' + db.add_column(u'djangocms_blog_blogcategory', 'app_config', + self.gf('aldryn_apphooks_config.fields.AppHookConfigField')(to=orm['djangocms_blog.BlogConfig'], null=True), + keep_default=False) + + # Adding field 'Post.app_config' + db.add_column(u'djangocms_blog_post', 'app_config', + self.gf('aldryn_apphooks_config.fields.AppHookConfigField')(to=orm['djangocms_blog.BlogConfig'], null=True), + keep_default=False) + + # Adding field 'LatestPostsPlugin.app_config' + db.add_column(u'djangocms_blog_latestpostsplugin', 'app_config', + self.gf('aldryn_apphooks_config.fields.AppHookConfigField')(to=orm['djangocms_blog.BlogConfig'], null=True, blank=True), + keep_default=False) + + # Adding field 'AuthorEntriesPlugin.app_config' + db.add_column(u'djangocms_blog_authorentriesplugin', 'app_config', + self.gf('aldryn_apphooks_config.fields.AppHookConfigField')(to=orm['djangocms_blog.BlogConfig'], null=True, blank=True), + keep_default=False) + + + def backwards(self, orm): + # Removing unique constraint on 'BlogConfigTranslation', fields ['language_code', u'master'] + db.delete_unique(u'djangocms_blog_blogconfig_translation', ['language_code', u'master_id']) + + # Deleting model 'GenericBlogPlugin' + db.delete_table(u'djangocms_blog_genericblogplugin') + + # Deleting model 'BlogConfig' + db.delete_table(u'djangocms_blog_blogconfig') + + # Deleting model 'BlogConfigTranslation' + db.delete_table(u'djangocms_blog_blogconfig_translation') + + # Deleting field 'BlogCategory.app_config' + db.delete_column(u'djangocms_blog_blogcategory', 'app_config_id') + + # Deleting field 'Post.app_config' + db.delete_column(u'djangocms_blog_post', 'app_config_id') + + # Deleting field 'LatestPostsPlugin.app_config' + db.delete_column(u'djangocms_blog_latestpostsplugin', 'app_config_id') + + # Deleting field 'AuthorEntriesPlugin.app_config' + db.delete_column(u'djangocms_blog_authorentriesplugin', 'app_config_id') + + + models = { + u'auth.group': { + 'Meta': {'object_name': 'Group'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + u'auth.permission': { + 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + u'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'cms.cmsplugin': { + 'Meta': {'object_name': 'CMSPlugin'}, + 'changed_date': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'creation_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'depth': ('django.db.models.fields.PositiveIntegerField', [], {}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'language': ('django.db.models.fields.CharField', [], {'max_length': '15', 'db_index': 'True'}), + 'numchild': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cms.CMSPlugin']", 'null': 'True', 'blank': 'True'}), + 'path': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), + 'placeholder': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cms.Placeholder']", 'null': 'True'}), + 'plugin_type': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}), + 'position': ('django.db.models.fields.PositiveSmallIntegerField', [], {'null': 'True', 'blank': 'True'}) + }, + 'cms.placeholder': { + 'Meta': {'object_name': 'Placeholder'}, + 'default_width': ('django.db.models.fields.PositiveSmallIntegerField', [], {'null': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'slot': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}) + }, + u'cmsplugin_filer_image.thumbnailoption': { + 'Meta': {'ordering': "(u'width', u'height')", 'object_name': 'ThumbnailOption'}, + 'crop': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'height': ('django.db.models.fields.IntegerField', [], {}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'upscale': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'width': ('django.db.models.fields.IntegerField', [], {}) + }, + u'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + u'djangocms_blog.authorentriesplugin': { + 'Meta': {'object_name': 'AuthorEntriesPlugin'}, + 'app_config': ('aldryn_apphooks_config.fields.AppHookConfigField', [], {'to': u"orm['djangocms_blog.BlogConfig']", 'null': 'True', 'blank': 'True'}), + 'authors': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.User']", 'symmetrical': 'False'}), + u'cmsplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['cms.CMSPlugin']", 'unique': 'True', 'primary_key': 'True'}), + 'latest_posts': ('django.db.models.fields.IntegerField', [], {'default': '5'}) + }, + u'djangocms_blog.blogcategory': { + 'Meta': {'object_name': 'BlogCategory'}, + 'app_config': ('aldryn_apphooks_config.fields.AppHookConfigField', [], {'to': u"orm['djangocms_blog.BlogConfig']", 'null': 'True'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'date_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['djangocms_blog.BlogCategory']", 'null': 'True', 'blank': 'True'}) + }, + u'djangocms_blog.blogcategorytranslation': { + 'Meta': {'unique_together': "[(u'language_code', u'slug'), (u'language_code', u'master')]", 'object_name': 'BlogCategoryTranslation', 'db_table': "u'djangocms_blog_blogcategory_translation'"}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'language_code': ('django.db.models.fields.CharField', [], {'max_length': '15', 'db_index': 'True'}), + u'master': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'translations'", 'null': 'True', 'to': u"orm['djangocms_blog.BlogCategory']"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'blank': 'True'}) + }, + u'djangocms_blog.blogconfig': { + 'Meta': {'object_name': 'BlogConfig'}, + 'app_data': ('app_data.fields.AppDataField', [], {'default': "'{}'"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'namespace': ('django.db.models.fields.CharField', [], {'default': 'None', 'unique': 'True', 'max_length': '100'}), + 'type': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + u'djangocms_blog.blogconfigtranslation': { + 'Meta': {'unique_together': "[(u'language_code', u'master')]", 'object_name': 'BlogConfigTranslation', 'db_table': "u'djangocms_blog_blogconfig_translation'"}, + 'app_title': ('django.db.models.fields.CharField', [], {'max_length': '234'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'language_code': ('django.db.models.fields.CharField', [], {'max_length': '15', 'db_index': 'True'}), + u'master': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'translations'", 'null': 'True', 'to': u"orm['djangocms_blog.BlogConfig']"}) + }, + u'djangocms_blog.genericblogplugin': { + 'Meta': {'object_name': 'GenericBlogPlugin'}, + 'app_config': ('aldryn_apphooks_config.fields.AppHookConfigField', [], {'to': u"orm['djangocms_blog.BlogConfig']", 'null': 'True', 'blank': 'True'}), + u'cmsplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['cms.CMSPlugin']", 'unique': 'True', 'primary_key': 'True'}) + }, + u'djangocms_blog.latestpostsplugin': { + 'Meta': {'object_name': 'LatestPostsPlugin'}, + 'app_config': ('aldryn_apphooks_config.fields.AppHookConfigField', [], {'to': u"orm['djangocms_blog.BlogConfig']", 'null': 'True', 'blank': 'True'}), + 'categories': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['djangocms_blog.BlogCategory']", 'symmetrical': 'False', 'blank': 'True'}), + u'cmsplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['cms.CMSPlugin']", 'unique': 'True', 'primary_key': 'True'}), + 'latest_posts': ('django.db.models.fields.IntegerField', [], {'default': '5'}) + }, + u'djangocms_blog.post': { + 'Meta': {'ordering': "(u'-date_published', u'-date_created')", 'object_name': 'Post'}, + 'app_config': ('aldryn_apphooks_config.fields.AppHookConfigField', [], {'to': u"orm['djangocms_blog.BlogConfig']", 'null': 'True'}), + 'author': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "u'djangocms_blog_post_author'", 'null': 'True', 'to': u"orm['auth.User']"}), + 'categories': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "u'blog_posts'", 'symmetrical': 'False', 'to': u"orm['djangocms_blog.BlogCategory']"}), + 'content': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'post_content'", 'null': 'True', 'to': "orm['cms.Placeholder']"}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'date_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'date_published': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'date_published_end': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'enable_comments': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'main_image': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "u'djangocms_blog_post_image'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['filer.Image']"}), + 'main_image_full': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "u'djangocms_blog_post_full'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['cmsplugin_filer_image.ThumbnailOption']"}), + 'main_image_thumbnail': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "u'djangocms_blog_post_thumbnail'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['cmsplugin_filer_image.ThumbnailOption']"}), + 'publish': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'sites': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['sites.Site']", 'symmetrical': 'False', 'blank': 'True'}) + }, + u'djangocms_blog.posttranslation': { + 'Meta': {'unique_together': "[(u'language_code', u'slug'), (u'language_code', u'master')]", 'object_name': 'PostTranslation', 'db_table': "u'djangocms_blog_post_translation'"}, + 'abstract': ('djangocms_text_ckeditor.fields.HTMLField', [], {'default': "u''", 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'language_code': ('django.db.models.fields.CharField', [], {'max_length': '15', 'db_index': 'True'}), + u'master': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'translations'", 'null': 'True', 'to': u"orm['djangocms_blog.Post']"}), + 'meta_description': ('django.db.models.fields.TextField', [], {'default': "u''", 'blank': 'True'}), + 'meta_keywords': ('django.db.models.fields.TextField', [], {'default': "u''", 'blank': 'True'}), + 'meta_title': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}), + 'post_text': ('djangocms_text_ckeditor.fields.HTMLField', [], {'default': "u''", 'blank': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'blank': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + }, + u'filer.file': { + 'Meta': {'object_name': 'File'}, + '_file_size': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'file': ('django.db.models.fields.files.FileField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'folder': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "u'all_files'", 'null': 'True', 'to': u"orm['filer.Folder']"}), + 'has_all_mandatory_data': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'modified_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}), + 'original_filename': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "u'owned_files'", 'null': 'True', 'to': u"orm['auth.User']"}), + 'polymorphic_ctype': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'polymorphic_filer.file_set+'", 'null': 'True', 'to': u"orm['contenttypes.ContentType']"}), + 'sha1': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '40', 'blank': 'True'}), + 'uploaded_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}) + }, + u'filer.folder': { + 'Meta': {'ordering': "(u'name',)", 'unique_together': "((u'parent', u'name'),)", 'object_name': 'Folder'}, + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + u'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), + u'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), + 'modified_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "u'filer_owned_folders'", 'null': 'True', 'to': u"orm['auth.User']"}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "u'children'", 'null': 'True', 'to': u"orm['filer.Folder']"}), + u'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), + u'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), + 'uploaded_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}) + }, + 'filer.image': { + 'Meta': {'object_name': 'Image'}, + '_height': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + '_width': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'author': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'date_taken': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'default_alt_text': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'default_caption': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + u'file_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['filer.File']", 'unique': 'True', 'primary_key': 'True'}), + 'must_always_publish_author_credit': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'must_always_publish_copyright': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'related_url': ('django.db.models.fields.CharField', [], {'max_length': '250', 'null': 'True', 'blank': 'True'}), + 'subject_location': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '64', 'null': 'True', 'blank': 'True'}) + }, + u'sites.site': { + 'Meta': {'ordering': "(u'domain',)", 'object_name': 'Site', 'db_table': "u'django_site'"}, + 'domain': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + } + } + + complete_apps = ['djangocms_blog'] \ No newline at end of file diff --git a/djangocms_blog/south_migrations/0015_create_appconfig.py b/djangocms_blog/south_migrations/0015_create_appconfig.py new file mode 100644 index 0000000..7f8e270 --- /dev/null +++ b/djangocms_blog/south_migrations/0015_create_appconfig.py @@ -0,0 +1,236 @@ +# -*- coding: utf-8 -*- +from south.utils import datetime_utils as datetime +from south.db import db +from south.v2 import DataMigration +from django.db import models +from cms.models import Page +from cms.utils.i18n import get_language_list + + +class Migration(DataMigration): + + def forwards(self, orm): + BlogConfig = orm['djangocms_blog.BlogConfig'] + BlogConfigTranslation = orm['djangocms_blog.BlogConfigTranslation'] + Post = orm['djangocms_blog.Post'] + BlogCategory = orm['djangocms_blog.BlogCategory'] + GenericBlogPlugin = orm['djangocms_blog.GenericBlogPlugin'] + LatestPostsPlugin = orm['djangocms_blog.LatestPostsPlugin'] + AuthorEntriesPlugin = orm['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(self, orm): + # No need for backward data migration + pass + + + models = { + u'auth.group': { + 'Meta': {'object_name': 'Group'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + u'auth.permission': { + 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + u'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'cms.cmsplugin': { + 'Meta': {'object_name': 'CMSPlugin'}, + 'changed_date': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'creation_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'depth': ('django.db.models.fields.PositiveIntegerField', [], {}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'language': ('django.db.models.fields.CharField', [], {'max_length': '15', 'db_index': 'True'}), + 'numchild': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cms.CMSPlugin']", 'null': 'True', 'blank': 'True'}), + 'path': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), + 'placeholder': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cms.Placeholder']", 'null': 'True'}), + 'plugin_type': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}), + 'position': ('django.db.models.fields.PositiveSmallIntegerField', [], {'null': 'True', 'blank': 'True'}) + }, + 'cms.placeholder': { + 'Meta': {'object_name': 'Placeholder'}, + 'default_width': ('django.db.models.fields.PositiveSmallIntegerField', [], {'null': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'slot': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}) + }, + u'cmsplugin_filer_image.thumbnailoption': { + 'Meta': {'ordering': "(u'width', u'height')", 'object_name': 'ThumbnailOption'}, + 'crop': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'height': ('django.db.models.fields.IntegerField', [], {}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'upscale': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'width': ('django.db.models.fields.IntegerField', [], {}) + }, + u'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + u'djangocms_blog.authorentriesplugin': { + 'Meta': {'object_name': 'AuthorEntriesPlugin'}, + 'app_config': ('aldryn_apphooks_config.fields.AppHookConfigField', [], {'to': u"orm['djangocms_blog.BlogConfig']", 'null': 'True', 'blank': 'True'}), + 'authors': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.User']", 'symmetrical': 'False'}), + u'cmsplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['cms.CMSPlugin']", 'unique': 'True', 'primary_key': 'True'}), + 'latest_posts': ('django.db.models.fields.IntegerField', [], {'default': '5'}) + }, + u'djangocms_blog.blogcategory': { + 'Meta': {'object_name': 'BlogCategory'}, + 'app_config': ('aldryn_apphooks_config.fields.AppHookConfigField', [], {'to': u"orm['djangocms_blog.BlogConfig']", 'null': 'True'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'date_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['djangocms_blog.BlogCategory']", 'null': 'True', 'blank': 'True'}) + }, + u'djangocms_blog.blogcategorytranslation': { + 'Meta': {'unique_together': "[(u'language_code', u'slug'), (u'language_code', u'master')]", 'object_name': 'BlogCategoryTranslation', 'db_table': "u'djangocms_blog_blogcategory_translation'"}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'language_code': ('django.db.models.fields.CharField', [], {'max_length': '15', 'db_index': 'True'}), + u'master': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'translations'", 'null': 'True', 'to': u"orm['djangocms_blog.BlogCategory']"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'blank': 'True'}) + }, + u'djangocms_blog.blogconfig': { + 'Meta': {'object_name': 'BlogConfig'}, + 'app_data': ('app_data.fields.AppDataField', [], {'default': "'{}'"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'namespace': ('django.db.models.fields.CharField', [], {'default': 'None', 'unique': 'True', 'max_length': '100'}), + 'type': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + u'djangocms_blog.blogconfigtranslation': { + 'Meta': {'unique_together': "[(u'language_code', u'master')]", 'object_name': 'BlogConfigTranslation', 'db_table': "u'djangocms_blog_blogconfig_translation'"}, + 'app_title': ('django.db.models.fields.CharField', [], {'max_length': '234'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'language_code': ('django.db.models.fields.CharField', [], {'max_length': '15', 'db_index': 'True'}), + u'master': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'translations'", 'null': 'True', 'to': u"orm['djangocms_blog.BlogConfig']"}) + }, + u'djangocms_blog.genericblogplugin': { + 'Meta': {'object_name': 'GenericBlogPlugin'}, + 'app_config': ('aldryn_apphooks_config.fields.AppHookConfigField', [], {'to': u"orm['djangocms_blog.BlogConfig']", 'null': 'True', 'blank': 'True'}), + u'cmsplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['cms.CMSPlugin']", 'unique': 'True', 'primary_key': 'True'}) + }, + u'djangocms_blog.latestpostsplugin': { + 'Meta': {'object_name': 'LatestPostsPlugin'}, + 'app_config': ('aldryn_apphooks_config.fields.AppHookConfigField', [], {'to': u"orm['djangocms_blog.BlogConfig']", 'null': 'True', 'blank': 'True'}), + 'categories': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['djangocms_blog.BlogCategory']", 'symmetrical': 'False', 'blank': 'True'}), + u'cmsplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['cms.CMSPlugin']", 'unique': 'True', 'primary_key': 'True'}), + 'latest_posts': ('django.db.models.fields.IntegerField', [], {'default': '5'}) + }, + u'djangocms_blog.post': { + 'Meta': {'ordering': "(u'-date_published', u'-date_created')", 'object_name': 'Post'}, + 'app_config': ('aldryn_apphooks_config.fields.AppHookConfigField', [], {'to': u"orm['djangocms_blog.BlogConfig']", 'null': 'True'}), + 'author': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "u'djangocms_blog_post_author'", 'null': 'True', 'to': u"orm['auth.User']"}), + 'categories': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "u'blog_posts'", 'symmetrical': 'False', 'to': u"orm['djangocms_blog.BlogCategory']"}), + 'content': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'post_content'", 'null': 'True', 'to': "orm['cms.Placeholder']"}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'date_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'date_published': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'date_published_end': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'enable_comments': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'main_image': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "u'djangocms_blog_post_image'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['filer.Image']"}), + 'main_image_full': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "u'djangocms_blog_post_full'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['cmsplugin_filer_image.ThumbnailOption']"}), + 'main_image_thumbnail': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "u'djangocms_blog_post_thumbnail'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['cmsplugin_filer_image.ThumbnailOption']"}), + 'publish': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'sites': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['sites.Site']", 'symmetrical': 'False', 'blank': 'True'}) + }, + u'djangocms_blog.posttranslation': { + 'Meta': {'unique_together': "[(u'language_code', u'slug'), (u'language_code', u'master')]", 'object_name': 'PostTranslation', 'db_table': "u'djangocms_blog_post_translation'"}, + 'abstract': ('djangocms_text_ckeditor.fields.HTMLField', [], {'default': "u''", 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'language_code': ('django.db.models.fields.CharField', [], {'max_length': '15', 'db_index': 'True'}), + u'master': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'translations'", 'null': 'True', 'to': u"orm['djangocms_blog.Post']"}), + 'meta_description': ('django.db.models.fields.TextField', [], {'default': "u''", 'blank': 'True'}), + 'meta_keywords': ('django.db.models.fields.TextField', [], {'default': "u''", 'blank': 'True'}), + 'meta_title': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}), + 'post_text': ('djangocms_text_ckeditor.fields.HTMLField', [], {'default': "u''", 'blank': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'blank': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + }, + u'filer.file': { + 'Meta': {'object_name': 'File'}, + '_file_size': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'file': ('django.db.models.fields.files.FileField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'folder': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "u'all_files'", 'null': 'True', 'to': u"orm['filer.Folder']"}), + 'has_all_mandatory_data': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'modified_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}), + 'original_filename': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "u'owned_files'", 'null': 'True', 'to': u"orm['auth.User']"}), + 'polymorphic_ctype': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'polymorphic_filer.file_set+'", 'null': 'True', 'to': u"orm['contenttypes.ContentType']"}), + 'sha1': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '40', 'blank': 'True'}), + 'uploaded_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}) + }, + u'filer.folder': { + 'Meta': {'ordering': "(u'name',)", 'unique_together': "((u'parent', u'name'),)", 'object_name': 'Folder'}, + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + u'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), + u'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), + 'modified_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "u'filer_owned_folders'", 'null': 'True', 'to': u"orm['auth.User']"}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "u'children'", 'null': 'True', 'to': u"orm['filer.Folder']"}), + u'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), + u'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), + 'uploaded_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}) + }, + 'filer.image': { + 'Meta': {'object_name': 'Image'}, + '_height': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + '_width': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'author': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'date_taken': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'default_alt_text': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'default_caption': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + u'file_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['filer.File']", 'unique': 'True', 'primary_key': 'True'}), + 'must_always_publish_author_credit': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'must_always_publish_copyright': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'related_url': ('django.db.models.fields.CharField', [], {'max_length': '250', 'null': 'True', 'blank': 'True'}), + 'subject_location': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '64', 'null': 'True', 'blank': 'True'}) + }, + u'sites.site': { + 'Meta': {'ordering': "(u'domain',)", 'object_name': 'Site', 'db_table': "u'django_site'"}, + 'domain': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + } + } + + complete_apps = ['djangocms_blog'] \ No newline at end of file diff --git a/djangocms_blog/templates/djangocms_blog/post_detail.html b/djangocms_blog/templates/djangocms_blog/post_detail.html index 9313f11..1ca1fe3 100644 --- a/djangocms_blog/templates/djangocms_blog/post_detail.html +++ b/djangocms_blog/templates/djangocms_blog/post_detail.html @@ -21,7 +21,7 @@ {% endif %} {% endspaceless %} - {% if use_placeholder %} + {% if post.app_config.use_placeholder %}
{% render_placeholder post.content %}
{% else %}
{% render_model post "post_text" "post_text" %}
diff --git a/djangocms_blog/urls.py b/djangocms_blog/urls.py index b4896cb..0a76e27 100644 --- a/djangocms_blog/urls.py +++ b/djangocms_blog/urls.py @@ -3,12 +3,26 @@ 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 ( AuthorEntriesView, CategoryEntriesView, PostArchiveView, PostDetailView, PostListView, TaggedListView, ) + +def get_urls(): + urls = get_setting('PERMALINK_URLS') + details = [] + for urlconf in urls.values(): + details.append( + url(urlconf, PostDetailView.as_view(), name='post-detail'), + ) + return details + +detail_urls = get_urls() + urlpatterns = patterns( '', url(r'^$', @@ -19,8 +33,7 @@ urlpatterns = patterns( PostArchiveView.as_view(), name='posts-archive'), url(r'^(?P\d{4})/(?P\d{1,2})/$', PostArchiveView.as_view(), name='posts-archive'), - url(r'^(?P\d{4})/(?P\d{1,2})/(?P\d{1,2})/(?P\w[-\w]*)/$', - PostDetailView.as_view(), name='post-detail'), +) + detail_urls + [ url(r'^author/(?P[\w\.@+-]+)/$', AuthorEntriesView.as_view(), name='posts-author'), url(r'^category/(?P[\w\.@+-]+)/$', @@ -29,4 +42,6 @@ urlpatterns = patterns( TaggedListView.as_view(), name='posts-tagged'), url(r'^tag/(?P[-\w]+)/feed/$', TagFeed(), name='posts-tagged-feed'), -) +] + +BlogAppConfig.setup() diff --git a/djangocms_blog/views.py b/djangocms_blog/views.py index 31c64f5..5bb995f 100644 --- a/djangocms_blog/views.py +++ b/djangocms_blog/views.py @@ -1,38 +1,61 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import, print_function, unicode_literals +import os.path + +from aldryn_apphooks_config.mixins import AppConfigMixin from django.contrib.auth import get_user_model -from django.core.urlresolvers import resolve +from django.core.exceptions import ImproperlyConfigured +from django.core.urlresolvers import reverse from django.utils.timezone import now from django.utils.translation import get_language from django.views.generic import DetailView, ListView from parler.views import TranslatableSlugMixin, ViewUrlMixin -from .models import BLOG_CURRENT_POST_IDENTIFIER, BlogCategory, Post +from .models import BLOG_CURRENT_NAMESPACE, BLOG_CURRENT_POST_IDENTIFIER, BlogCategory, Post from .settings import get_setting User = get_user_model() -class BaseBlogView(ViewUrlMixin): +class BaseBlogView(AppConfigMixin, ViewUrlMixin): + + def get_view_url(self): + if not self.view_url_name: + raise ImproperlyConfigured( + 'Missing `view_url_name` attribute on {0}'.format(self.__class__.__name__) + ) + + return reverse( + self.view_url_name, + args=self.args, + kwargs=self.kwargs, + current_app=self.namespace + ) def get_queryset(self): language = get_language() - queryset = self.model._default_manager.all().active_translations(language_code=language) + queryset = self.model._default_manager.namespace( + self.namespace + ).active_translations( + language_code=language + ) if not getattr(self.request, 'toolbar', False) or not self.request.toolbar.edit_mode: queryset = queryset.published() + setattr(self.request, BLOG_CURRENT_NAMESPACE, self.config) return queryset - def render_to_response(self, context, **response_kwargs): - response_kwargs['current_app'] = resolve(self.request.path).namespace - return super(BaseBlogView, self).render_to_response(context, **response_kwargs) + def get_template_names(self): + if self.config.template_prefix: + return os.path.join(self.config.template_prefix, self.base_template_name) + else: + return os.path.join('djangocms_blog', self.base_template_name) class PostListView(BaseBlogView, ListView): model = Post context_object_name = 'post_list' - template_name = 'djangocms_blog/post_list.html' - paginate_by = get_setting('PAGINATION') + base_template_name = 'post_list.html' view_url_name = 'djangocms_blog:posts-latest' def get_context_data(self, **kwargs): @@ -40,11 +63,17 @@ class PostListView(BaseBlogView, ListView): context['TRUNCWORDS_COUNT'] = get_setting('POSTS_LIST_TRUNCWORDS_COUNT') return context + def get_paginate_by(self, queryset): + if self.config.paginate_by: + return self.config.paginate_by + else: + return get_setting('PAGINATION') + class PostDetailView(TranslatableSlugMixin, BaseBlogView, DetailView): model = Post context_object_name = 'post' - template_name = 'djangocms_blog/post_detail.html' + base_template_name = 'post_detail.html' slug_field = 'slug' view_url_name = 'djangocms_blog:post-detail' @@ -71,7 +100,7 @@ class PostDetailView(TranslatableSlugMixin, BaseBlogView, DetailView): class PostArchiveView(BaseBlogView, ListView): model = Post context_object_name = 'post_list' - template_name = 'djangocms_blog/post_list.html' + base_template_name = 'post_list.html' date_field = 'date_published' allow_empty = True allow_future = True @@ -99,7 +128,7 @@ class PostArchiveView(BaseBlogView, ListView): class TaggedListView(BaseBlogView, ListView): model = Post context_object_name = 'post_list' - template_name = 'djangocms_blog/post_list.html' + base_template_name = 'post_list.html' paginate_by = get_setting('PAGINATION') view_url_name = 'djangocms_blog:posts-tagged' @@ -118,7 +147,7 @@ class TaggedListView(BaseBlogView, ListView): class AuthorEntriesView(BaseBlogView, ListView): model = Post context_object_name = 'post_list' - template_name = 'djangocms_blog/post_list.html' + base_template_name = 'post_list.html' paginate_by = get_setting('PAGINATION') view_url_name = 'djangocms_blog:posts-authors' @@ -138,7 +167,7 @@ class AuthorEntriesView(BaseBlogView, ListView): class CategoryEntriesView(BaseBlogView, ListView): model = Post context_object_name = 'post_list' - template_name = 'djangocms_blog/post_list.html' + base_template_name = 'post_list.html' _category = None paginate_by = get_setting('PAGINATION') view_url_name = 'djangocms_blog:posts-category' diff --git a/setup.py b/setup.py index da66159..7ee4f3b 100755 --- a/setup.py +++ b/setup.py @@ -47,7 +47,8 @@ setup( 'djangocms-text-ckeditor', 'cmsplugin-filer', 'django-meta>=0.2', - 'django-meta-mixin>=0.1.1', + 'django-meta-mixin>=0.2.1', + 'aldryn-apphooks-config>=0.2.6', ], license='BSD', zip_safe=False, @@ -73,5 +74,6 @@ setup( 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', ], ) diff --git a/tests/__init__.py b/tests/__init__.py index 9cf872c..40a96af 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,151 +1 @@ # -*- coding: utf-8 -*- -""" -Tests for `djangocms_blog` module. -""" -from __future__ import absolute_import, print_function, unicode_literals - -from cmsplugin_filer_image.models import ThumbnailOption -from django.contrib.auth import get_user_model -from django.contrib.sites.models import Site -from django.utils.translation import activate -from djangocms_helper.base_test import BaseTestCase - -from djangocms_blog.models import BlogCategory, Post - -User = get_user_model() - - -def _get_cat_pk(lang, name): - return lambda: BlogCategory.objects.translated(lang, name=name).get().pk - - -class BaseTest(BaseTestCase): - """ - Base class with utility function - """ - category_1 = None - thumb_1 = None - thumb_2 = None - - _pages_data = ( - {'en': {'title': 'page one', 'template': 'page.html', 'publish': True}, - 'fr': {'title': 'page un', 'publish': True}, - 'it': {'title': 'pagina uno', 'publish': True}}, - {'en': {'title': 'page two', 'template': 'page.html', 'publish': True}, - 'fr': {'title': 'page deux', 'publish': True}, - 'it': {'title': 'pagina due', 'publish': True}}, - ) - - data = { - 'it': [ - {'title': u'Primo post', 'abstract': u'

prima riga

', - 'description': u'Questa è la descrizione', 'keywords': u'keyword1, keyword2', - 'text': u'Testo del post'}, - {'title': u'Secondo post', 'abstract': u'

prima riga del secondo post

', - 'description': u'Descrizione del secondo post', 'keywords': u'keyword3, keyword4', - 'text': u'Testo del secondo post'}, - {'title': u'Terzo post', 'abstract': u'

prima riga del terzo post

', - 'description': u'Descrizione del terzo post', 'keywords': u'keyword5, keyword6', - 'text': u'Testo del terzo post'}, - ], - 'en': [ - {'title': u'First post', 'abstract': u'

first line

', - 'description': u'This is the description', 'keywords': u'keyword1, keyword2', - 'text': u'Post text'}, - {'title': u'Second post', 'abstract': u'

second post first line

', - 'description': u'Second post description', 'keywords': u'keyword3, keyword4', - 'text': u'Second post text'}, - {'title': u'Third post', 'abstract': u'

third post first line

', - 'description': u'third post description', 'keywords': u'keyword5, keyword6', - 'text': u'Third post text'} - ] - } - - cat_data = { - 'it': [ - {'name': u'Fortissimo'}, - {'name': u'Pianississimo'}, - {'name': u'Mezzo'}, - {'name': u'Forte', 'parent_id': _get_cat_pk('it', 'Mezzo')}, - ], - 'en': [ - {'name': u'Very loud'}, - {'name': u'Very very silent'}, - {'name': u'Almost'}, - {'name': u'Loud', 'parent_id': _get_cat_pk('en', 'Almost')}, - {'name': u'Silent', 'parent_id': _get_cat_pk('en', 'Almost')}, - ] - } - - @classmethod - def setUpClass(cls): - super(BaseTest, cls).setUpClass() - cls.site_2 = Site.objects.create(domain='http://example2.com', name='example 2') - - def setUp(self): - activate('en') - super(BaseTest, self).setUp() - self.category_1 = BlogCategory.objects.create(name=u'category 1') - self.category_1.set_current_language('it', initialize=True) - self.category_1.name = u'categoria 1' - self.category_1.save() - self.thumb_1 = ThumbnailOption.objects.create( - name='base', width=100, height=100, crop=True, upscale=False - ) - self.thumb_2 = ThumbnailOption.objects.create( - name='main', width=200, height=200, crop=False, upscale=False - ) - self.img = self.create_filer_image_object() - - def tearDown(self): - for post in Post.objects.all(): - post.delete() - super(BaseTest, self).tearDown() - - def _get_category(self, data, category=None, lang='en'): - for k, v in data.items(): - if hasattr(v, '__call__'): - data[k] = v() - if not category: - category = BlogCategory.objects.create(**data) - else: - category.set_current_language(lang) - for attr, val in data.items(): - setattr(category, attr, val) - category.save() - return category - - def _get_post(self, data, post=None, lang='en', sites=None): - if not post: - post_data = { - 'author': self.user, - 'title': data['title'], - 'abstract': data['abstract'], - 'meta_description': data['description'], - 'meta_keywords': data['keywords'], - } - post = Post.objects.create(**post_data) - else: - post.set_current_language(lang) - post.title = data['title'] - post.abstract = data['abstract'] - post.meta_description = data['description'] - post.meta_keywords = data['keywords'] - post.save() - post.categories.add(self.category_1) - if sites: - for site in sites: - post.sites.add(site) - return post - - def get_posts(self, sites=None): - post1 = self._get_post(self.data['en'][0], sites=sites) - post1 = self._get_post(self.data['it'][0], post1, 'it') - post1.publish = True - post1.main_image = self.img - post1.save() - post2 = self._get_post(self.data['en'][1], sites=sites) - post2 = self._get_post(self.data['it'][1], post2, 'it') - post2.main_image = self.img - post2.save() - return post1, post2 diff --git a/tests/base.py b/tests/base.py new file mode 100644 index 0000000..fc96f81 --- /dev/null +++ b/tests/base.py @@ -0,0 +1,185 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import, print_function, unicode_literals + +from copy import deepcopy + +from cmsplugin_filer_image.models import ThumbnailOption +from django.contrib.auth import get_user_model +from django.contrib.sites.models import Site +from djangocms_helper.base_test import BaseTestCase +from parler.utils.context import smart_override + +from djangocms_blog.cms_appconfig import BlogConfig +from djangocms_blog.models import BlogCategory, Post + +User = get_user_model() + + +def _get_cat_pk(lang, name): + return lambda: BlogCategory.objects.translated(lang, name=name).get().pk + + +class BaseTest(BaseTestCase): + """ + Base class with utility function + """ + category_1 = None + thumb_1 = None + thumb_2 = None + + _pages_data = ( + {'en': {'title': 'page one', 'template': 'blog.html', 'publish': True}, + 'fr': {'title': 'page un', 'publish': True}, + 'it': {'title': 'pagina uno', 'publish': True}}, + {'en': {'title': 'page two', 'template': 'blog.html', 'publish': True, + 'apphook': 'BlogApp', 'apphook_namespace': 'sample_app'}, + 'fr': {'title': 'page deux', 'publish': True}, + 'it': {'title': 'pagina due', 'publish': True}}, + {'en': {'title': 'page three', 'template': 'blog.html', 'publish': True, + 'apphook': 'BlogApp', 'apphook_namespace': 'sample_app2'}, + 'fr': {'title': 'page trois', 'publish': True}, + 'it': {'title': 'pagina tre', 'publish': True}}, + ) + + _post_data = ( + {'en': {'title': 'First post', 'abstract': '

first line

', + 'description': 'This is the description', 'keywords': 'keyword1, keyword2', + 'text': 'Post text', 'app_config': 'sample_app', 'publish': True}, + 'it': {'title': 'Primo post', 'abstract': '

prima riga

', + 'description': 'Questa è la descrizione', 'keywords': 'keyword1, keyword2', + 'text': 'Testo del post'}, + }, + {'en': {'title': 'Second post', 'abstract': '

second post first line

', + 'description': 'Second post description', 'keywords': 'keyword3, keyword4', + 'text': 'Second post text', 'app_config': 'sample_app', 'publish': False}, + 'it': {'title': 'Secondo post', 'abstract': '

prima riga del secondo post

', + 'description': 'Descrizione del secondo post', 'keywords': 'keyword3, keyword4', + 'text': 'Testo del secondo post', 'app_config': 'sample_app'}, + }, + {'en': {'title': 'Third post', 'abstract': '

third post first line

', + 'description': 'third post description', 'keywords': 'keyword5, keyword6', + 'text': 'Third post text', 'app_config': 'sample_app', 'publish': False}, + 'it': {'title': 'Terzo post', 'abstract': '

prima riga del terzo post

', + 'description': 'Descrizione del terzo post', 'keywords': 'keyword5, keyword6', + 'text': 'Testo del terzo post'}, + }, + {'en': {'title': 'Different appconfig', 'abstract': '

Different appconfig first line

', + 'description': 'Different appconfig description', 'keywords': 'keyword5, keyword6', + 'text': 'Different appconfig text', 'app_config': 'sample_app2', 'publish': True}, + 'it': {'title': 'Altro appconfig', 'abstract': '

prima riga del Altro appconfig

', + 'description': 'Descrizione Altro appconfig', 'keywords': 'keyword5, keyword6', + 'text': 'Testo del Altro appconfig'}, + }, + ) + + _categories_data = ( + {'en': {'name': 'Very loud', 'app_config': 'sample_app'}, + 'it': {'name': 'Fortissimo'}, + }, + {'en': {'name': 'Very very silent', 'app_config': 'sample_app'}, + 'it': {'name': 'Pianississimo'}, + }, + {'en': {'name': 'Almost', 'app_config': 'sample_app'}, + 'it': {'name': 'Mezzo'}, + }, + {'en': {'name': 'Loud', 'parent_id': _get_cat_pk('en', 'Almost'), 'app_config': 'sample_app'}, + 'it': {'name': 'Forte', 'parent_id': _get_cat_pk('it', 'Mezzo')}, + }, + {'en': {'name': 'Silent', 'parent_id': _get_cat_pk('en', 'Almost'), 'app_config': 'sample_app'}, + }, + {'en': {'name': 'Drums', 'app_config': 'sample_app2'}, + 'it': {'name': 'Tamburi'}, + }, + {'en': {'name': 'Guitars', 'app_config': 'sample_app2'}, + 'it': {'name': 'Chitarre'}, + }, + ) + + @classmethod + def setUpClass(cls): + super(BaseTest, cls).setUpClass() + cls.thumb_1 = ThumbnailOption.objects.create( + name='base', width=100, height=100, crop=True, upscale=False + ) + cls.thumb_2 = ThumbnailOption.objects.create( + name='main', width=200, height=200, crop=False, upscale=False + ) + cls.app_config_1 = BlogConfig.objects.create( + namespace='sample_app', app_title='app1' + ) + cls.app_config_2 = BlogConfig.objects.create( + namespace='sample_app2', app_title='app2' + ) + cls.app_config_1.app_data.config.paginate_by = 1 + cls.app_config_1.save() + cls.app_config_2.app_data.config.paginate_by = 2 + cls.app_config_2.save() + cls.app_configs = { + 'sample_app': cls.app_config_1, + 'sample_app2': cls.app_config_2, + } + cls.category_1 = BlogCategory.objects.create(name='category 1', app_config=cls.app_config_1) + cls.category_1.set_current_language('it', initialize=True) + cls.category_1.name = 'categoria 1' + cls.category_1.save() + cls.site_2 = Site.objects.create(domain='http://example2.com', name='example 2') + + @classmethod + def tearDownClass(cls): + super(BaseTest, cls).tearDownClass() + BlogConfig.objects.all().delete() + BlogCategory.objects.all().delete() + ThumbnailOption.objects.all().delete() + + def _get_category(self, data, category=None, lang='en'): + data = deepcopy(data) + for k, v in data.items(): + if hasattr(v, '__call__'): + data[k] = v() + if not category: + with smart_override(lang): + data['app_config'] = self.app_configs[data['app_config']] + category = BlogCategory.objects.create(**data) + else: + category.set_current_language(lang, initialize=True) + for attr, val in data.items(): + setattr(category, attr, val) + category.save() + return category + + def _get_post(self, data, post=None, lang='en', sites=None): + if not post: + post_data = { + 'author': self.user, + 'title': data['title'], + 'abstract': data['abstract'], + 'meta_description': data['description'], + 'meta_keywords': data['keywords'], + 'app_config': self.app_configs[data['app_config']] + } + post = Post.objects.create(**post_data) + else: + post.create_translation( + lang, + title=data['title'], + abstract=data['abstract'], + meta_description=data['description'], + meta_keywords=data['keywords'] + ) + post = self.reload_model(post) + post.categories.add(self.category_1) + if sites: + for site in sites: + post.sites.add(site) + return post + + def get_posts(self, sites=None): + posts = [] + for post in self._post_data: + post1 = self._get_post(post['en'], sites=sites) + post1 = self._get_post(post['it'], post=post1, lang='it') + post1.publish = post['en']['publish'] + post1.main_image = self.create_filer_image_object() + post1.save() + posts.append(post1) + return posts diff --git a/tests/test_menu.py b/tests/test_menu.py index 84d334c..36380ac 100644 --- a/tests/test_menu.py +++ b/tests/test_menu.py @@ -1,25 +1,30 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import, print_function, unicode_literals -import copy - +from aldryn_apphooks_config.utils import get_app_instance +from django.core.cache import cache from django.utils.translation import activate from menus.menu_pool import menu_pool -from parler.utils.context import switch_language +from parler.utils.context import smart_override, switch_language +from djangocms_blog.settings import ( + MENU_TYPE_CATEGORIES, MENU_TYPE_COMPLETE, MENU_TYPE_NONE, MENU_TYPE_POSTS, +) from djangocms_blog.views import CategoryEntriesView, PostDetailView -from . import BaseTest +from .base import BaseTest class MenuTest(BaseTest): + cats = [] + def setUp(self): super(MenuTest, self).setUp() self.cats = [self.category_1] - for i, cat_data in enumerate(self.cat_data['en']): - cat = self._get_category(cat_data) - if i < len(self.cat_data['it']): - cat = self._get_category(self.cat_data['it'][i], cat, 'it') + for i, lang_data in enumerate(self._categories_data): + cat = self._get_category(lang_data['en']) + if 'it' in lang_data: + cat = self._get_category(lang_data['it'], cat, 'it') self.cats.append(cat) activate('en') @@ -32,54 +37,111 @@ class MenuTest(BaseTest): """ Tests if all categories are present in the menu """ + self.get_posts() + self.get_pages() + for lang in ('en', 'it'): - request = self.get_page_request(None, self.user, - r'/%s/blog/' % lang, edit=False) - activate(lang) - nodes = menu_pool.get_nodes(request, namespace='BlogCategoryMenu') - nodes_copy = copy.deepcopy(nodes) - for cat in self.cats: - if not cat.has_translation(lang): - continue - with switch_language(cat, lang): - # find in node list - found = None - for node in nodes_copy: - if node.url == cat.get_absolute_url(): - found = node - break - self.assertIsNotNone(found) - nodes_copy.remove(found) - self.assertEqual(node.id, cat.id) - self.assertEqual(node.title, cat.name) - # check that all categories were found in menu - self.assertEqual(len(nodes_copy), 0) + request = self.get_page_request(None, self.user, r'/%s/page-two/' % lang) + with smart_override(lang): + nodes = menu_pool.get_nodes(request, namespace='BlogCategoryMenu') + nodes_url = set([node.url for node in nodes]) + cats_url = set([cat.get_absolute_url() for cat in self.cats if cat.has_translation(lang)]) + self.assertTrue(cats_url.issubset(nodes_url)) + + def test_menu_options(self): + """ + Tests menu structure based on menu_structure configuration + """ + posts = self.get_posts() + self.get_pages() + + cats_url = {} + posts_url = {} + + languages = ('en', 'it') + + for lang in languages: + with smart_override(lang): + cats_url[lang] = set([cat.get_absolute_url() for cat in self.cats if cat.has_translation(lang)]) + posts_url[lang] = set([post.get_absolute_url() for post in posts if post.has_translation(lang) and post.app_config == self.app_config_1]) + + # No item in the menu + self.app_config_1.app_data.config.menu_structure = MENU_TYPE_NONE + self.app_config_1.save() + cache.clear() + for lang in languages: + request = self.get_page_request(None, self.user, r'/%s/page-two/' % lang) + with smart_override(lang): + nodes = menu_pool.get_nodes(request, namespace='BlogCategoryMenu') + nodes_url = set([node.url for node in nodes]) + self.assertFalse(cats_url[lang].issubset(nodes_url)) + self.assertFalse(posts_url[lang].issubset(nodes_url)) + + # Only posts in the menu + self.app_config_1.app_data.config.menu_structure = MENU_TYPE_POSTS + self.app_config_1.save() + cache.clear() + for lang in languages: + request = self.get_page_request(None, self.user, r'/%s/page-two/' % lang) + with smart_override(lang): + nodes = menu_pool.get_nodes(request, namespace='BlogCategoryMenu') + nodes_url = set([node.url for node in nodes]) + self.assertFalse(cats_url[lang].issubset(nodes_url)) + self.assertTrue(posts_url[lang].issubset(nodes_url)) + + # Only categories in the menu + self.app_config_1.app_data.config.menu_structure = MENU_TYPE_CATEGORIES + self.app_config_1.save() + cache.clear() + for lang in languages: + request = self.get_page_request(None, self.user, r'/%s/page-two/' % lang) + with smart_override(lang): + nodes = menu_pool.get_nodes(request, namespace='BlogCategoryMenu') + nodes_url = set([node.url for node in nodes]) + self.assertTrue(cats_url[lang].issubset(nodes_url)) + self.assertFalse(posts_url[lang].issubset(nodes_url)) + + # Both types in the menu + self.app_config_1.app_data.config.menu_structure = MENU_TYPE_COMPLETE + self.app_config_1.save() + cache.clear() + for lang in languages: + request = self.get_page_request(None, self.user, r'/%s/page-two/' % lang) + with smart_override(lang): + nodes = menu_pool.get_nodes(request, namespace='BlogCategoryMenu') + nodes_url = set([node.url for node in nodes]) + self.assertTrue(cats_url[lang].issubset(nodes_url)) + self.assertTrue(posts_url[lang].issubset(nodes_url)) def test_modifier(self): """ Tests if correct category is selected in the menu according to context (view object) """ - post1, post2 = self.get_posts() + posts = self.get_posts() + pages = self.get_pages() + tests = ( # view class, view kwarg, view object, category - (PostDetailView, 'slug', post1, post1.categories.first()), + (PostDetailView, 'slug', posts[0], posts[0].categories.first()), (CategoryEntriesView, 'category', self.cats[2], self.cats[2]) ) for view_cls, kwarg, obj, cat in tests: - request = self.get_page_request(None, self.user, r'/en/blog/', edit=False) - activate('en') - with switch_language(obj, 'en'): - view_obj = view_cls() - view_obj.request = request - view_obj.kwargs = {kwarg: obj.slug} - view_obj.get(request) - # check if selected menu node points to cat - nodes = menu_pool.get_nodes(request, namespace='BlogCategoryMenu') - found = False - for node in nodes: - if node.selected: - self.assertEqual(node.url, cat.get_absolute_url()) - found = True - break - self.assertTrue(found) + 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) + # check if selected menu node points to cat + nodes = menu_pool.get_nodes(request, namespace='BlogCategoryMenu') + found = False + for node in nodes: + if node.selected: + self.assertEqual(node.url, obj.get_absolute_url()) + found = True + break + self.assertTrue(found) diff --git a/tests/test_models.py b/tests/test_models.py index 771ac94..77cd4f8 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import, print_function, unicode_literals +import re from copy import deepcopy import parler @@ -12,37 +13,109 @@ from django.contrib.auth.models import AnonymousUser from django.contrib.messages.middleware import MessageMiddleware from django.contrib.sites.models import Site from django.core.urlresolvers import reverse +from django.utils.encoding import force_text +from django.utils.html import strip_tags from django.utils.timezone import now from django.utils.translation import get_language, override from djangocms_helper.utils import CMS_30 from taggit.models import Tag -from djangocms_blog.models import Post +from djangocms_blog.cms_appconfig import BlogConfig, BlogConfigForm +from djangocms_blog.models import BlogCategory, Post from djangocms_blog.settings import get_setting -from . import BaseTest +from .base import BaseTest class AdminTest(BaseTest): - def test_admin_fieldsets(self): - request = self.get_page_request('/', self.user_staff, r'/en/blog/', edit=False) + def setUp(self): + super(AdminTest, self).setUp() + admin.autodiscover() + + def test_admin_post_views(self): post_admin = admin.site._registry[Post] + request = self.get_page_request('/', self.user, r'/en/blog/', edit=False) - with self.settings(BLOG_USE_PLACEHOLDER=True): - fsets = post_admin.get_fieldsets(request) - self.assertFalse('post_text' in fsets[0][1]['fields']) + post = self._get_post(self._post_data[0]['en']) + post = self._get_post(self._post_data[0]['it'], post, 'it') - with self.settings(BLOG_USE_PLACEHOLDER=False): - fsets = post_admin.get_fieldsets(request) - self.assertTrue('post_text' in fsets[0][1]['fields']) + # Add view only contains the apphook selection widget + response = post_admin.add_view(request) + self.assertNotContains(response, 'Blog / sample_app' % self.app_config_1.pk) - with self.settings(BLOG_USE_ABSTRACT=True): - fsets = post_admin.get_fieldsets(request) - self.assertTrue('abstract' in fsets[0][1]['fields']) - with self.settings(BLOG_USE_ABSTRACT=False): - fsets = post_admin.get_fieldsets(request) - self.assertFalse('abstract' in fsets[0][1]['fields']) + # Changeview is 'normal' + response = post_admin.change_view(request, str(post.pk)) + self.assertContains(response, '') + self.assertContains(response, '' % self.app_config_1.pk) + + def test_admin_blogconfig_views(self): + post_admin = admin.site._registry[BlogConfig] + request = self.get_page_request('/', self.user, r'/en/blog/', edit=False) + + # Add view only has an empty form - no type + response = post_admin.add_view(request) + self.assertNotContains(response, 'djangocms_blog.cms_appconfig.BlogConfig') + self.assertContains(response, '') + + # Changeview is 'normal', with a few preselected items + response = post_admin.change_view(request, str(self.app_config_1.pk)) + self.assertContains(response, 'djangocms_blog.cms_appconfig.BlogConfig') + self.assertContains(response, '') + # check that all the form fields are visible in the admin + for fieldname in BlogConfigForm.base_fields: + self.assertContains(response, 'id="id_config-%s"' % fieldname) + self.assertContains(response, '') + self.assertContains(response, '') + + def test_admin_category_views(self): + post_admin = admin.site._registry[BlogCategory] + request = self.get_page_request('/', self.user, r'/en/blog/', edit=False) + + # Add view only has an empty form - no type + response = post_admin.add_view(request) + self.assertNotContains(response, '') + self.assertContains(response, '' % self.app_config_1.pk) + + # Changeview is 'normal', with a few preselected items + response = post_admin.change_view(request, str(self.category_1.pk)) + # response.render() + # print(response.content.decode('utf-8')) + self.assertContains(response, '') + self.assertContains(response, '' % self.app_config_1.pk) + + def test_admin_fieldsets(self): + post_admin = admin.site._registry[Post] + request = self.get_page_request('/', self.user_staff, r'/en/blog/?app_config=%s' % self.app_config_1.pk, edit=False) + + # Use placeholder + self.app_config_1.app_data.config.use_placeholder = True + self.app_config_1.save() + fsets = post_admin.get_fieldsets(request) + self.assertFalse('post_text' in fsets[0][1]['fields']) + + self.app_config_1.app_data.config.use_placeholder = False + self.app_config_1.save() + fsets = post_admin.get_fieldsets(request) + self.assertTrue('post_text' in fsets[0][1]['fields']) + + self.app_config_1.app_data.config.use_placeholder = True + self.app_config_1.save() + + # Use abstract + self.app_config_1.app_data.config.use_abstract = True + self.app_config_1.save() + fsets = post_admin.get_fieldsets(request) + self.assertTrue('abstract' in fsets[0][1]['fields']) + + self.app_config_1.app_data.config.use_abstract = False + self.app_config_1.save() + fsets = post_admin.get_fieldsets(request) + self.assertFalse('abstract' in fsets[0][1]['fields']) + + self.app_config_1.app_data.config.use_abstract = True + self.app_config_1.save() with self.settings(BLOG_MULTISITE=True): fsets = post_admin.get_fieldsets(request) @@ -51,49 +124,65 @@ class AdminTest(BaseTest): fsets = post_admin.get_fieldsets(request) self.assertFalse('sites' in fsets[1][1]['fields'][0]) - request = self.get_page_request('/', self.user, r'/en/blog/', edit=False) + request = self.get_page_request('/', self.user, r'/en/blog/?app_config=%s' % self.app_config_1.pk, edit=False) fsets = post_admin.get_fieldsets(request) self.assertTrue('author' in fsets[1][1]['fields'][0]) + with self.login_user_context(self.user): + request = self.get_request('/', 'en', user=self.user, path=r'/en/blog/?app_config=%s' % self.app_config_1.pk) + msg_mid = MessageMiddleware() + msg_mid.process_request(request) + post_admin = admin.site._registry[Post] + response = post_admin.add_view(request) + self.assertContains(response, '' % ( + self.category_1.pk, self.category_1.safe_translation_getter('name', language_code='en') + )) + def test_admin_auto_author(self): - page1, page2 = self.get_pages() - data = deepcopy(self.data['en'][0]) + pages = self.get_pages() + data = deepcopy(self._post_data[0]['en']) with self.login_user_context(self.user): - with self.settings(BLOG_AUTHOR_DEFAULT=True): - data['date_published_0'] = now().strftime('%Y-%m-%d') - data['date_published_1'] = now().strftime('%H:%M:%S') - data['categories'] = self.category_1.pk - request = self.post_request(page1, 'en', user=self.user, data=data, path='/en/?edit_fields=post_text') - msg_mid = MessageMiddleware() - msg_mid.process_request(request) - post_admin = admin.site._registry[Post] - response = post_admin.add_view(request) - self.assertEqual(response.status_code, 302) - self.assertEqual(Post.objects.count(), 1) - self.assertEqual(Post.objects.get(translations__slug='first-post').author_id, - request.user.pk) + self.app_config_1.app_data.config.set_author = True + self.app_config_1.save() + data['date_published_0'] = now().strftime('%Y-%m-%d') + data['date_published_1'] = now().strftime('%H:%M:%S') + data['categories'] = self.category_1.pk + data['app_config'] = self.app_config_1.pk + request = self.post_request(pages[0], 'en', user=self.user, data=data, path=r'/en/blog/?app_config=%s' % self.app_config_1.pk) + msg_mid = MessageMiddleware() + msg_mid.process_request(request) + post_admin = admin.site._registry[Post] + response = post_admin.add_view(request) + self.assertEqual(response.status_code, 302) + self.assertEqual(Post.objects.count(), 1) + self.assertEqual(Post.objects.get(translations__slug='first-post').author_id, request.user.pk) - with self.settings(BLOG_AUTHOR_DEFAULT=False): - data = deepcopy(self.data['en'][1]) - data['date_published_0'] = now().strftime('%Y-%m-%d') - data['date_published_1'] = now().strftime('%H:%M:%S') - data['categories'] = self.category_1.pk - request = self.post_request(page1, 'en', user=self.user, data=data, path='/en/?edit_fields=post_text') - msg_mid = MessageMiddleware() - msg_mid.process_request(request) - post_admin = admin.site._registry[Post] - response = post_admin.add_view(request) - self.assertEqual(response.status_code, 302) - self.assertEqual(Post.objects.count(), 2) - self.assertEqual(Post.objects.get(translations__slug='second-post').author_id, None) + self.app_config_1.app_data.config.set_author = False + self.app_config_1.save() + data = deepcopy(self._post_data[1]['en']) + data['date_published_0'] = now().strftime('%Y-%m-%d') + data['date_published_1'] = now().strftime('%H:%M:%S') + data['categories'] = self.category_1.pk + data['app_config'] = self.app_config_1.pk + request = self.post_request(pages[0], 'en', user=self.user, data=data, path=r'/en/blog/?app_config=%s' % self.app_config_1.pk) + msg_mid = MessageMiddleware() + msg_mid.process_request(request) + post_admin = admin.site._registry[Post] + response = post_admin.add_view(request) + self.assertEqual(response.status_code, 302) + self.assertEqual(Post.objects.count(), 2) + self.assertEqual(Post.objects.get(translations__slug='second-post').author_id, None) with self.settings(BLOG_AUTHOR_DEFAULT='staff'): - data = deepcopy(self.data['en'][2]) + self.app_config_1.app_data.config.set_author = True + self.app_config_1.save() + data = deepcopy(self._post_data[2]['en']) data['date_published_0'] = now().strftime('%Y-%m-%d') data['date_published_1'] = now().strftime('%H:%M:%S') data['categories'] = self.category_1.pk - request = self.post_request(page1, 'en', user=self.user, data=data, path='/en/?edit_fields=post_text') + data['app_config'] = self.app_config_1.pk + request = self.post_request(pages[0], 'en', user=self.user, data=data, path=r'/en/blog/?app_config=%s' % self.app_config_1.pk) msg_mid = MessageMiddleware() msg_mid.process_request(request) post_admin = admin.site._registry[Post] @@ -103,13 +192,13 @@ class AdminTest(BaseTest): self.assertEqual(Post.objects.get(translations__slug='third-post').author.username, 'staff') def test_admin_post_text(self): - page1, page2 = self.get_pages() - post = self._get_post(self.data['en'][0]) + pages = self.get_pages() + post = self._get_post(self._post_data[0]['en']) with self.login_user_context(self.user): with self.settings(BLOG_USE_PLACEHOLDER=False): - data = {'post_text': 'ehi text'} - request = self.post_request(page1, 'en', user=self.user, data=data, path='/en/?edit_fields=post_text') + data = {'post_text': 'ehi text', 'title': 'some title'} + request = self.post_request(pages[0], 'en', user=self.user, data=data, path='/en/?edit_fields=post_text') msg_mid = MessageMiddleware() msg_mid.process_request(request) post_admin = admin.site._registry[Post] @@ -122,9 +211,14 @@ class AdminTest(BaseTest): class ModelsTest(BaseTest): def test_model_attributes(self): - post = self._get_post(self.data['en'][0]) - post = self._get_post(self.data['it'][0], post, 'it') - post.main_image = self.img + self.get_pages() + + self.app_config_1.app_data.config.gplus_author = 'RandomJoe' + self.app_config_1.save() + + post = self._get_post(self._post_data[0]['en']) + post = self._get_post(self._post_data[0]['it'], post, 'it') + post.main_image = self.create_filer_image_object() post.save() post.set_current_language('en') meta_en = post.as_meta() @@ -134,6 +228,14 @@ class ModelsTest(BaseTest): self.assertEqual(meta_en.description, post.meta_description) self.assertEqual(meta_en.keywords, post.meta_keywords.split(',')) self.assertEqual(meta_en.published_time, post.date_published) + self.assertEqual(meta_en.locale, 'en') + self.assertEqual(meta_en.twitter_site, '') + self.assertEqual(meta_en.twitter_author, '') + self.assertEqual(meta_en.twitter_type, 'summary') + self.assertEqual(meta_en.gplus_author, 'RandomJoe') + self.assertEqual(meta_en.gplus_type, 'Blog') + self.assertEqual(meta_en.og_type, 'Article') + self.assertEqual(meta_en.facebook_app_id, None) post.set_current_language('it') meta_it = post.as_meta() self.assertEqual(meta_it.title, post.title) @@ -147,7 +249,11 @@ class ModelsTest(BaseTest): 'month': '%02d' % post.date_published.month, 'day': '%02d' % post.date_published.day, 'slug': post.safe_translation_getter('slug', any_language=get_language())} - url_en = reverse('djangocms_blog:post-detail', kwargs=kwargs) + url_en = reverse( + '%s:post-detail' % self.app_config_1.namespace, + kwargs=kwargs, + current_app=self.app_config_1 + ) self.assertEqual(url_en, post.get_absolute_url()) with override('it'): @@ -156,7 +262,11 @@ class ModelsTest(BaseTest): 'month': '%02d' % post.date_published.month, 'day': '%02d' % post.date_published.day, 'slug': post.safe_translation_getter('slug', any_language=get_language())} - url_it = reverse('djangocms_blog:post-detail', kwargs=kwargs) + url_it = reverse( + '%s:post-detail' % self.app_config_1.namespace, + kwargs=kwargs, + current_app=self.app_config_1 + ) self.assertEqual(url_it, post.get_absolute_url()) self.assertNotEqual(url_it, url_en) @@ -185,9 +295,47 @@ class ModelsTest(BaseTest): post.meta_title = 'meta title' self.assertEqual(post.get_title(), 'meta title') + def test_urls(self): + self.get_pages() + post = self._get_post(self._post_data[0]['en']) + post = self._get_post(self._post_data[0]['it'], post, 'it') + + # default + self.assertTrue(re.match(r'.*\d{4}/\d{2}/\d{2}/%s/$' % post.slug, post.get_absolute_url())) + + # full date + self.app_config_1.app_data.config.url_patterns = 'full_date' + self.app_config_1.save() + post.app_config = self.app_config_1 + self.assertTrue(re.match(r'.*\d{4}/\d{2}/\d{2}/%s/$' % post.slug, post.get_absolute_url())) + + # short date + self.app_config_1.app_data.config.url_patterns = 'short_date' + self.app_config_1.save() + post.app_config = self.app_config_1 + self.assertTrue(re.match(r'.*\d{4}/\d{2}/%s/$' % post.slug, post.get_absolute_url())) + + # category + self.app_config_1.app_data.config.url_patterns = 'category' + self.app_config_1.save() + post.app_config = self.app_config_1 + self.assertTrue(re.match(r'.*/\w[-\w]*/%s/$' % post.slug, post.get_absolute_url())) + self.assertTrue( + re.match( + r'.*%s/%s/$' % (post.categories.first().slug, post.slug), + post.get_absolute_url() + ) + ) + + # slug only + self.app_config_1.app_data.config.url_patterns = 'category' + self.app_config_1.save() + post.app_config = self.app_config_1 + self.assertTrue(re.match(r'.*/%s/$' % post.slug, post.get_absolute_url())) + def test_manager(self): - post1 = self._get_post(self.data['en'][0]) - post2 = self._get_post(self.data['en'][1]) + post1 = self._get_post(self._post_data[0]['en']) + post2 = self._get_post(self._post_data[1]['en']) # default queryset, published and unpublished posts months = Post.objects.get_months() @@ -211,6 +359,7 @@ class ModelsTest(BaseTest): post2.save() self.assertEqual(len(Post.objects.available()), 2) self.assertEqual(len(Post.objects.published()), 1) + self.assertEqual(len(Post.objects.published_future()), 2) self.assertEqual(len(Post.objects.archived()), 0) # If post is published but end publishing date is in the past @@ -222,7 +371,7 @@ class ModelsTest(BaseTest): self.assertEqual(len(Post.objects.archived()), 1) # counting with language fallback enabled - self._get_post(self.data['it'][0], post1, 'it') + self._get_post(self._post_data[0]['it'], post1, 'it') self.assertEqual(len(Post.objects.filter_by_language('it')), 2) # No fallback @@ -235,8 +384,8 @@ class ModelsTest(BaseTest): parler.appsettings.PARLER_LANGUAGES[Site.objects.get_current().pk][index]['hide_untranslated'] = False def test_tag_cloud(self): - post1 = self._get_post(self.data['en'][0]) - post2 = self._get_post(self.data['en'][1]) + post1 = self._get_post(self._post_data[0]['en']) + post2 = self._get_post(self._post_data[1]['en']) post1.tags.add('tag 1', 'tag 2', 'tag 3', 'tag 4') post1.save() post2.tags.add('tag 6', 'tag 2', 'tag 5', 'tag 8') @@ -269,15 +418,24 @@ class ModelsTest(BaseTest): self.assertEqual(set(Post.objects.tag_cloud()), set(tags_1)) self.assertEqual(set(Post.objects.tag_cloud(published=False)), set(tags)) + tags1 = set(Post.objects.tag_list(Post)) + tags2 = set(Tag.objects.all()) + self.assertEqual(tags1, tags2) + + self.assertEqual( + list(Post.objects.tagged(queryset=Post.objects.filter(pk=post1.pk)).order_by('pk').values_list('pk')), + list(Post.objects.filter(pk__in=(post1.pk, post2.pk)).order_by('pk').values_list('pk')) + ) + def test_plugin_latest(self): - post1 = self._get_post(self.data['en'][0]) - self._get_post(self.data['en'][1]) + post1 = self._get_post(self._post_data[0]['en']) + self._get_post(self._post_data[1]['en']) post1.tags.add('tag 1') post1.save() request = self.get_page_request('/', AnonymousUser(), r'/en/blog/', edit=False) request_auth = self.get_page_request('/', self.user_staff, r'/en/blog/', edit=False) request_edit = self.get_page_request('/', self.user_staff, r'/en/blog/', edit=True) - plugin = add_plugin(post1.content, 'BlogLatestEntriesPlugin', language='en') + plugin = add_plugin(post1.content, 'BlogLatestEntriesPlugin', language='en', app_config=self.app_config_1) tag = Tag.objects.get(slug='tag-1') plugin.tags.add(tag) # unauthenticated users get no post @@ -292,11 +450,11 @@ class ModelsTest(BaseTest): self.assertEqual(len(plugin.get_posts(request)), 1) def test_copy_plugin_latest(self): - post1 = self._get_post(self.data['en'][0]) - post2 = self._get_post(self.data['en'][1]) + post1 = self._get_post(self._post_data[0]['en']) + post2 = self._get_post(self._post_data[1]['en']) tag1 = Tag.objects.create(name='tag 1') tag2 = Tag.objects.create(name='tag 2') - plugin = add_plugin(post1.content, 'BlogLatestEntriesPlugin', language='en') + plugin = add_plugin(post1.content, 'BlogLatestEntriesPlugin', language='en', app_config=self.app_config_1) plugin.tags.add(tag1) plugin.tags.add(tag2) if CMS_30: @@ -309,10 +467,10 @@ class ModelsTest(BaseTest): self.assertEqual(set(new[0].tags.all()), set(plugin.tags.all())) def test_plugin_author(self): - post1 = self._get_post(self.data['en'][0]) - post2 = self._get_post(self.data['en'][1]) + post1 = self._get_post(self._post_data[0]['en']) + post2 = self._get_post(self._post_data[1]['en']) request = self.get_page_request('/', AnonymousUser(), r'/en/blog/', edit=False) - plugin = add_plugin(post1.content, 'BlogAuthorPostsPlugin', language='en') + plugin = add_plugin(post1.content, 'BlogAuthorPostsPlugin', language='en', app_config=self.app_config_1) plugin.authors.add(self.user) self.assertEqual(len(plugin.get_posts(request)), 0) self.assertEqual(plugin.get_authors()[0].count, 0) @@ -328,9 +486,9 @@ class ModelsTest(BaseTest): self.assertEqual(plugin.get_authors()[0].count, 2) def test_copy_plugin_author(self): - post1 = self._get_post(self.data['en'][0]) - post2 = self._get_post(self.data['en'][1]) - plugin = add_plugin(post1.content, 'BlogAuthorPostsPlugin', language='en') + post1 = self._get_post(self._post_data[0]['en']) + post2 = self._get_post(self._post_data[1]['en']) + plugin = add_plugin(post1.content, 'BlogAuthorPostsPlugin', language='en', app_config=self.app_config_1) plugin.authors.add(self.user) if CMS_30: plugins = list(post1.content.cmsplugin_set.filter(language='en').order_by('tree_id', 'level', 'position')) @@ -342,9 +500,9 @@ class ModelsTest(BaseTest): def test_multisite(self): with override('en'): - post1 = self._get_post(self.data['en'][0], sites=(self.site_1,)) - post2 = self._get_post(self.data['en'][1], sites=(self.site_2,)) - post3 = self._get_post(self.data['en'][2], sites=(self.site_2, self.site_1)) + post1 = self._get_post(self._post_data[0]['en'], sites=(self.site_1,)) + post2 = self._get_post(self._post_data[1]['en'], sites=(self.site_2,)) + post3 = self._get_post(self._post_data[2]['en'], sites=(self.site_2, self.site_1)) self.assertEqual(len(Post.objects.all()), 3) with self.settings(**{'SITE_ID': self.site_1.pk}): @@ -353,3 +511,25 @@ class ModelsTest(BaseTest): with self.settings(**{'SITE_ID': self.site_2.pk}): self.assertEqual(len(Post.objects.all().on_site()), 2) self.assertEqual(set(list(Post.objects.all().on_site())), set([post2, post3])) + + def test_str_repr(self): + post1 = self._get_post(self._post_data[0]['en']) + post1.meta_description = '' + post1.main_image = None + post1.save() + + self.assertEqual(force_text(post1), post1.title) + self.assertEqual(post1.get_description(), strip_tags(post1.abstract)) + self.assertEqual(post1.get_image_full_url(), '') + self.assertEqual(post1.get_author(), self.user) + + self.assertEqual(force_text(post1.categories.first()), 'category 1') + + plugin = add_plugin(post1.content, 'BlogAuthorPostsPlugin', language='en', app_config=self.app_config_1) + self.assertEqual(force_text(plugin.__str__()), '5 latest articles by author') + + plugin = add_plugin(post1.content, 'BlogLatestEntriesPlugin', language='en', app_config=self.app_config_1) + self.assertEqual(force_text(plugin.__str__()), '5 latest articles by tag') + + plugin = add_plugin(post1.content, 'BlogArchivePlugin', language='en', app_config=self.app_config_1) + self.assertEqual(force_text(plugin.__str__()), 'generic blog plugin') diff --git a/tests/test_plugins.py b/tests/test_plugins.py index 348a6ca..cb209f2 100644 --- a/tests/test_plugins.py +++ b/tests/test_plugins.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import, print_function, unicode_literals +import os.path import re from cms.api import add_plugin @@ -10,79 +11,80 @@ from taggit.models import Tag from djangocms_blog.models import BlogCategory -from . import BaseTest +from .base import BaseTest class PluginTest(BaseTest): def test_plugin_latest(self): - page1, page2 = self.get_pages() - post1 = self._get_post(self.data['en'][0]) - post2 = self._get_post(self.data['en'][1]) - post1.tags.add('tag 1') - post1.publish = True - post1.save() - ph = page1.placeholders.get(slot='content') + pages = self.get_pages() + posts = self.get_posts() + posts[0].tags.add('tag 1') + posts[0].publish = True + posts[0].save() + ph = pages[0].placeholders.get(slot='content') - plugin = add_plugin(ph, 'BlogLatestEntriesPlugin', language='en') + plugin = add_plugin(ph, 'BlogLatestEntriesPlugin', language='en', app_config=self.app_config_1) tag = Tag.objects.get(slug='tag-1') plugin.tags.add(tag) - context = self.get_plugin_context(page1, 'en', plugin, edit=True) + context = self.get_plugin_context(pages[0], 'en', plugin, edit=True) rendered = plugin.render_plugin(context, ph) - self.assertTrue(rendered.find('cms_plugin-djangocms_blog-post-abstract-1') > -1) + try: + self.assertTrue(rendered.find('cms_plugin-djangocms_blog-post-abstract-1') > -1) + except AssertionError: + self.assertTrue(rendered.find('cms-plugin-djangocms_blog-post-abstract-1') > -1) self.assertTrue(rendered.find(reverse('djangocms_blog:posts-tagged', kwargs={'tag': tag.slug})) > -1) self.assertTrue(rendered.find('

first line

') > -1) self.assertTrue(rendered.find('
-1) - self.assertTrue(rendered.find(post1.get_absolute_url()) > -1) + self.assertTrue(rendered.find(posts[0].get_absolute_url()) > -1) - category_2 = BlogCategory.objects.create(name=u'category 2') + category_2 = BlogCategory.objects.create(name='category 2', app_config=self.app_config_1) category_2.set_current_language('it', initialize=True) - category_2.name = u'categoria 2' + category_2.name = 'categoria 2' category_2.save() category_2.set_current_language('en') - post2.categories.add(category_2) - plugin = add_plugin(ph, 'BlogLatestEntriesPlugin', language='en') + posts[1].categories.add(category_2) + plugin = add_plugin(ph, 'BlogLatestEntriesPlugin', language='en', app_config=self.app_config_1) plugin.categories.add(category_2) - context = self.get_plugin_context(page1, 'en', plugin, edit=True) + context = self.get_plugin_context(pages[0], 'en', plugin, edit=True) rendered = plugin.render_plugin(context, ph) - self.assertTrue(rendered.find('cms_plugin-djangocms_blog-post-abstract-2') > -1) + try: + self.assertTrue(rendered.find('cms_plugin-djangocms_blog-post-abstract-2') > -1) + except AssertionError: + self.assertTrue(rendered.find('cms-plugin-djangocms_blog-post-abstract-2') > -1) self.assertTrue(rendered.find(reverse('djangocms_blog:posts-category', kwargs={'category': category_2.slug})) > -1) self.assertTrue(rendered.find('

second post first line

') > -1) self.assertTrue(rendered.find('
-1) - self.assertTrue(rendered.find(post2.get_absolute_url()) > -1) + self.assertTrue(rendered.find(posts[1].get_absolute_url()) > -1) def test_plugin_authors(self): - page1, page2 = self.get_pages() - post1 = self._get_post(self.data['en'][0]) - post2 = self._get_post(self.data['en'][1]) - post1.publish = True - post1.save() - post2.publish = True - post2.save() - ph = page1.placeholders.get(slot='content') - plugin = add_plugin(ph, 'BlogAuthorPostsPlugin', language='en') - plugin.authors.add(self.user) + pages = self.get_pages() + posts = self.get_posts() + posts[0].publish = True + posts[0].save() + posts[1].publish = True + posts[1].save() + ph = pages[0].placeholders.get(slot='content') + plugin = add_plugin(ph, 'BlogAuthorPostsPlugin', language='en', app_config=self.app_config_1) - context = self.get_plugin_context(page1, 'en', plugin, edit=True) + context = self.get_plugin_context(pages[0], 'en', plugin, edit=True) rendered = plugin.render_plugin(context, ph) - self.assertTrue(rendered.find(reverse('djangocms_blog:posts-author', kwargs={'username': self.user.get_username()})) > -1) - self.assertTrue(rendered.find('2 articles') > -1) + self.assertTrue(rendered.find('No article found') > -1) def test_plugin_tags(self): - page1, page2 = self.get_pages() - post1 = self._get_post(self.data['en'][0]) - post2 = self._get_post(self.data['en'][1]) - post1.tags.add('tag 1', 'tag 2', 'test tag') - post1.publish = True - post1.save() - post2.tags.add('test tag', 'another tag') - post2.publish = True - post2.save() - ph = page1.placeholders.get(slot='content') - plugin = add_plugin(ph, 'BlogTagsPlugin', language='en') - context = self.get_plugin_context(page1, 'en', plugin, edit=True) + pages = self.get_pages() + posts = self.get_posts() + posts[0].tags.add('tag 1', 'tag 2', 'test tag') + posts[0].publish = True + posts[0].save() + posts[1].tags.add('test tag', 'another tag') + posts[1].publish = True + posts[1].save() + ph = pages[0].placeholders.get(slot='content') + plugin = add_plugin(ph, 'BlogTagsPlugin', language='en', app_config=self.app_config_1) + context = self.get_plugin_context(pages[0], 'en', plugin, edit=True) rendered = plugin.render_plugin(context, ph) for tag in Tag.objects.all(): self.assertTrue(rendered.find(reverse('djangocms_blog:posts-tagged', kwargs={'tag': tag.slug})) > -1) @@ -94,38 +96,55 @@ class PluginTest(BaseTest): self.assertEqual(len(rx.findall(rendered)), 1) def test_blog_category_plugin(self): - page1, page2 = self.get_pages() - post1, post2 = self.get_posts() - post1.publish = True - post1.save() - post2.publish = True - post2.save() - ph = page1.placeholders.get(slot='content') - plugin = add_plugin(ph, 'BlogCategoryPlugin', language='en') + pages = self.get_pages() + posts = self.get_posts() + posts[0].publish = True + posts[0].save() + posts[1].publish = True + posts[1].save() + ph = pages[0].placeholders.get(slot='content') + plugin = add_plugin(ph, 'BlogCategoryPlugin', language='en', app_config=self.app_config_1) plugin_class = plugin.get_plugin_class_instance() - context = self.get_plugin_context(page1, 'en', plugin, edit=True) + context = self.get_plugin_context(pages[0], 'en', plugin, edit=True) context = plugin_class.render(context, plugin, ph) self.assertTrue(context['categories']) self.assertEqual(list(context['categories']), [self.category_1]) def test_blog_archive_plugin(self): - page1, page2 = self.get_pages() - post1, post2 = self.get_posts() - post1.publish = True - post1.save() - post2.publish = True - post2.save() - ph = page1.placeholders.get(slot='content') - plugin = add_plugin(ph, 'BlogArchivePlugin', language='en') + pages = self.get_pages() + posts = self.get_posts() + posts[0].publish = True + posts[0].save() + posts[1].publish = True + posts[1].save() + ph = pages[0].placeholders.get(slot='content') + plugin = add_plugin(ph, 'BlogArchivePlugin', language='en', app_config=self.app_config_1) plugin_class = plugin.get_plugin_class_instance() - context = self.get_plugin_context(page1, 'en', plugin, edit=True) + context = self.get_plugin_context(pages[0], 'en', plugin, edit=True) context = plugin_class.render(context, plugin, ph) self.assertEqual(context['dates'][0]['date'].date(), now().replace(year=now().year, month=now().month, day=1).date()) self.assertEqual(context['dates'][0]['count'], 2) - post2.publish = False - post2.save() + posts[1].publish = False + posts[1].save() context = plugin_class.render(context, plugin, ph) self.assertEqual(context['dates'][0]['date'].date(), now().replace(year=now().year, month=now().month, day=1).date()) self.assertEqual(context['dates'][0]['count'], 1) + + def test_templates(self): + posts = self.get_posts() + pages = self.get_pages() + + ph = pages[0].placeholders.get(slot='content') + plugin = add_plugin(ph, 'BlogLatestEntriesPlugin', language='en', app_config=self.app_config_1) + + context = self.get_plugin_context(pages[0], 'en', plugin) + plugin_class = plugin.get_plugin_class_instance() + self.assertEqual(plugin_class.get_render_template(context, plugin, ph), os.path.join('djangocms_blog', plugin_class.base_render_template)) + + self.app_config_1.app_data.config.template_prefix = 'whatever' + self.app_config_1.save() + self.assertEqual(plugin_class.get_render_template(context, plugin, ph), os.path.join('whatever', plugin_class.base_render_template)) + self.app_config_1.app_data.config.template_prefix = '' + self.app_config_1.save() diff --git a/tests/test_setup.py b/tests/test_setup.py new file mode 100644 index 0000000..13428fc --- /dev/null +++ b/tests/test_setup.py @@ -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 .base 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='blog.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') diff --git a/tests/test_toolbar.py b/tests/test_toolbar.py index be0ffb9..90b6ccb 100644 --- a/tests/test_toolbar.py +++ b/tests/test_toolbar.py @@ -6,7 +6,7 @@ from django.core.urlresolvers import reverse from djangocms_blog.models import BLOG_CURRENT_POST_IDENTIFIER -from . import BaseTest +from .base import BaseTest class ToolbarTest(BaseTest): @@ -16,13 +16,13 @@ class ToolbarTest(BaseTest): Test that Blog toolbar is present and contains all items """ from cms.toolbar.toolbar import CMSToolbar - post = self._get_post(self.data['en'][0]) - page1, page2 = self.get_pages() - request = self.get_page_request(page1, self.user, r'/en/blog/', edit=True) - setattr(request, BLOG_CURRENT_POST_IDENTIFIER, post) + posts = self.get_posts() + 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]) toolbar = CMSToolbar(request) 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=(post.pk,)))), 1) + self.assertEqual(len(blog_menu.find_items(ModalItem, url=reverse('admin:djangocms_blog_post_change', args=(posts[0].pk,)))), 1) diff --git a/tests/test_utils/models.py b/tests/test_utils/models.py new file mode 100644 index 0000000..40a96af --- /dev/null +++ b/tests/test_utils/models.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/tests/test_utils/templates/blog.html b/tests/test_utils/templates/blog.html new file mode 100644 index 0000000..f46def0 --- /dev/null +++ b/tests/test_utils/templates/blog.html @@ -0,0 +1,35 @@ +{% load cms_tags static menu_tags sekizai_tags %} + + + + {% block title %}{% page_attribute 'title' %}{% endblock title %} + {% render_block "css" %} + {% include "meta_mixin/meta.html" %} + + + +{% cms_toolbar %} +
+ + {% block content %} + {% placeholder "content" %} + {% endblock content %} +
+{% render_block "js" %} +{% with_data "js-script" as jsset %} + {% for js in jsset %}{% endfor %} +{% end_with_data %} +{% render_block "js_end" %} + + diff --git a/tests/test_utils/urls.py b/tests/test_utils/urls.py index ec11f56..b15d02a 100644 --- a/tests/test_utils/urls.py +++ b/tests/test_utils/urls.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import, print_function, unicode_literals +import sys + from cms.utils.conf import get_cms_setting from django.conf import settings from django.conf.urls import include, patterns, url @@ -22,9 +24,15 @@ urlpatterns = patterns( urlpatterns += staticfiles_urlpatterns() +if 'server' not in sys.argv: + urlpatterns += i18n_patterns( + '', + url(r'^blog/', include( + 'djangocms_blog.urls', namespace='djangocms_blog', app_name='djangocms_blog' + )), + ) urlpatterns += i18n_patterns( '', url(r'^admin/', include(admin.site.urls)), - url(r'^blog/', include('djangocms_blog.urls', namespace='djangocms_blog')), url(r'^', include('cms.urls')), ) diff --git a/tests/test_views.py b/tests/test_views.py index 27c35e8..6bb8a4c 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -1,71 +1,127 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import, print_function, unicode_literals +import os.path + +from aldryn_apphooks_config.utils import get_app_instance +from cms.toolbar.items import ModalItem from django.contrib.auth.models import AnonymousUser +from django.core.exceptions import ImproperlyConfigured +from django.core.urlresolvers import reverse from django.http import Http404 from django.utils.timezone import now +from django.utils.translation import ugettext_lazy as _ from parler.tests.utils import override_parler_settings from parler.utils.conf import add_default_language_settings from parler.utils.context import smart_override, switch_language from djangocms_blog.feeds import LatestEntriesFeed, TagFeed +from djangocms_blog.models import BLOG_CURRENT_NAMESPACE from djangocms_blog.sitemaps import BlogSitemap from djangocms_blog.views import ( AuthorEntriesView, CategoryEntriesView, PostArchiveView, PostDetailView, PostListView, TaggedListView, ) -from . import BaseTest +from .base import BaseTest class ViewTest(BaseTest): def test_post_list_view(self): - page1, page2 = self.get_pages() - post1, post2 = self.get_posts() + posts = self.get_posts() + pages = self.get_pages() - request = self.get_page_request(page1, AnonymousUser(), r'/en/blog/', edit=False) + request = self.get_request(pages[1], 'en', AnonymousUser()) with smart_override('en'): view_obj = PostListView() view_obj.request = request + view_obj.namespace, view_obj.config = get_app_instance(request) + self.assertEqual(getattr(request, BLOG_CURRENT_NAMESPACE, None), None) - self.assertEqual(list(view_obj.get_queryset()), [post1]) + self.assertEqual(list(view_obj.get_queryset()), [posts[0]]) + self.assertEqual(getattr(request, BLOG_CURRENT_NAMESPACE), self.app_config_1) - request = self.get_page_request(page1, self.user, r'/en/blog/', edit=False) + request = self.get_page_request(pages[1], self.user, lang='en', edit=False) + view_obj.namespace, view_obj.config = get_app_instance(request) view_obj.request = request view_obj.kwargs = {} qs = view_obj.get_queryset() self.assertEqual(qs.count(), 1) - self.assertEqual(set(qs), set([post1])) + self.assertEqual(set(qs), set([posts[0]])) - request = self.get_page_request(page1, self.user, r'/en/blog/', edit=True) + request = self.get_page_request(pages[1], self.user, lang='en', edit=True) + view_obj.namespace, view_obj.config = get_app_instance(request) view_obj.request = request - self.assertEqual(set(view_obj.get_queryset()), set([post1, post2])) + self.assertEqual(set(view_obj.get_queryset()), set([posts[0], posts[1], posts[2]])) view_obj.kwargs = {} view_obj.object_list = view_obj.get_queryset() view_obj.paginate_by = 1 context = view_obj.get_context_data(object_list=view_obj.object_list) self.assertTrue(context['is_paginated']) - self.assertEqual(list(context['post_list']), [post2]) - self.assertEqual(context['paginator'].count, 2) - self.assertEqual(context['post_list'][0].title, 'Second post') + self.assertEqual(list(context['post_list']), [posts[2]]) + self.assertEqual(context['paginator'].count, 3) + self.assertEqual(context['post_list'][0].title, 'Third post') response = view_obj.render_to_response(context) self.assertContains(response, context['post_list'][0].get_absolute_url()) + self.assertEqual(getattr(request, BLOG_CURRENT_NAMESPACE), self.app_config_1) with smart_override('it'): - request = self.get_page_request(page1, self.user, r'/it/blog/', lang='it', edit=True) + request = self.get_page_request(pages[1], self.user, lang='it', edit=True) + view_obj = PostListView() + view_obj.namespace, view_obj.config = get_app_instance(request) view_obj.request = request + view_obj.kwargs = {} view_obj.object_list = view_obj.get_queryset() context = view_obj.get_context_data(object_list=view_obj.object_list) - self.assertEqual(context['post_list'][0].title, 'Secondo post') + self.assertEqual(context['post_list'][0].title, 'Terzo post') response = view_obj.render_to_response(context) self.assertContains(response, context['post_list'][0].get_absolute_url()) + blog_menu = request.toolbar.get_or_create_menu('djangocms_blog', _('Blog')) + + self.assertEqual(len(blog_menu.items), 3) + 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_blogconfig_change', args=(self.app_config_1.pk,) + ) + )), 1) + + def test_get_view_url(self): + posts = self.get_posts() + pages = self.get_pages() + + # Test the custom version of get_view_url against the different namespaces + request = self.get_request(pages[1], 'en', AnonymousUser()) + view_obj_1 = PostListView() + view_obj_1.request = request + view_obj_1.args = () + view_obj_1.kwargs = {} + view_obj_1.namespace, view_obj_1.config = get_app_instance(request) + self.assertEqual(view_obj_1.get_view_url(), pages[1].get_absolute_url()) + + request = self.get_request(pages[2], 'en', AnonymousUser()) + view_obj_2 = PostListView() + view_obj_2.request = request + view_obj_2.args = () + view_obj_2.kwargs = {} + view_obj_2.namespace, view_obj_2.config = get_app_instance(request) + self.assertEqual(view_obj_2.get_view_url(), pages[2].get_absolute_url()) + + view_obj_2.view_url_name = None + with self.assertRaises(ImproperlyConfigured): + view_obj_2.get_view_url() def test_post_list_view_fallback(self): - page1, page2 = self.get_pages() - post1, post2 = self.get_posts() + posts = self.get_posts() + pages = self.get_pages() PARLER_FALLBACK = { 1: ( @@ -81,89 +137,97 @@ class ViewTest(BaseTest): with smart_override('fr'): view_obj = PostListView() - request = self.get_page_request(page1, self.user, r'/fr/blog/', lang='fr', edit=True) + request = self.get_page_request(pages[1], self.user, lang='fr', edit=True) view_obj.request = request + view_obj.namespace, view_obj.config = get_app_instance(request) view_obj.kwargs = {} view_obj.object_list = view_obj.get_queryset() view_obj.get_context_data(object_list=view_obj.object_list) - self.assertEqual(view_obj.get_queryset().count(), 2) + self.assertEqual(view_obj.get_queryset().count(), 3) PARLER_FALLBACK = add_default_language_settings(PARLER_FALLBACK) with override_parler_settings(PARLER_LANGUAGES=PARLER_FALLBACK): view_obj = PostListView() - request = self.get_page_request(page1, self.user, r'/fr/blog/', lang='fr', edit=True) + request = self.get_page_request(pages[1], self.user, lang='fr', edit=True) view_obj.request = request + view_obj.namespace, view_obj.config = get_app_instance(request) view_obj.kwargs = {} view_obj.object_list = view_obj.get_queryset() view_obj.get_context_data(object_list=view_obj.object_list) self.assertEqual(view_obj.get_queryset().count(), 0) def test_post_detail_view(self): - page1, page2 = self.get_pages() - post1, post2 = self.get_posts() + posts = self.get_posts() + pages = self.get_pages() with smart_override('en'): - with switch_language(post1, 'en'): - request = self.get_page_request(page1, AnonymousUser(), r'/en/blog/', edit=False) + with switch_language(posts[0], 'en'): + request = self.get_page_request(pages[1], AnonymousUser(), lang='en', edit=False) view_obj = PostDetailView() view_obj.request = request + view_obj.namespace, view_obj.config = get_app_instance(request) with self.assertRaises(Http404): view_obj.kwargs = {'slug': 'not-existing'} post_obj = view_obj.get_object() - view_obj.kwargs = {'slug': post1.slug} + view_obj.kwargs = {'slug': posts[0].slug} post_obj = view_obj.get_object() - self.assertEqual(post_obj, post1) + self.assertEqual(post_obj, posts[0]) self.assertEqual(post_obj.language_code, 'en') with smart_override('it'): - with switch_language(post1, 'it'): - request = self.get_page_request(page1, AnonymousUser(), r'/it/blog/', lang='it', edit=False) + with switch_language(posts[0], 'it'): + request = self.get_page_request(pages[1], AnonymousUser(), lang='it', edit=False) + view_obj = PostDetailView() view_obj.request = request - view_obj.kwargs = {'slug': post1.slug} + view_obj.namespace, view_obj.config = get_app_instance(request) + + view_obj.kwargs = {'slug': posts[0].slug} post_obj = view_obj.get_object() - self.assertEqual(post_obj, post1) + self.assertEqual(post_obj, posts[0]) self.assertEqual(post_obj.language_code, 'it') view_obj.object = post_obj context = view_obj.get_context_data() - self.assertEqual(context['post'], post1) + self.assertEqual(context['post'], posts[0]) self.assertEqual(context['post'].language_code, 'it') self.assertTrue(context['meta']) def test_post_archive_view(self): - page1, page2 = self.get_pages() - post1, post2 = self.get_posts() + posts = self.get_posts() + pages = self.get_pages() with smart_override('en'): - request = self.get_page_request(page1, AnonymousUser(), r'/en/blog/', edit=False) + request = self.get_page_request(pages[1], AnonymousUser(), lang='en', edit=False) view_obj = PostArchiveView() view_obj.request = request + view_obj.namespace, view_obj.config = get_app_instance(request) view_obj.kwargs = {'year': now().year, 'month': now().month} # One post only, anonymous request qs = view_obj.get_queryset() self.assertEqual(qs.count(), 1) - self.assertEqual(list(qs), [post1]) + self.assertEqual(list(qs), [posts[0]]) view_obj.object_list = qs context = view_obj.get_context_data(object_list=view_obj.object_list) self.assertEqual(context['archive_date'].date(), now().replace(year=now().year, month=now().month, day=1).date()) def test_category_entries_view(self): - page1, page2 = self.get_pages() - post1, post2 = self.get_posts() + posts = self.get_posts() + pages = self.get_pages() with smart_override('en'): - request = self.get_page_request(page1, self.user, r'/en/blog/', edit=True) + request = self.get_page_request(pages[1], self.user, lang='en', edit=True) view_obj = CategoryEntriesView() view_obj.request = request + view_obj.namespace, view_obj.config = get_app_instance(request) view_obj.kwargs = {'category': 'category-1'} qs = view_obj.get_queryset() - self.assertEqual(qs.count(), 2) - self.assertEqual(set(qs), set([post1, post2])) + self.assertEqual(qs.count(), 3) + self.assertEqual(set(qs), set([posts[0], posts[1], posts[2]])) view_obj.paginate_by = 1 view_obj.object_list = qs @@ -171,22 +235,28 @@ class ViewTest(BaseTest): self.assertTrue(context['category']) self.assertEqual(context['category'], self.category_1) self.assertTrue(context['is_paginated']) - self.assertEqual(list(context['post_list']), [post2]) - self.assertEqual(context['paginator'].count, 2) - self.assertEqual(context['post_list'][0].title, 'Second post') + self.assertEqual(list(context['post_list']), [posts[2]]) + self.assertEqual(context['paginator'].count, 3) + self.assertEqual(context['post_list'][0].title, 'Third post') + + request = self.get_page_request(pages[1], self.user, edit=False) + view_obj.request = request + qs = view_obj.get_queryset() + self.assertEqual(qs.count(), 1) def test_author_entries_view(self): - page1, page2 = self.get_pages() - post1, post2 = self.get_posts() + posts = self.get_posts() + pages = self.get_pages() with smart_override('en'): - request = self.get_page_request(page1, self.user, r'/en/blog/', edit=True) + request = self.get_page_request(pages[1], self.user, lang='en', edit=True) view_obj = AuthorEntriesView() + view_obj.namespace, view_obj.config = get_app_instance(request) view_obj.request = request view_obj.kwargs = {'username': self.user.get_username()} qs = view_obj.get_queryset() - self.assertEqual(qs.count(), 2) - self.assertEqual(set(qs), set([post1, post2])) + self.assertEqual(qs.count(), 3) + self.assertEqual(set(qs), set([posts[0], posts[1], posts[2]])) view_obj.paginate_by = 1 view_obj.object_list = qs @@ -194,74 +264,107 @@ class ViewTest(BaseTest): self.assertTrue(context['author']) self.assertEqual(context['author'], self.user) self.assertTrue(context['is_paginated']) - self.assertEqual(list(context['post_list']), [post2]) - self.assertEqual(context['paginator'].count, 2) - self.assertEqual(context['post_list'][0].title, 'Second post') + self.assertEqual(list(context['post_list']), [posts[2]]) + self.assertEqual(context['paginator'].count, 3) + self.assertEqual(context['post_list'][0].title, 'Third post') + + request = self.get_page_request(pages[1], self.user, edit=False) + view_obj.request = request + qs = view_obj.get_queryset() + self.assertEqual(qs.count(), 1) def test_taggedlist_view(self): - page1, page2 = self.get_pages() - post1, post2 = self.get_posts() - post1.tags.add('tag 1', 'tag 2', 'tag 3', 'tag 4') - post1.save() - post2.tags.add('tag 6', 'tag 2', 'tag 5', 'tag 8') - post2.save() + pages = self.get_pages() + posts = self.get_posts() + posts[0].tags.add('tag 1', 'tag 2', 'tag 3', 'tag 4') + posts[0].save() + posts[1].tags.add('tag 6', 'tag 2', 'tag 5', 'tag 8') + posts[1].save() with smart_override('en'): - request = self.get_page_request(page1, self.user, r'/en/blog/', edit=True) + request = self.get_page_request(pages[1], self.user, lang='en', edit=True) view_obj = TaggedListView() view_obj.request = request + view_obj.namespace, view_obj.config = get_app_instance(request) view_obj.kwargs = {'tag': 'tag-2'} qs = view_obj.get_queryset() self.assertEqual(qs.count(), 2) - self.assertEqual(set(qs), set([post1, post2])) + self.assertEqual(set(qs), set([posts[0], posts[1]])) view_obj.paginate_by = 1 view_obj.object_list = qs context = view_obj.get_context_data(object_list=view_obj.object_list) self.assertTrue(context['tagged_entries'], 'tag-2') self.assertTrue(context['is_paginated']) - self.assertEqual(list(context['post_list']), [post2]) + self.assertEqual(list(context['post_list']), [posts[1]]) self.assertEqual(context['paginator'].count, 2) self.assertEqual(context['post_list'][0].title, 'Second post') def test_feed(self): - page1, page2 = self.get_pages() - post1, post2 = self.get_posts() - post1.tags.add('tag 1', 'tag 2', 'tag 3', 'tag 4') - post1.save() - post2.tags.add('tag 6', 'tag 2', 'tag 5', 'tag 8') - post2.save() - post1.set_current_language('en') + posts = self.get_posts() + pages = self.get_pages() + posts[0].tags.add('tag 1', 'tag 2', 'tag 3', 'tag 4') + posts[0].save() + posts[1].tags.add('tag 6', 'tag 2', 'tag 5', 'tag 8') + posts[1].save() + posts[0].set_current_language('en') - feed = LatestEntriesFeed() - self.assertEqual(list(feed.items()), [post1]) - request = self.get_page_request(page1, self.user, r'/en/blog/', lang='en', edit=False) - xml = feed(request) - self.assertContains(xml, post1.get_absolute_url()) - self.assertContains(xml, 'Blog articles on example.com') + with smart_override('en'): + with switch_language(posts[0], 'en'): + + request = self.get_page_request(pages[1], self.user, path=posts[0].get_absolute_url()) + + feed = LatestEntriesFeed() + feed.namespace, feed.config = get_app_instance(request) + self.assertEqual(list(feed.items()), [posts[0]]) + self.reload_urlconf() + xml = feed(request) + self.assertContains(xml, posts[0].get_absolute_url()) + self.assertContains(xml, 'Blog articles on example.com') with smart_override('it'): - with switch_language(post1, 'it'): + with switch_language(posts[0], 'it'): feed = LatestEntriesFeed() - self.assertEqual(list(feed.items()), [post1]) - request = self.get_page_request(page1, self.user, r'/it/blog/', lang='it', edit=False) + feed.namespace, feed.config = get_app_instance(request) + self.assertEqual(list(feed.items()), [posts[0]]) + request = self.get_page_request(pages[1], self.user, path=posts[0].get_absolute_url()) xml = feed(request) - self.assertContains(xml, post1.get_absolute_url()) + self.assertContains(xml, posts[0].get_absolute_url()) self.assertContains(xml, 'Articoli del blog su example.com') feed = TagFeed() - self.assertEqual(list(feed.items('tag-2')), [post1]) + feed.namespace = self.app_config_1.namespace + feed.config = self.app_config_1 + self.assertEqual(list(feed.items('tag-2')), [posts[0]]) def test_sitemap(self): - post1, post2 = self.get_posts() - post1.tags.add('tag 1', 'tag 2', 'tag 3', 'tag 4') - post1.save() - post2.tags.add('tag 6', 'tag 2', 'tag 5', 'tag 8') - post2.publish = True - post2.save() - post1.set_current_language('en') + posts = self.get_posts() + posts[0].tags.add('tag 1', 'tag 2', 'tag 3', 'tag 4') + posts[0].save() + posts[1].tags.add('tag 6', 'tag 2', 'tag 5', 'tag 8') + posts[1].publish = True + posts[1].save() + posts[0].set_current_language('en') sitemap = BlogSitemap() - self.assertEqual(sitemap.items().count(), 2) + self.assertEqual(sitemap.items().count(), 3) for item in sitemap.items(): self.assertTrue(sitemap.lastmod(item).date(), now().today()) + + def test_templates(self): + posts = self.get_posts() + pages = self.get_pages() + + with smart_override('en'): + request = self.get_page_request(pages[1], self.user, edit=True) + view_obj = PostListView() + view_obj.request = request + view_obj.namespace = self.app_config_1.namespace + view_obj.config = self.app_config_1 + self.assertEqual(view_obj.get_template_names(), os.path.join('djangocms_blog', 'post_list.html')) + + self.app_config_1.app_data.config.template_prefix = 'whatever' + self.app_config_1.save() + self.assertEqual(view_obj.get_template_names(), os.path.join('whatever', 'post_list.html')) + self.app_config_1.app_data.config.template_prefix = '' + self.app_config_1.save() diff --git a/tox.ini b/tox.ini index 1b3859d..ba86678 100644 --- a/tox.ini +++ b/tox.ini @@ -1,18 +1,25 @@ [tox] -envlist = py{26}-django16-cms{30,31},py{27,33,34}-django{16,17}-cms{30,31,32},py{27,33,34}-django{18}-cms{31,32},pep8,isort +envlist = pep8,isort,py{35,34,33,27}-django{19,18}-cms{32,31},py{34,33,27}-django{17,16}-cms{32,31,30},py{26}-django16-cms{31,30} [testenv] -commands = {env:COMMAND:python} setup.py test +commands = {env:COMMAND:python} cms_helper.py test djangocms_blog --no-migrate deps = django16: Django>=1.6,<1.7 django17: Django>=1.7,<1.8 django18: Django>=1.7,<1.9 django18: https://github.com/stefanfoulis/django-filer/archive/develop.zip + django18: https://github.com/nephila/cmsplugin-filer/archive/fix/static_filer.zip + django19: Django==1.9a1 + django19: https://github.com/stefanfoulis/django-filer/archive/develop.zip + django19: https://github.com/nephila/cmsplugin-filer/archive/fix/static_filer.zip cms30: https://github.com/divio/django-cms/archive/support/3.0.x.zip cms31: https://github.com/divio/django-cms/archive/support/3.1.x.zip cms32: https://github.com/divio/django-cms/archive/develop.zip + https://github.com/nephila/django-meta-mixin/archive/master.zip https://github.com/nephila/djangocms-helper/archive/develop.zip py26: unittest2 + django-parler<1.5 + https://github.com/aldryn/aldryn-apphooks-config/archive/master.zip -r{toxinidir}/requirements-test.txt [testenv:isort]