Merge pull request #149 from nephila/feature/variable_slugs
Add configurable permalinks
This commit is contained in:
commit
fdbd30bd8c
6 changed files with 181 additions and 15 deletions
|
@ -123,11 +123,46 @@ class PostAdmin(PlaceholderAdminMixin, FrontendEditableAdminMixin,
|
|||
|
||||
class BlogConfigAdmin(BaseAppHookConfig, TranslatableAdmin):
|
||||
|
||||
def get_config_fields(self):
|
||||
return (
|
||||
'app_title', 'config.paginate_by', 'config.set_author', 'config.default_published',
|
||||
'config.use_placeholder', 'config.use_abstract',
|
||||
)
|
||||
@property
|
||||
def declared_fieldsets(self):
|
||||
return [
|
||||
(None, {
|
||||
'fields': ('type', 'namespace',)
|
||||
}),
|
||||
('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',
|
||||
),
|
||||
'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',
|
||||
)
|
||||
}),
|
||||
('Twitter', {
|
||||
'fields': (
|
||||
'config.twitter_type', 'config.twitter_site', 'config.twitter_author',
|
||||
)
|
||||
}),
|
||||
('Google+', {
|
||||
'fields': (
|
||||
'config.gplus_type', 'config.gplus_author',
|
||||
)
|
||||
}),
|
||||
]
|
||||
|
||||
admin.site.register(BlogCategory, BlogCategoryAdmin)
|
||||
admin.site.register(Post, PostAdmin)
|
||||
|
|
|
@ -29,6 +29,11 @@ class BlogConfigForm(AppDataForm):
|
|||
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')
|
||||
|
|
|
@ -177,12 +177,19 @@ class Post(ModelMeta, TranslatableModel):
|
|||
def get_absolute_url(self, lang=None):
|
||||
if not lang:
|
||||
lang = get_language()
|
||||
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=lang,
|
||||
any_language=True)}
|
||||
category = self.categories.first()
|
||||
kwargs = {}
|
||||
urlconf = get_setting('PERMALINK_URLS')[self.app_config.url_patterns]
|
||||
if '<year>' in urlconf:
|
||||
kwargs['year'] = self.date_published.year
|
||||
if '<month>' in urlconf:
|
||||
kwargs['month'] = '%02d' % self.date_published.month
|
||||
if '<day>' in urlconf:
|
||||
kwargs['day'] = '%02d' % self.date_published.day
|
||||
if '<slug>' in urlconf:
|
||||
kwargs['slug'] = self.safe_translation_getter('slug', language_code=lang, any_language=True) # NOQA
|
||||
if '<category>' 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 get_meta_attribute(self, param):
|
||||
|
|
|
@ -13,6 +13,19 @@ def get_setting(name):
|
|||
)
|
||||
BLOG_TYPES = getattr(settings, 'BLOG_TYPES', OBJECT_TYPES)
|
||||
|
||||
PERMALINKS = (
|
||||
('full_date', _('Full date')),
|
||||
('short_date', _('Year / Month')),
|
||||
('category', _('Category')),
|
||||
('slug', _('Just slug')),
|
||||
)
|
||||
PERMALINKS_URLS = {
|
||||
'full_date': r'^(?P<year>\d{4})/(?P<month>\d{1,2})/(?P<day>\d{1,2})/(?P<slug>\w[-\w]*)/$',
|
||||
'short_date': r'^(?P<year>\d{4})/(?P<month>\d{1,2})/(?P<slug>\w[-\w]*)/$',
|
||||
'category': r'^(?P<category>\w[-\w]*)/(?P<slug>\w[-\w]*)/$',
|
||||
'slug': r'^(?P<slug>\w[-\w]*)/$',
|
||||
}
|
||||
|
||||
default = {
|
||||
'BLOG_IMAGE_THUMBNAIL_SIZE': getattr(settings, 'BLOG_IMAGE_THUMBNAIL_SIZE', {
|
||||
'size': '120x120',
|
||||
|
@ -55,5 +68,7 @@ def get_setting(name):
|
|||
'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),
|
||||
}
|
||||
return default['BLOG_%s' % name]
|
||||
|
|
|
@ -4,11 +4,24 @@ from __future__ import absolute_import, print_function, unicode_literals
|
|||
from django.conf.urls import patterns, url
|
||||
|
||||
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 +32,7 @@ urlpatterns = patterns(
|
|||
PostArchiveView.as_view(), name='posts-archive'),
|
||||
url(r'^(?P<year>\d{4})/(?P<month>\d{1,2})/$',
|
||||
PostArchiveView.as_view(), name='posts-archive'),
|
||||
url(r'^(?P<year>\d{4})/(?P<month>\d{1,2})/(?P<day>\d{1,2})/(?P<slug>\w[-\w]*)/$',
|
||||
PostDetailView.as_view(), name='post-detail'),
|
||||
) + detail_urls + [
|
||||
url(r'^author/(?P<username>[\w\.@+-]+)/$',
|
||||
AuthorEntriesView.as_view(), name='posts-author'),
|
||||
url(r'^category/(?P<category>[\w\.@+-]+)/$',
|
||||
|
@ -29,4 +41,4 @@ urlpatterns = patterns(
|
|||
TaggedListView.as_view(), name='posts-tagged'),
|
||||
url(r'^tag/(?P<tag>[-\w]+)/feed/$',
|
||||
TagFeed(), name='posts-tagged-feed'),
|
||||
)
|
||||
]
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
|
||||
import re
|
||||
from copy import deepcopy
|
||||
|
||||
import parler
|
||||
|
@ -19,7 +20,8 @@ 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
|
||||
|
@ -31,6 +33,58 @@ class AdminTest(BaseTest):
|
|||
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)
|
||||
|
||||
post = self._get_post(self._post_data[0]['en'])
|
||||
post = self._get_post(self._post_data[0]['it'], post, 'it')
|
||||
|
||||
# Add view only contains the apphook selection widget
|
||||
response = post_admin.add_view(request)
|
||||
self.assertNotContains(response, '<input id="id_slug" maxlength="50" name="slug" type="text"')
|
||||
self.assertContains(response, '<option value="%s">Blog / sample_app</option>' % self.app_config_1.pk)
|
||||
|
||||
# Changeview is 'normal'
|
||||
response = post_admin.change_view(request, str(post.pk))
|
||||
self.assertContains(response, '<input id="id_slug" maxlength="50" name="slug" type="text" value="first-post" />')
|
||||
self.assertContains(response, '<option value="%s" selected="selected">Blog / sample_app</option>' % 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, '<input class="vTextField" id="id_namespace" maxlength="100" name="namespace" type="text" />')
|
||||
|
||||
# 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, '<option value="Article" selected="selected">Article</option>')
|
||||
# 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, '<input id="id_config-og_app_id" maxlength="200" name="config-og_app_id" type="text" />')
|
||||
self.assertContains(response, '<input class="vTextField" id="id_namespace" maxlength="100" name="namespace" type="text" value="sample_app" />')
|
||||
|
||||
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, '<input class="vTextField" id="id_name" maxlength="255" name="name" type="text" value="category 1" />')
|
||||
self.assertContains(response, '<option value="%s">Blog / sample_app</option>' % 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, '<input class="vTextField" id="id_name" maxlength="255" name="name" type="text" value="category 1" />')
|
||||
self.assertContains(response, '<option value="%s" selected="selected">Blog / sample_app</option>' % 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)
|
||||
|
@ -220,6 +274,44 @@ 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._post_data[0]['en'])
|
||||
post2 = self._get_post(self._post_data[1]['en'])
|
||||
|
|
Loading…
Reference in a new issue