From e8ab9bc5024414f273a9cfcd32561cded3de0ba3 Mon Sep 17 00:00:00 2001 From: Magdalena Rother Date: Thu, 24 Nov 2016 14:43:24 +0100 Subject: [PATCH 01/24] Move page_titleextension to cms_tags --- barbeque/templatetags/cms_tags.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 barbeque/templatetags/cms_tags.py diff --git a/barbeque/templatetags/cms_tags.py b/barbeque/templatetags/cms_tags.py new file mode 100644 index 0000000..bff52a5 --- /dev/null +++ b/barbeque/templatetags/cms_tags.py @@ -0,0 +1,24 @@ +from cms.models import Page +from cms.utils.moderator import use_draft +from django import template +from django.core.exceptions import ObjectDoesNotExist + + +register = template.Library() + + +@register.simple_tag(takes_context=True) +def page_titleextension(context, page_id, extension): + try: + page = Page.objects.get(pk=page_id) + if 'request' in context and use_draft(context['request']): + page = page.get_draft_object() + else: + page = page.get_public_object() + except Page.DoesNotExist: + return None + + try: + return getattr(page.get_title_obj(), extension) + except ObjectDoesNotExist: + return None From c45c6d214b49b91fe96894ee41b3e4e9481157e2 Mon Sep 17 00:00:00 2001 From: Magdalena Rother Date: Thu, 24 Nov 2016 14:55:07 +0100 Subject: [PATCH 02/24] Move SharingExtensionToolbar and corresponding model --- barbeque/cms/models.py | 26 ++++++++++++++++++++++++++ barbeque/cms/toolbar.py | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 barbeque/cms/models.py diff --git a/barbeque/cms/models.py b/barbeque/cms/models.py new file mode 100644 index 0000000..13b4571 --- /dev/null +++ b/barbeque/cms/models.py @@ -0,0 +1,26 @@ +from barbeque.filer import FilerFileField +from cms.extensions import TitleExtension +from cms.extensions.extension_pool import extension_pool +from django.db import models +from django.utils.translation import ugettext_lazy as _ + + +class SharingExtension(TitleExtension): + sharing_title = models.CharField( + _('Sharing title'), max_length=255, blank=True, null=True) + + sharing_description = models.TextField( + _('Sharing description'), blank=True, null=True) + + sharing_image = FilerFileField( + verbose_name=_('Sharing image'), blank=True, null=True, + on_delete=models.SET_NULL) + + class Meta: + verbose_name = _('Sharing Configuration') + verbose_name_plural = _('Sharing Configuration') + + def __str__(self): + return str(self.extended_object) + +extension_pool.register(SharingExtension) diff --git a/barbeque/cms/toolbar.py b/barbeque/cms/toolbar.py index 90ed3ee..5ab5416 100644 --- a/barbeque/cms/toolbar.py +++ b/barbeque/cms/toolbar.py @@ -1,8 +1,11 @@ from cms.cms_toolbars import ( ADMIN_MENU_IDENTIFIER, PAGE_MENU_IDENTIFIER) +from cms.extensions.toolbar import ExtensionToolbar from cms.toolbar_base import CMSToolbar from cms.toolbar_pool import toolbar_pool from cms.toolbar.items import SideframeItem, ModalItem, SubMenu +from django.utils.translation import ugettext_lazy as _ +from .models import SharingExtension class ForceModalDialogToolbar(CMSToolbar): @@ -41,3 +44,35 @@ def populate(self): self.rebuild_menu(menu) toolbar_pool.register(ForceModalDialogToolbar) + + +@toolbar_pool.register +class SharingExtensionToolbar(ExtensionToolbar): + model = SharingExtension + insert_after = _('Advanced settings') + + def get_item_position(self, menu): + for items in menu._memo.values(): + for item in items: + if str(getattr(item, 'name', None)) in ( + str(self.insert_after), + '{0}...'.format(self.insert_after) + ): + return menu._item_position(item) + 1 + + return None + + def populate(self): + current_page_menu = self._setup_extension_toolbar() + if not current_page_menu or not self.page: + return + + position = self.get_item_position(current_page_menu) + + urls = self.get_title_extension_admin() + for title_extension, url in urls: + current_page_menu.add_modal_item( + self.model._meta.verbose_name, + url=url, position=position, + disabled=not self.toolbar.edit_mode + ) From e7257b06f494332698b7b46d6ed5132cfdbb42df Mon Sep 17 00:00:00 2001 From: Magdalena Rother Date: Thu, 24 Nov 2016 14:59:08 +0100 Subject: [PATCH 03/24] Unify toolbars registration --- barbeque/cms/toolbar.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/barbeque/cms/toolbar.py b/barbeque/cms/toolbar.py index 5ab5416..a507874 100644 --- a/barbeque/cms/toolbar.py +++ b/barbeque/cms/toolbar.py @@ -8,6 +8,7 @@ from .models import SharingExtension +@toolbar_pool.register class ForceModalDialogToolbar(CMSToolbar): def rebuild_menu(self, menu): items = [] @@ -43,8 +44,6 @@ def populate(self): for menu in [menu for menu in menus if menu]: self.rebuild_menu(menu) -toolbar_pool.register(ForceModalDialogToolbar) - @toolbar_pool.register class SharingExtensionToolbar(ExtensionToolbar): From 074e413067048a63c3755494955f1090d60933b8 Mon Sep 17 00:00:00 2001 From: Magdalena Rother Date: Fri, 25 Nov 2016 09:47:23 +0100 Subject: [PATCH 04/24] Add UploadToPath --- barbeque/files.py | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/barbeque/files.py b/barbeque/files.py index 9e853da..640eca1 100644 --- a/barbeque/files.py +++ b/barbeque/files.py @@ -4,12 +4,20 @@ import uuid from django.template.defaultfilters import slugify +from django.utils.deconstruct import deconstructible -def upload_to_path(base_path, attr=None, uuid_filename=False): - def upload_to_path_callback(instance, filename): - if attr: - parts = attr.split('__') +@deconstructible +class UploadToPath(object): + + def __init__(self, base_path, attr=None, uuid_filename=False): + self.base_path = base_path + self.attr = attr + self.uuid_filename = uuid_filename + + def __call__(self, instance, filename): + if self.attr: + parts = self.attr.split('__') obj_path = parts[:-1] field_name = parts[-1] @@ -17,22 +25,24 @@ def upload_to_path_callback(instance, filename): for part in obj_path: obj = getattr(obj, part) - path = base_path % slugify(getattr(obj, field_name, '_')) + path = self.base_path % slugify(getattr(obj, field_name, '_')) else: - path = base_path + path = self.base_path filename_parts = filename.rsplit('.', 1) - if uuid_filename: + if self.uuid_filename: filename = str(uuid.uuid4()) else: filename = slugify(filename_parts[0]) extension = len(filename_parts) > 1 and '.{0}'.format(filename_parts[-1]) or '' - return '%s%s%s' % (path, filename, extension) + return os.path.join(path, '{0}{1}'.format(filename, extension)) - return upload_to_path_callback + +def upload_to_path(base_path, attr=None, uuid_filename=False): + return UploadToPath(base_path=base_path, attr=attr, uuid_filename=uuid_filename) class MoveableNamedTemporaryFile(object): From 823428d464e16ebf2edaf80de9528d089a3d04ca Mon Sep 17 00:00:00 2001 From: Magdalena Rother Date: Fri, 25 Nov 2016 10:33:43 +0100 Subject: [PATCH 05/24] Change forms structure --- barbeque/forms/__init__.py | 1 + barbeque/{forms.py => forms/mixins.py} | 0 2 files changed, 1 insertion(+) create mode 100644 barbeque/forms/__init__.py rename barbeque/{forms.py => forms/mixins.py} (100%) diff --git a/barbeque/forms/__init__.py b/barbeque/forms/__init__.py new file mode 100644 index 0000000..5c27ca4 --- /dev/null +++ b/barbeque/forms/__init__.py @@ -0,0 +1 @@ +from .mixins import ItemLimitInlineMixin, PlaceholderFormMixin # noqa diff --git a/barbeque/forms.py b/barbeque/forms/mixins.py similarity index 100% rename from barbeque/forms.py rename to barbeque/forms/mixins.py From 9281d67a809438bf5af6bd928514763aeef9c2c7 Mon Sep 17 00:00:00 2001 From: Magdalena Rother Date: Fri, 25 Nov 2016 10:38:06 +0100 Subject: [PATCH 06/24] Add FieldsetRenderer and FloppyformsLayoutMixin --- barbeque/forms/__init__.py | 3 ++- barbeque/forms/mixins.py | 14 ++++++++++++++ barbeque/forms/renderer.py | 39 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 barbeque/forms/renderer.py diff --git a/barbeque/forms/__init__.py b/barbeque/forms/__init__.py index 5c27ca4..73257d0 100644 --- a/barbeque/forms/__init__.py +++ b/barbeque/forms/__init__.py @@ -1 +1,2 @@ -from .mixins import ItemLimitInlineMixin, PlaceholderFormMixin # noqa +from .mixins import ItemLimitInlineMixin, FloppyformsLayoutMixin, PlaceholderFormMixin # noqa +from .renderer import FieldsetRenderer # noqa diff --git a/barbeque/forms/mixins.py b/barbeque/forms/mixins.py index bf0661f..b3f08f6 100644 --- a/barbeque/forms/mixins.py +++ b/barbeque/forms/mixins.py @@ -61,3 +61,17 @@ def get_error_message(self, message, num): message = message.format(num=num, verbose_name=verbose_name) return message + + +class FloppyformsLayoutMixin(object): + row_classname = 'form-row' + div_template_name = 'modules/generic/form/layout/div.html' + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + for name, field in self.fields.items(): + widget = self.fields[name].widget + widget.widget_type = widget.__class__.__name__.lower() + + def as_div(self): + return self._render_as(self.div_template_name) diff --git a/barbeque/forms/renderer.py b/barbeque/forms/renderer.py new file mode 100644 index 0000000..7dc634f --- /dev/null +++ b/barbeque/forms/renderer.py @@ -0,0 +1,39 @@ +from floppyforms.forms import LayoutRenderer + +from .mixins import FloppyformsLayoutMixin + + +class FieldsetRenderer(FloppyformsLayoutMixin, LayoutRenderer, object): + non_field_errors = None + + def __init__(self, form, fields=None, exclude=None, primary=False, template=None): + assert fields or exclude is not None, 'Please provide fields or exclude argument.' + + self.form = form + self.fields = fields or () + self.exclude = exclude or () + self.primary_fieldset = primary + + if template: + self.div_template_name = template + + def __str__(self): + return self.as_div() + + def hidden_fields(self): + return self.form.hidden_fields() if self.primary_fieldset else () + + def non_field_errors(self): + return self.form.non_field_errors() if self.primary_fieldset else () + + def visible_fields(self): + form_visible_fields = self.form.visible_fields() + + if self.fields: + fields = self.fields + else: + fields = [field.name for field in form_visible_fields] + + filtered_fields = [field for field in fields if field not in self.exclude] + + return [field for field in form_visible_fields if field.name in filtered_fields] From 46794a7630c860b8bd93ad3462c2581556891fa2 Mon Sep 17 00:00:00 2001 From: Magdalena Rother Date: Fri, 25 Nov 2016 11:34:43 +0100 Subject: [PATCH 07/24] Don't import renderer by default in forms as it uses floppyforms --- barbeque/forms/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/barbeque/forms/__init__.py b/barbeque/forms/__init__.py index 73257d0..2a72377 100644 --- a/barbeque/forms/__init__.py +++ b/barbeque/forms/__init__.py @@ -1,2 +1 @@ from .mixins import ItemLimitInlineMixin, FloppyformsLayoutMixin, PlaceholderFormMixin # noqa -from .renderer import FieldsetRenderer # noqa From 868a874d89e4ac8b6d4d30835ced0256473d4e6e Mon Sep 17 00:00:00 2001 From: Magdalena Rother Date: Fri, 25 Nov 2016 11:35:35 +0100 Subject: [PATCH 08/24] Add floppyforms to test requirements --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index df4189e..20ddcfc 100644 --- a/setup.py +++ b/setup.py @@ -35,6 +35,7 @@ 'django-polymorphic==0.8.1', 'django-compressor==1.6', 'django-filer==1.1.1', + 'django-floppyforms==1.7.0', 'tox==2.3.1', 'tox-pyenv==1.0.3', ] From c2a823b26e67c55f1a112705aa4145e5918945b9 Mon Sep 17 00:00:00 2001 From: Magdalena Rother Date: Fri, 25 Nov 2016 11:36:08 +0100 Subject: [PATCH 09/24] Test FieldsetRenderer --- barbeque/tests/test_forms_renderer.py | 53 +++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 barbeque/tests/test_forms_renderer.py diff --git a/barbeque/tests/test_forms_renderer.py b/barbeque/tests/test_forms_renderer.py new file mode 100644 index 0000000..733d57f --- /dev/null +++ b/barbeque/tests/test_forms_renderer.py @@ -0,0 +1,53 @@ +from datetime import date + +from django import forms + +from barbeque.forms.renderer import FieldsetRenderer + + +class FooForm(forms.Form): + name = forms.CharField(max_length=100, required=True) + email = forms.CharField(max_length=100, required=True) + birthdate = forms.DateField(required=True) + secret_field = forms.CharField(widget=forms.HiddenInput) + + +class TestRenderer: + + def get_form(self): + return FooForm({ + 'name': 'Test', + 'email': 'foo@bar.com', + 'birthdate': date(1970, 1, 1), + 'secret_field': 'Secret', + }) + + def test_form_valid(self): + form = self.get_form() + valid = form.is_valid() + + assert valid is True + + def test_hidden_fields(self): + form = self.get_form() + renderer = FieldsetRenderer(form, exclude=(), primary=True) + + assert len(renderer.hidden_fields()) == 1 + + def test_non_field_errors(self): + form = self.get_form() + renderer = FieldsetRenderer(form, exclude=(), primary=True) + + assert len(renderer.non_field_errors()) == 0 + + def test_visible_fields(self): + form = self.get_form() + renderer = FieldsetRenderer(form, exclude=(), primary=True) + + assert len(renderer.visible_fields()) == len(form.fields) - 1 + + def test_visible_fields_limited(self): + form = self.get_form() + renderer = FieldsetRenderer(form, fields=('name',), primary=True) + + assert len(renderer.visible_fields()) == 1 From 7048e7886d3d840ceae70ea29f5b7216ecfbf823 Mon Sep 17 00:00:00 2001 From: Magdalena Rother Date: Fri, 25 Nov 2016 12:05:51 +0100 Subject: [PATCH 10/24] Add validators and python-dateutil to test requirements --- barbeque/tests/test_validators.py | 20 ++++++++++++++++++++ barbeque/validators.py | 22 ++++++++++++++++++++++ setup.py | 1 + 3 files changed, 43 insertions(+) create mode 100644 barbeque/tests/test_validators.py create mode 100644 barbeque/validators.py diff --git a/barbeque/tests/test_validators.py b/barbeque/tests/test_validators.py new file mode 100644 index 0000000..c207a0e --- /dev/null +++ b/barbeque/tests/test_validators.py @@ -0,0 +1,20 @@ +from datetime import date +from dateutil.relativedelta import relativedelta + +import pytest +from django.core.exceptions import ValidationError + +from barbeque.validators import AgeValidator + + +class TestAgeValidator: + def setup(self): + self.validator = AgeValidator(10) + + def test_invalid(self): + with pytest.raises(ValidationError): + self.validator( + date.today() - relativedelta(years=10) + relativedelta(days=1)) + + def test_valid(self): + self.validator(date.today() - relativedelta(years=10)) diff --git a/barbeque/validators.py b/barbeque/validators.py new file mode 100644 index 0000000..285b105 --- /dev/null +++ b/barbeque/validators.py @@ -0,0 +1,22 @@ +from dateutil.relativedelta import relativedelta + +from django.core.validators import BaseValidator +from django.utils import timezone +from django.utils.translation import ugettext_lazy as _ + + +class AgeValidator(BaseValidator): + message = _(u'You must be at least %(limit_value)d years old.') + code = 'age' + + def compare(self, value, min_age): + today = timezone.now() + return value > (today - relativedelta(years=min_age)).date() + + +class UniqueEmailValidator(BaseValidator): + message = _('This email address is already in use.') + code = 'unique_email' + + def compare(self, value, qset): + return qset.filter(email__iexact=value).exists() diff --git a/setup.py b/setup.py index 20ddcfc..fee3659 100644 --- a/setup.py +++ b/setup.py @@ -22,6 +22,7 @@ 'mock==1.3.0', 'openpyxl==2.2.6', 'psutil==3.2.1', + 'python-dateutil==2.4.2', 'pytest==3.0.3', 'pytest-cov==2.1.0', 'pytest-pep8==1.0.6', From e79e009bc17a80d30ba68918b8ca7b51cd246f42 Mon Sep 17 00:00:00 2001 From: Magdalena Rother Date: Fri, 25 Nov 2016 13:18:47 +0100 Subject: [PATCH 11/24] Optimise FilerFileField with default null and blank --- barbeque/filer.py | 5 ++++- barbeque/tests/test_filer.py | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/barbeque/filer.py b/barbeque/filer.py index 64872ee..65cb719 100644 --- a/barbeque/filer.py +++ b/barbeque/filer.py @@ -42,6 +42,9 @@ def __init__(self, verbose_name=None, *args, **kwargs): kwargs['verbose_name'] = verbose_name if 'related_name' not in kwargs: kwargs['related_name'] = '+' + if kwargs.pop('blank', False) or kwargs.pop('null', False): + kwargs['null'] = True + kwargs['blank'] = True self.extensions = kwargs.pop('extensions', None) self.alt_text_required = kwargs.pop('alt_text_required', True) @@ -51,7 +54,7 @@ def __init__(self, verbose_name=None, *args, **kwargs): def formfield(self, **kwargs): defaults = { 'extensions': self.extensions, - 'alt_text_required': self.alt_text_required + 'alt_text_required': self.alt_text_required, } defaults.update(kwargs) return super(FilerFileField, self).formfield(**defaults) diff --git a/barbeque/tests/test_filer.py b/barbeque/tests/test_filer.py index fa5ea81..ca703c2 100644 --- a/barbeque/tests/test_filer.py +++ b/barbeque/tests/test_filer.py @@ -72,3 +72,17 @@ class FileModel(models.Model): form_class = forms.models.modelform_factory(FileModel, fields='__all__') assert isinstance(form_class().fields['file'], AdminFileFormField) + + @pytest.mark.django_db + def test_blank_null(self): + class FileModel(models.Model): + file1 = FilerFileField(null=True) + file2 = FilerFileField(blank=True) + file3 = FilerFileField() + + assert FileModel._meta.get_field('file1').blank is True + assert FileModel._meta.get_field('file1').null is True + assert FileModel._meta.get_field('file2').blank is True + assert FileModel._meta.get_field('file2').null is True + assert FileModel._meta.get_field('file3').blank is False + assert FileModel._meta.get_field('file3').null is False From a4ec28a86c7ac571772eaa7823194fecf73dd29e Mon Sep 17 00:00:00 2001 From: Magdalena Rother Date: Fri, 25 Nov 2016 13:57:15 +0100 Subject: [PATCH 12/24] Add hashed_staticfile templatetag --- barbeque/templatetags/barbeque_tags.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/barbeque/templatetags/barbeque_tags.py b/barbeque/templatetags/barbeque_tags.py index b8454b2..71e0a56 100644 --- a/barbeque/templatetags/barbeque_tags.py +++ b/barbeque/templatetags/barbeque_tags.py @@ -1,11 +1,11 @@ import re from django import template +from django.contrib.staticfiles.storage import staticfiles_storage from django.template.defaultfilters import stringfilter from django.utils.html import conditional_escape from django.utils.safestring import mark_safe - register = template.Library() @@ -24,3 +24,11 @@ def set_tag(context, **kwargs): @stringfilter def starspan(value): return mark_safe(STARSPAN_RE.sub(r'\2', conditional_escape(value))) + + +@register.simple_tag +def hashed_staticfile(path): + try: + return staticfiles_storage.hashed_name(path) + except (AttributeError, ValueError): + return path From d9dbdd9d59538f9b072d17ed83024b9efd5c05de Mon Sep 17 00:00:00 2001 From: Magdalena Rother Date: Fri, 25 Nov 2016 14:49:56 +0100 Subject: [PATCH 13/24] Change SharingExtensionToolbar to base class --- barbeque/cms/models.py | 26 -------------------------- barbeque/cms/toolbar.py | 6 ++---- 2 files changed, 2 insertions(+), 30 deletions(-) delete mode 100644 barbeque/cms/models.py diff --git a/barbeque/cms/models.py b/barbeque/cms/models.py deleted file mode 100644 index 13b4571..0000000 --- a/barbeque/cms/models.py +++ /dev/null @@ -1,26 +0,0 @@ -from barbeque.filer import FilerFileField -from cms.extensions import TitleExtension -from cms.extensions.extension_pool import extension_pool -from django.db import models -from django.utils.translation import ugettext_lazy as _ - - -class SharingExtension(TitleExtension): - sharing_title = models.CharField( - _('Sharing title'), max_length=255, blank=True, null=True) - - sharing_description = models.TextField( - _('Sharing description'), blank=True, null=True) - - sharing_image = FilerFileField( - verbose_name=_('Sharing image'), blank=True, null=True, - on_delete=models.SET_NULL) - - class Meta: - verbose_name = _('Sharing Configuration') - verbose_name_plural = _('Sharing Configuration') - - def __str__(self): - return str(self.extended_object) - -extension_pool.register(SharingExtension) diff --git a/barbeque/cms/toolbar.py b/barbeque/cms/toolbar.py index a507874..0553612 100644 --- a/barbeque/cms/toolbar.py +++ b/barbeque/cms/toolbar.py @@ -4,8 +4,6 @@ from cms.toolbar_base import CMSToolbar from cms.toolbar_pool import toolbar_pool from cms.toolbar.items import SideframeItem, ModalItem, SubMenu -from django.utils.translation import ugettext_lazy as _ -from .models import SharingExtension @toolbar_pool.register @@ -47,8 +45,8 @@ def populate(self): @toolbar_pool.register class SharingExtensionToolbar(ExtensionToolbar): - model = SharingExtension - insert_after = _('Advanced settings') + model = None + insert_after = None def get_item_position(self, menu): for items in menu._memo.values(): From acea203947a0c24508d5385801f5c7358e109537 Mon Sep 17 00:00:00 2001 From: Magdalena Rother Date: Mon, 28 Nov 2016 06:31:41 +0100 Subject: [PATCH 14/24] Move page_titleextension to barbeque_tags --- barbeque/templatetags/barbeque_tags.py | 20 ++++++++++++++++++++ barbeque/templatetags/cms_tags.py | 24 ------------------------ 2 files changed, 20 insertions(+), 24 deletions(-) delete mode 100644 barbeque/templatetags/cms_tags.py diff --git a/barbeque/templatetags/barbeque_tags.py b/barbeque/templatetags/barbeque_tags.py index 71e0a56..3a889ee 100644 --- a/barbeque/templatetags/barbeque_tags.py +++ b/barbeque/templatetags/barbeque_tags.py @@ -2,6 +2,7 @@ from django import template from django.contrib.staticfiles.storage import staticfiles_storage +from django.core.exceptions import ObjectDoesNotExist from django.template.defaultfilters import stringfilter from django.utils.html import conditional_escape from django.utils.safestring import mark_safe @@ -32,3 +33,22 @@ def hashed_staticfile(path): return staticfiles_storage.hashed_name(path) except (AttributeError, ValueError): return path + + +@register.simple_tag(takes_context=True) +def page_titleextension(context, page_id, extension): + from cms.models import Page + from cms.utils.moderator import use_draft + try: + page = Page.objects.get(pk=page_id) + if 'request' in context and use_draft(context['request']): + page = page.get_draft_object() + else: + page = page.get_public_object() + except Page.DoesNotExist: + return None + + try: + return getattr(page.get_title_obj(), extension) + except ObjectDoesNotExist: + return None diff --git a/barbeque/templatetags/cms_tags.py b/barbeque/templatetags/cms_tags.py deleted file mode 100644 index bff52a5..0000000 --- a/barbeque/templatetags/cms_tags.py +++ /dev/null @@ -1,24 +0,0 @@ -from cms.models import Page -from cms.utils.moderator import use_draft -from django import template -from django.core.exceptions import ObjectDoesNotExist - - -register = template.Library() - - -@register.simple_tag(takes_context=True) -def page_titleextension(context, page_id, extension): - try: - page = Page.objects.get(pk=page_id) - if 'request' in context and use_draft(context['request']): - page = page.get_draft_object() - else: - page = page.get_public_object() - except Page.DoesNotExist: - return None - - try: - return getattr(page.get_title_obj(), extension) - except ObjectDoesNotExist: - return None From 067980438770572fea0f8eeaafa984b65cf38ec6 Mon Sep 17 00:00:00 2001 From: Magdalena Rother Date: Mon, 28 Nov 2016 09:42:46 +0100 Subject: [PATCH 15/24] Rename SharingExtensionToolbar to TitleExtensionToolbar --- barbeque/cms/toolbar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/barbeque/cms/toolbar.py b/barbeque/cms/toolbar.py index 0553612..767fb7c 100644 --- a/barbeque/cms/toolbar.py +++ b/barbeque/cms/toolbar.py @@ -44,7 +44,7 @@ def populate(self): @toolbar_pool.register -class SharingExtensionToolbar(ExtensionToolbar): +class TitleExtensionToolbar(ExtensionToolbar): model = None insert_after = None From 52ad3cdcdfc89f026489624efe352b1abcc3ccaf Mon Sep 17 00:00:00 2001 From: Magdalena Rother Date: Mon, 28 Nov 2016 09:43:51 +0100 Subject: [PATCH 16/24] Use force_text instead of str in toolbars --- barbeque/cms/toolbar.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/barbeque/cms/toolbar.py b/barbeque/cms/toolbar.py index 767fb7c..a712c57 100644 --- a/barbeque/cms/toolbar.py +++ b/barbeque/cms/toolbar.py @@ -1,3 +1,5 @@ +from django.utils.encoding import force_text + from cms.cms_toolbars import ( ADMIN_MENU_IDENTIFIER, PAGE_MENU_IDENTIFIER) from cms.extensions.toolbar import ExtensionToolbar @@ -51,8 +53,8 @@ class TitleExtensionToolbar(ExtensionToolbar): def get_item_position(self, menu): for items in menu._memo.values(): for item in items: - if str(getattr(item, 'name', None)) in ( - str(self.insert_after), + if force_text(getattr(item, 'name', None)) in ( + force_text(self.insert_after), '{0}...'.format(self.insert_after) ): return menu._item_position(item) + 1 From 4e46500acf4e240618d6a230a639195f3e66ee14 Mon Sep 17 00:00:00 2001 From: Magdalena Rother Date: Mon, 28 Nov 2016 09:51:29 +0100 Subject: [PATCH 17/24] Change usega of str in barbeque to force_text --- barbeque/anylink.py | 3 ++- barbeque/files.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/barbeque/anylink.py b/barbeque/anylink.py index 817785d..701f042 100644 --- a/barbeque/anylink.py +++ b/barbeque/anylink.py @@ -5,6 +5,7 @@ from cms.utils import get_cms_setting from django.core.cache import cache from django.db import models +from django.utils.encoding import force_text from django.utils.six.moves.urllib import parse as urlparse from django.utils.translation import get_language, ugettext_lazy as _ @@ -22,7 +23,7 @@ def configure_model(self, model): def get_absolute_url(self, link): cache_key = '{0}anylink-page-url:{1}:{2}'.format( - get_cms_setting('CACHE_PREFIX'), str(link.page_id), get_language()) + get_cms_setting('CACHE_PREFIX'), force_text(link.page_id), get_language()) url = cache.get(cache_key) if url is None: diff --git a/barbeque/files.py b/barbeque/files.py index 640eca1..5ab08d7 100644 --- a/barbeque/files.py +++ b/barbeque/files.py @@ -5,6 +5,7 @@ from django.template.defaultfilters import slugify from django.utils.deconstruct import deconstructible +from django.utils.encoding import force_text @deconstructible @@ -32,7 +33,7 @@ def __call__(self, instance, filename): filename_parts = filename.rsplit('.', 1) if self.uuid_filename: - filename = str(uuid.uuid4()) + filename = force_text(uuid.uuid4()) else: filename = slugify(filename_parts[0]) From 93fa252553936f2ceba5f0fc2c282bca1ebe5e35 Mon Sep 17 00:00:00 2001 From: Magdalena Rother Date: Mon, 28 Nov 2016 11:07:05 +0100 Subject: [PATCH 18/24] Add deprecation warning when importing forms mixins through init --- barbeque/forms/__init__.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/barbeque/forms/__init__.py b/barbeque/forms/__init__.py index 2a72377..35b1c86 100644 --- a/barbeque/forms/__init__.py +++ b/barbeque/forms/__init__.py @@ -1 +1,10 @@ +import warnings + from .mixins import ItemLimitInlineMixin, FloppyformsLayoutMixin, PlaceholderFormMixin # noqa + + +message = """Importing mixins directly from barbeque.forms is deprecated. +Use barbeque.forms.mixins instead.""" + +warnings.simplefilter('always', DeprecationWarning) +warnings.warn(message, DeprecationWarning) From a46d841a2fc0848f6db4ca6ebdce5d8c3b3d00fc Mon Sep 17 00:00:00 2001 From: Magdalena Rother Date: Mon, 28 Nov 2016 11:11:01 +0100 Subject: [PATCH 19/24] Remove onject from FieldsetRenderer --- barbeque/forms/renderer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/barbeque/forms/renderer.py b/barbeque/forms/renderer.py index 7dc634f..c55384a 100644 --- a/barbeque/forms/renderer.py +++ b/barbeque/forms/renderer.py @@ -3,7 +3,7 @@ from .mixins import FloppyformsLayoutMixin -class FieldsetRenderer(FloppyformsLayoutMixin, LayoutRenderer, object): +class FieldsetRenderer(FloppyformsLayoutMixin, LayoutRenderer): non_field_errors = None def __init__(self, form, fields=None, exclude=None, primary=False, template=None): From 0722057886fe4a3788e4fc8aa47ca32ef615c08e Mon Sep 17 00:00:00 2001 From: Magdalena Rother Date: Mon, 28 Nov 2016 11:21:52 +0100 Subject: [PATCH 20/24] Import cms in templatetags as try-except --- barbeque/templatetags/barbeque_tags.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/barbeque/templatetags/barbeque_tags.py b/barbeque/templatetags/barbeque_tags.py index 3a889ee..87fd41d 100644 --- a/barbeque/templatetags/barbeque_tags.py +++ b/barbeque/templatetags/barbeque_tags.py @@ -7,6 +7,12 @@ from django.utils.html import conditional_escape from django.utils.safestring import mark_safe +try: + from cms.models import Page + from cms.utils.moderator import use_draft +except ImportError: + pass + register = template.Library() @@ -37,14 +43,14 @@ def hashed_staticfile(path): @register.simple_tag(takes_context=True) def page_titleextension(context, page_id, extension): - from cms.models import Page - from cms.utils.moderator import use_draft try: page = Page.objects.get(pk=page_id) if 'request' in context and use_draft(context['request']): page = page.get_draft_object() else: page = page.get_public_object() + except NameError: + raise ImportError('django-cms is required when using page_titleextension tag') except Page.DoesNotExist: return None From 786e3f0fd5f9e7c5f2a91dbd10d38e8a4112102a Mon Sep 17 00:00:00 2001 From: Magdalena Rother Date: Mon, 28 Nov 2016 12:05:01 +0100 Subject: [PATCH 21/24] Add translations --- barbeque/locale/de/LC_MESSAGES/django.mo | Bin 0 -> 1420 bytes barbeque/locale/de/LC_MESSAGES/django.po | 62 +++++++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 barbeque/locale/de/LC_MESSAGES/django.mo create mode 100644 barbeque/locale/de/LC_MESSAGES/django.po diff --git a/barbeque/locale/de/LC_MESSAGES/django.mo b/barbeque/locale/de/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..dd0b1de410221ae8919f77262806072567ef909d GIT binary patch literal 1420 zcmaKr&2A$_6onfY{*3qu!wwdg#Yi(u+OY$SA}2x9W48x`9Y?kk&9E7j-Pi4c>S|L} zZBK~u5=guTOIBGRdCN1vf;I2Jsg5NlBZiVnU-#`>Rp;J1{pW|behDz%!Tbc%VQyjm zf$7HwuLQwo;1>86cmTc&#^8J45ZnRBU0h zzZL{5Z3GA?jF_ajW55rU#r(@NDK$Okbd)Z-qSc5guQ;)isSqbkW^# zWQ4X#xZE_TebVjHQPhj+Anrw-?!&0n8pOllS4h=qfdax2i>yK2=;-l@mweFeK2)-r zn{<-{sl84w-qcsG)+)_h!vHw$Y!2PtUpsRpq~52=3=L&>crt2-dz(BjYsw~!^(0T= z!3OP(rK=pP!Uz?%bCba~dS1TT!`*$ldOWC;21-`lSQ?bW5BL^4d}+a+nrGv$bJ zNn&GmLnElUGWC$9WBduFpJ7mc50VY*GUi=3Acyk#n!WpZCuRd;5k&!5-{svI$ Va&@WgWQMZKCeu#^g37Pa!M~;*o+, YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-11-28 11:54+0100\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: barbeque/anylink.py:15 +msgid "CMS Page" +msgstr "CMS Seite" + +#: barbeque/exporter.py:119 +msgid "Export as CSV" +msgstr "Als CSV exportieren" + +#: barbeque/exporter.py:125 +msgid "Export as XLSX" +msgstr "Als XLSX exportieren" + +#: barbeque/filer.py:27 +#, python-brace-format +msgid "Invalid file extension, allowed extensions: {0}" +msgstr "Ungültige Dateierweiterung, erlaubte Erweiterungen: {0}" + +#: barbeque/filer.py:33 +msgid "Alternative text is missing for this file." +msgstr "Für diese Datei fehlt ein alternativer Text." + +#: barbeque/forms/mixins.py:32 +#, python-brace-format +msgid "Please provide at least {num} {verbose_name}." +msgstr "Bitte geben Sie mindestens {num} {verbose_name}." + +#: barbeque/forms/mixins.py:33 +#, python-brace-format +msgid "Please provide at most {num} {verbose_name}." +msgstr "Bitte geben Sie höchstens {num} {verbose_name}." + +#: barbeque/validators.py:9 +#, python-format +msgid "You must be at least %(limit_value)d years old." +msgstr "Sie müssen mindestens %(limit_value)d Jahre alt sein." + +#: barbeque/validators.py:18 +msgid "This email address is already in use." +msgstr "Diese E-Mail-Adresse ist bereits in Verwendung." + +#: barbeque/views/mixins.py:30 +msgid "You must be logged in to access the requested page." +msgstr "Sie müssen angemeldet sein, um auf die gewünschte Seite zu gelangen" From 9f55a39f969da38fb9e57e722d947521128cf05a Mon Sep 17 00:00:00 2001 From: Magdalena Rother Date: Tue, 29 Nov 2016 10:16:35 +0100 Subject: [PATCH 22/24] Update translations --- barbeque/locale/de/LC_MESSAGES/django.mo | Bin 1420 -> 1390 bytes barbeque/locale/de/LC_MESSAGES/django.po | 8 ++++---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/barbeque/locale/de/LC_MESSAGES/django.mo b/barbeque/locale/de/LC_MESSAGES/django.mo index dd0b1de410221ae8919f77262806072567ef909d..291dd02f42b119db1dcafd79380538928c28dcf4 100644 GIT binary patch delta 223 zcmeC-e#bST##e)Zf#EST0|O_J=3!xA5C+m>Kw1PyYXNBkAZ-t%wSaU1kX8lKvw^fe zkUj{c&4BbrAngjIH8!4jP;EAngLA4S;kllwJp<^@05R zK-vsQ^RqHAxB_XnjVGm;b=(f`DN;zuOf61Ta7ipl%~VKB&B!THNX^VsNX#ioEy_zQ z$t+7PQV2<{D4EQ~;$~S?o|*|1FU?EWQAjPyNi0n&NmXz~h^FRQDO4NOGB{ zlTz~(f-_SUawiwE$QkNkh-Vz$mYh*slA2enP@PwrTdPnFv@R*XI5j>mF*mh#@_v@} E00&h^i2wiq diff --git a/barbeque/locale/de/LC_MESSAGES/django.po b/barbeque/locale/de/LC_MESSAGES/django.po index a455954..b2d93ef 100644 --- a/barbeque/locale/de/LC_MESSAGES/django.po +++ b/barbeque/locale/de/LC_MESSAGES/django.po @@ -32,21 +32,21 @@ msgstr "Als XLSX exportieren" #: barbeque/filer.py:27 #, python-brace-format msgid "Invalid file extension, allowed extensions: {0}" -msgstr "Ungültige Dateierweiterung, erlaubte Erweiterungen: {0}" +msgstr "Ungültige Dateiendung, erlaubte Dateitypen: {0}" #: barbeque/filer.py:33 msgid "Alternative text is missing for this file." -msgstr "Für diese Datei fehlt ein alternativer Text." +msgstr "Bildbeschreibung fehlt für diese Datei." #: barbeque/forms/mixins.py:32 #, python-brace-format msgid "Please provide at least {num} {verbose_name}." -msgstr "Bitte geben Sie mindestens {num} {verbose_name}." +msgstr "Mindestens {num} {verbose_name} benötigt." #: barbeque/forms/mixins.py:33 #, python-brace-format msgid "Please provide at most {num} {verbose_name}." -msgstr "Bitte geben Sie höchstens {num} {verbose_name}." +msgstr "Maximal {num} {verbose_name} erlaubt." #: barbeque/validators.py:9 #, python-format From 1c1e89d5c4615e26b235d1933b5892b1adbdb0dd Mon Sep 17 00:00:00 2001 From: Stephan Jaekel Date: Tue, 29 Nov 2016 15:12:23 +0100 Subject: [PATCH 23/24] Some updates. --- barbeque/anylink.py | 3 +-- barbeque/cms/toolbar.py | 4 ++-- barbeque/files.py | 6 +++--- barbeque/forms/__init__.py | 11 +++++------ barbeque/templatetags/barbeque_tags.py | 3 ++- barbeque/templatetags/buildcompress.py | 11 +++++++---- setup.py | 25 ++++++++++++++++++++++--- 7 files changed, 42 insertions(+), 21 deletions(-) diff --git a/barbeque/anylink.py b/barbeque/anylink.py index 701f042..817785d 100644 --- a/barbeque/anylink.py +++ b/barbeque/anylink.py @@ -5,7 +5,6 @@ from cms.utils import get_cms_setting from django.core.cache import cache from django.db import models -from django.utils.encoding import force_text from django.utils.six.moves.urllib import parse as urlparse from django.utils.translation import get_language, ugettext_lazy as _ @@ -23,7 +22,7 @@ def configure_model(self, model): def get_absolute_url(self, link): cache_key = '{0}anylink-page-url:{1}:{2}'.format( - get_cms_setting('CACHE_PREFIX'), force_text(link.page_id), get_language()) + get_cms_setting('CACHE_PREFIX'), str(link.page_id), get_language()) url = cache.get(cache_key) if url is None: diff --git a/barbeque/cms/toolbar.py b/barbeque/cms/toolbar.py index a712c57..95c149a 100644 --- a/barbeque/cms/toolbar.py +++ b/barbeque/cms/toolbar.py @@ -1,7 +1,6 @@ from django.utils.encoding import force_text -from cms.cms_toolbars import ( - ADMIN_MENU_IDENTIFIER, PAGE_MENU_IDENTIFIER) +from cms.cms_toolbars import ADMIN_MENU_IDENTIFIER, PAGE_MENU_IDENTIFIER from cms.extensions.toolbar import ExtensionToolbar from cms.toolbar_base import CMSToolbar from cms.toolbar_pool import toolbar_pool @@ -10,6 +9,7 @@ @toolbar_pool.register class ForceModalDialogToolbar(CMSToolbar): + def rebuild_menu(self, menu): items = [] for item in menu.items: diff --git a/barbeque/files.py b/barbeque/files.py index 5ab08d7..169bf0d 100644 --- a/barbeque/files.py +++ b/barbeque/files.py @@ -37,13 +37,13 @@ def __call__(self, instance, filename): else: filename = slugify(filename_parts[0]) - extension = len(filename_parts) > 1 and '.{0}'.format(filename_parts[-1]) or '' + extension = len(filename_parts) > 1 and u'.{0}'.format(filename_parts[-1]) or '' - return os.path.join(path, '{0}{1}'.format(filename, extension)) + return os.path.join(path, u'{0}{1}'.format(filename, extension)) def upload_to_path(base_path, attr=None, uuid_filename=False): - return UploadToPath(base_path=base_path, attr=attr, uuid_filename=uuid_filename) + return UploadToPath(base_path, attr=attr, uuid_filename=uuid_filename) class MoveableNamedTemporaryFile(object): diff --git a/barbeque/forms/__init__.py b/barbeque/forms/__init__.py index 35b1c86..1ab0c7e 100644 --- a/barbeque/forms/__init__.py +++ b/barbeque/forms/__init__.py @@ -1,10 +1,9 @@ import warnings -from .mixins import ItemLimitInlineMixin, FloppyformsLayoutMixin, PlaceholderFormMixin # noqa +from .mixins import FloppyformsLayoutMixin, ItemLimitInlineMixin, PlaceholderFormMixin # noqa -message = """Importing mixins directly from barbeque.forms is deprecated. -Use barbeque.forms.mixins instead.""" - -warnings.simplefilter('always', DeprecationWarning) -warnings.warn(message, DeprecationWarning) +warnings.warn(( + 'Importing mixins directly from barbeque.forms is deprecated and will be removed ' + 'in barbeque 1.3. Use barbeque.forms.mixins instead.' +), DeprecationWarning) diff --git a/barbeque/templatetags/barbeque_tags.py b/barbeque/templatetags/barbeque_tags.py index 87fd41d..0e309ba 100644 --- a/barbeque/templatetags/barbeque_tags.py +++ b/barbeque/templatetags/barbeque_tags.py @@ -50,7 +50,8 @@ def page_titleextension(context, page_id, extension): else: page = page.get_public_object() except NameError: - raise ImportError('django-cms is required when using page_titleextension tag') + raise ImportError( + 'django-cms is required when using page_titleextension tag') except Page.DoesNotExist: return None diff --git a/barbeque/templatetags/buildcompress.py b/barbeque/templatetags/buildcompress.py index 2223f6c..63d5943 100644 --- a/barbeque/templatetags/buildcompress.py +++ b/barbeque/templatetags/buildcompress.py @@ -4,8 +4,7 @@ try: from compressor.templatetags.compress import CompressorNode, OUTPUT_FILE except ImportError: - CompressorNode = None - OUTPUT_FILE = None + pass register = template.Library() @@ -29,7 +28,11 @@ def buildcompress(parser, token): args = token.split_contents() assert len(args) == 2, 'Invalid arguments to buildcompress.' - if settings.DEBUG or not CompressorNode: + if settings.DEBUG: return BuildCompressNoopNode() - return CompressorNode(nodelist, args[1], OUTPUT_FILE, None) + try: + return CompressorNode(nodelist, args[1], OUTPUT_FILE, None) + except NameError: + raise ImportError( + 'django-compressor is required when using buildcompress tag') diff --git a/setup.py b/setup.py index fee3659..aea25f0 100644 --- a/setup.py +++ b/setup.py @@ -9,6 +9,24 @@ version = '1.1.1' +# TEMPORARY FIX FOR +# https://bitbucket.org/pypa/setuptools/issues/450/egg_info-command-is-very-slow-if-there-are +TO_OMIT = ['.git', '.tox'] +orig_os_walk = os.walk + + +def patched_os_walk(path, *args, **kwargs): + for (dirpath, dirnames, filenames) in orig_os_walk(path, *args, **kwargs): + if '.git' in dirnames: + # We're probably in our own root directory. + print("MONKEY PATCH: omitting a few directories like .git and .tox...") + dirnames[:] = list(set(dirnames) - set(TO_OMIT)) + yield (dirpath, dirnames, filenames) + +os.walk = patched_os_walk +# END IF TEMPORARY FIX. + + if sys.argv[-1] == 'publish': os.system('python setup.py sdist upload') os.system('python setup.py bdist_wheel upload') @@ -24,15 +42,16 @@ 'psutil==3.2.1', 'python-dateutil==2.4.2', 'pytest==3.0.3', - 'pytest-cov==2.1.0', + 'pytest-cov==2.3.1', 'pytest-pep8==1.0.6', 'pytest-flakes==1.0.1', 'pytest-django==3.0.0', - 'factory-boy==2.5.2', + 'pytest-isort==0.1.0', + 'factory-boy==2.7.0', 'Pillow==3.4.0', 'django-anylink==0.3.0', 'django-treebeard>=4.0', - 'django-cms==3.2.5', + 'django-cms==3.3.3', 'django-polymorphic==0.8.1', 'django-compressor==1.6', 'django-filer==1.1.1', From 06a16e3054073ddaec88f8bc73e804be9d8eab58 Mon Sep 17 00:00:00 2001 From: Stephan Jaekel Date: Tue, 29 Nov 2016 21:19:34 +0100 Subject: [PATCH 24/24] Testing for new features. --- barbeque/cms/toolbar.py | 7 +- barbeque/forms/mixins.py | 2 +- barbeque/templatetags/barbeque_tags.py | 3 + barbeque/tests/cms_urls.py | 2 + barbeque/tests/resources/cmsapp/__init__.py | 0 barbeque/tests/resources/cmsapp/admin.py | 7 + .../tests/resources/cmsapp/cms_toolbars.py | 12 ++ barbeque/tests/resources/cmsapp/models.py | 15 ++ barbeque/tests/resources/mockapp/models.py | 1 + barbeque/tests/settings.py | 42 ++-- barbeque/tests/test_cms.py | 23 +- .../{test_forms.py => test_forms_mixins.py} | 24 ++- barbeque/tests/test_forms_renderer.py | 16 ++ barbeque/tests/test_static_files.py | 200 ------------------ barbeque/tests/test_staticfiles.py | 10 +- barbeque/tests/test_templatetags.py | 90 ++++++++ barbeque/tests/test_validators.py | 22 +- conftest.py | 8 +- pytest.ini | 2 +- 19 files changed, 243 insertions(+), 243 deletions(-) create mode 100644 barbeque/tests/resources/cmsapp/__init__.py create mode 100644 barbeque/tests/resources/cmsapp/admin.py create mode 100644 barbeque/tests/resources/cmsapp/cms_toolbars.py create mode 100644 barbeque/tests/resources/cmsapp/models.py rename barbeque/tests/{test_forms.py => test_forms_mixins.py} (83%) delete mode 100644 barbeque/tests/test_static_files.py diff --git a/barbeque/cms/toolbar.py b/barbeque/cms/toolbar.py index 95c149a..be67d8c 100644 --- a/barbeque/cms/toolbar.py +++ b/barbeque/cms/toolbar.py @@ -45,21 +45,22 @@ def populate(self): self.rebuild_menu(menu) -@toolbar_pool.register class TitleExtensionToolbar(ExtensionToolbar): model = None insert_after = None def get_item_position(self, menu): + position = None for items in menu._memo.values(): for item in items: if force_text(getattr(item, 'name', None)) in ( force_text(self.insert_after), '{0}...'.format(self.insert_after) ): - return menu._item_position(item) + 1 + position = menu._item_position(item) + 1 + break - return None + return position def populate(self): current_page_menu = self._setup_extension_toolbar() diff --git a/barbeque/forms/mixins.py b/barbeque/forms/mixins.py index b3f08f6..036365e 100644 --- a/barbeque/forms/mixins.py +++ b/barbeque/forms/mixins.py @@ -68,7 +68,7 @@ class FloppyformsLayoutMixin(object): div_template_name = 'modules/generic/form/layout/div.html' def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) + super(FloppyformsLayoutMixin, self).__init__(*args, **kwargs) for name, field in self.fields.items(): widget = self.fields[name].widget widget.widget_type = widget.__class__.__name__.lower() diff --git a/barbeque/templatetags/barbeque_tags.py b/barbeque/templatetags/barbeque_tags.py index 0e309ba..94c6a5c 100644 --- a/barbeque/templatetags/barbeque_tags.py +++ b/barbeque/templatetags/barbeque_tags.py @@ -55,6 +55,9 @@ def page_titleextension(context, page_id, extension): except Page.DoesNotExist: return None + if not page: + return None + try: return getattr(page.get_title_obj(), extension) except ObjectDoesNotExist: diff --git a/barbeque/tests/cms_urls.py b/barbeque/tests/cms_urls.py index 7751e92..77c06f3 100644 --- a/barbeque/tests/cms_urls.py +++ b/barbeque/tests/cms_urls.py @@ -1,8 +1,10 @@ from django.conf.urls import include, url from django.contrib import admin +from django.views.generic import TemplateView urlpatterns = [ url(r'^admin/', include(admin.site.urls)), + url(r'^non-cms/', TemplateView.as_view(template_name='empty_template.html')), url(r'', include('cms.urls')), ] diff --git a/barbeque/tests/resources/cmsapp/__init__.py b/barbeque/tests/resources/cmsapp/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/barbeque/tests/resources/cmsapp/admin.py b/barbeque/tests/resources/cmsapp/admin.py new file mode 100644 index 0000000..4e61b30 --- /dev/null +++ b/barbeque/tests/resources/cmsapp/admin.py @@ -0,0 +1,7 @@ +from cms.extensions import TitleExtensionAdmin +from django.contrib import admin + +from .models import ExtensionModel + + +admin.site.register(ExtensionModel, TitleExtensionAdmin) diff --git a/barbeque/tests/resources/cmsapp/cms_toolbars.py b/barbeque/tests/resources/cmsapp/cms_toolbars.py new file mode 100644 index 0000000..181172b --- /dev/null +++ b/barbeque/tests/resources/cmsapp/cms_toolbars.py @@ -0,0 +1,12 @@ +from cms.toolbar_pool import toolbar_pool + +from barbeque.cms.toolbar import TitleExtensionToolbar + +from .models import ExtensionModel + + +class ExtensionToolbar(TitleExtensionToolbar): + model = ExtensionModel + insert_after = 'Advanced settings' + +toolbar_pool.register(ExtensionToolbar) diff --git a/barbeque/tests/resources/cmsapp/models.py b/barbeque/tests/resources/cmsapp/models.py new file mode 100644 index 0000000..8b793fc --- /dev/null +++ b/barbeque/tests/resources/cmsapp/models.py @@ -0,0 +1,15 @@ +from cms.extensions import TitleExtension +from cms.extensions.extension_pool import extension_pool +from django.db import models + + +class ExtensionModel(TitleExtension): + name = models.CharField(max_length=255) + + class Meta: + verbose_name = 'Extension' + + def __unicode__(self): + return self.name + +extension_pool.register(ExtensionModel) diff --git a/barbeque/tests/resources/mockapp/models.py b/barbeque/tests/resources/mockapp/models.py index 34a5dd6..9afcb1f 100644 --- a/barbeque/tests/resources/mockapp/models.py +++ b/barbeque/tests/resources/mockapp/models.py @@ -13,3 +13,4 @@ class RelatedMockModel(models.Model): class DummyModel(models.Model): name = models.CharField(max_length=256) slug = models.SlugField() + email = models.EmailField(blank=True) diff --git a/barbeque/tests/settings.py b/barbeque/tests/settings.py index 4151b31..8d58d70 100644 --- a/barbeque/tests/settings.py +++ b/barbeque/tests/settings.py @@ -1,8 +1,6 @@ import os import tempfile -import django - DEBUG = True @@ -33,6 +31,7 @@ 'barbeque', 'barbeque.tests.resources.mockapp', + 'barbeque.tests.resources.cmsapp', ) ROOT_URLCONF = 'django.contrib.auth.urls' @@ -53,29 +52,18 @@ 'barbeque.anylink.CmsPageLink', ) -if django.VERSION < (1, 8): - TEMPLATE_DIRS = ( - os.path.join(os.path.dirname(__file__), 'resources', 'templates'), - ) - - TEMPLATE_CONTEXT_PROCESSORS = ( - 'django.core.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'barbeque.context_processors.settings', - ) -else: - TEMPLATES = [ - { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [os.path.join(os.path.dirname(__file__), 'resources', 'templates')], - 'APP_DIRS': True, - 'OPTIONS': { - 'debug': True, - 'context_processors': [ - 'django.core.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'barbeque.context_processors.settings', - ], - }, +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [os.path.join(os.path.dirname(__file__), 'resources', 'templates')], + 'APP_DIRS': True, + 'OPTIONS': { + 'debug': True, + 'context_processors': [ + 'django.core.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'barbeque.context_processors.settings', + ], }, - ] + }, +] diff --git a/barbeque/tests/test_cms.py b/barbeque/tests/test_cms.py index 0abeeb8..de65641 100644 --- a/barbeque/tests/test_cms.py +++ b/barbeque/tests/test_cms.py @@ -1,5 +1,6 @@ +import mock from cms.api import create_page -from cms.toolbar.items import SideframeItem +from cms.toolbar.items import ModalItem, SideframeItem def test_forcemodaldialogtoolbar(admin_client, activate_cms): @@ -9,3 +10,23 @@ def test_forcemodaldialogtoolbar(admin_client, activate_cms): toolbar = response.context['request'].toolbar for item in toolbar.get_menu('admin-menu').items: assert item.__class__ is not SideframeItem + + +def test_titleextensiontoolbar_inserted(admin_client, activate_cms): + page = create_page('Test Page', 'INHERIT', 'en-us') + + response = admin_client.get('{0}?edit=on'.format(page.get_absolute_url())) + toolbar = response.context['request'].toolbar + menu = toolbar.get_menu('page') + item = menu.items[5] + assert isinstance(item, ModalItem) + assert item.name == 'Extension...' + assert item.url.startswith('/admin/cmsapp/extensionmodel/') + + +@mock.patch('barbeque.cms.toolbar.TitleExtensionToolbar.get_item_position') +def test_titleextensiontoolbar_not_inserted(position_mock, admin_client, activate_cms): + response = admin_client.get('/non-cms/') + toolbar = response.context['request'].toolbar + assert toolbar.get_menu('page') is None + assert position_mock.called is False diff --git a/barbeque/tests/test_forms.py b/barbeque/tests/test_forms_mixins.py similarity index 83% rename from barbeque/tests/test_forms.py rename to barbeque/tests/test_forms_mixins.py index f7b7986..e34c1fd 100644 --- a/barbeque/tests/test_forms.py +++ b/barbeque/tests/test_forms_mixins.py @@ -1,10 +1,13 @@ +import mock import pytest +import floppyforms.__future__ as floppyforms from django import forms from django.core.exceptions import ValidationError from django.forms.models import inlineformset_factory, BaseInlineFormSet -from barbeque.forms import PlaceholderFormMixin, ItemLimitInlineMixin +from barbeque.forms.mixins import ( + FloppyformsLayoutMixin, PlaceholderFormMixin, ItemLimitInlineMixin) from barbeque.tests.resources.mockapp.models import MockModel, RelatedMockModel @@ -108,3 +111,22 @@ def test_max_forms_invalid(self): assert exc.value.messages == [ 'Please provide at most 2 related mock models.'] + + +class FloppyformsLayoutForm(FloppyformsLayoutMixin, floppyforms.Form): + name = forms.CharField(max_length=255, label='Name Label') + + class Meta: + fields = '__all__' + + +class TestFloppyformsLayoutMixin: + + def test_widget_type(self): + form = FloppyformsLayoutForm() + assert form.fields['name'].widget.widget_type == 'textinput' + + @mock.patch('barbeque.tests.test_forms_mixins.FloppyformsLayoutForm._render_as') + def test_as_div(self, render_mock): + FloppyformsLayoutForm().as_div() + assert render_mock.call_args[0][0] == 'modules/generic/form/layout/div.html' diff --git a/barbeque/tests/test_forms_renderer.py b/barbeque/tests/test_forms_renderer.py index 733d57f..1bd0911 100644 --- a/barbeque/tests/test_forms_renderer.py +++ b/barbeque/tests/test_forms_renderer.py @@ -1,5 +1,6 @@ from datetime import date +import mock from django import forms from barbeque.forms.renderer import FieldsetRenderer @@ -51,3 +52,18 @@ def test_visible_fields_limited(self): renderer = FieldsetRenderer(form, fields=('name',), primary=True) assert len(renderer.visible_fields()) == 1 + + def test_template(self): + form = self.get_form() + renderer = FieldsetRenderer(form, exclude=(), primary=True) + assert renderer.div_template_name == 'modules/generic/form/layout/div.html' + + renderer = FieldsetRenderer(form, exclude=(), primary=True, template='foo.html') + assert renderer.div_template_name == 'foo.html' + + @mock.patch('barbeque.forms.renderer.FieldsetRenderer.as_div') + def test_str(self, div_mock): + form = self.get_form() + renderer = FieldsetRenderer(form, exclude=(), primary=True) + renderer.__str__() + assert div_mock.called is True diff --git a/barbeque/tests/test_static_files.py b/barbeque/tests/test_static_files.py deleted file mode 100644 index 59818e2..0000000 --- a/barbeque/tests/test_static_files.py +++ /dev/null @@ -1,200 +0,0 @@ -import os -import mock -from collections import OrderedDict - -import pytest -from django.http import HttpResponse, HttpResponseNotFound, HttpResponsePermanentRedirect -from django.shortcuts import redirect -from django.conf.urls import url - -from barbeque.staticfiles.middleware import ServeStaticFileMiddleware - - -def foo_view(request): - return redirect('bar', permanent=True) - - -def bar_view(request): - return HttpResponse('Hllo FooBar!') - - -urlpatterns = [ - url(r'^bar$', bar_view, name='bar'), - url(r'^foo$', foo_view, name='foo'), - -] - - -class TestServeStaticFileMiddleware: - - @pytest.fixture(autouse=True) - def setup(self, settings): - settings.ROOT_DIR = os.path.dirname(os.path.dirname(__file__)) - settings.STATIC_ROOT = os.path.join(settings.ROOT_DIR, 'tests', 'resources', 'static') - # settings.STATICFILES_STORAGE = ( - # 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage') - settings.STATICFILES_STORAGE = ( - 'barbeque.staticfiles.storage.CompactManifestStaticFilesStorage') - - @pytest.fixture - def patch_settings(self, settings): - """ - Patch settings for tests fith django client - """ - settings.STATICFILES_FINDERS = ( - 'django.contrib.staticfiles.finders.FileSystemFinder', - 'django.contrib.staticfiles.finders.AppDirectoriesFinder', - 'compressor.finders.CompressorFinder', - ) - settings.MIDDLEWARE_CLASSES = [ - 'barbeque.staticfiles.middleware.ServeStaticFileMiddleware', - ] - settings.INSTALLED_APPS = settings.INSTALLED_APPS + ('django.contrib.staticfiles',) - settings.ROOT_URLCONF = 'barbeque.tests.test_static_files' - - def test_file_exists(self, rf): - request = rf.get('/static/test.jpg') - middleware = ServeStaticFileMiddleware() - response = middleware.process_response(request, HttpResponseNotFound('')) - assert response.status_code == 200 - assert response['Content-Type'] == 'image/jpeg' - assert len(response.items()) == 3 - assert response.has_header('Content-Length') - assert response.has_header('Last-Modified') - - def test_file_missing(self, rf): - request = rf.get('/static/doesnotexist.jpg') - middleware = ServeStaticFileMiddleware() - response = middleware.process_response(request, HttpResponseNotFound('')) - assert response.status_code == 404 - - def test_unknown_prefix(self, rf): - request = rf.get('/foo/test.jpg') - middleware = ServeStaticFileMiddleware() - response = middleware.process_response(request, HttpResponseNotFound('')) - assert response.status_code == 404 - - def test_redirect_for_static(self, rf): - request = rf.get('/static/test.jpg') - middleware = ServeStaticFileMiddleware() - response = middleware.process_response( - request, HttpResponsePermanentRedirect('/static/test.jpg/')) - assert response.status_code == 200 - - def test_redirect_other(self, rf): - request = rf.get('/foo') - middleware = ServeStaticFileMiddleware() - redirect = HttpResponsePermanentRedirect('/foo/') - response = middleware.process_response(request, redirect) - assert response == redirect - - @mock.patch('barbeque.staticfiles.middleware.ServeStaticFileMiddleware.process_response') - def test_new_style_middleware(self, process_response_mock, rf): - request = rf.get('/static/test.jpg') - get_response_mock = mock.Mock() - get_response_mock.return_value = HttpResponseNotFound() - middleware = ServeStaticFileMiddleware(get_response=get_response_mock) - middleware(request) - get_response_mock.assert_called_with(request) - process_response_mock.assert_called_with( - request, get_response_mock.return_value) - - def test_with_client_hit(self, client, patch_settings): - response = client.get('/static/test.jpg') - assert response.status_code == 200 - - def test_with_client_redirect(self, client, patch_settings): - response = client.get('/foo') - assert response.status_code == 301 - assert response['Location'].endswith('/bar') - - def test_with_client_query_params(self, client, patch_settings): - response = client.get('/static/test.jpg?v=1') - assert response.status_code == 200 - assert response['Content-Type'] == 'image/jpeg' - - -class TestServeStaticFileMiddlewareWithHashedFiles: - - @pytest.fixture(autouse=True) - def setup(self, settings): - settings.ROOT_DIR = os.path.dirname(os.path.dirname(__file__)) - settings.STATIC_ROOT = os.path.join(settings.ROOT_DIR, 'tests', 'resources', 'static') - settings.STATICFILES_STORAGE = ( - 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage') - - @pytest.fixture - def patch_settings(self, settings): - """ - Patch settings for tests fith django client - """ - settings.STATICFILES_FINDERS = ( - 'django.contrib.staticfiles.finders.FileSystemFinder', - 'django.contrib.staticfiles.finders.AppDirectoriesFinder', - 'compressor.finders.CompressorFinder', - ) - settings.MIDDLEWARE_CLASSES = [ - 'barbeque.staticfiles.middleware.ServeStaticFileMiddleware', - ] - settings.INSTALLED_APPS = settings.INSTALLED_APPS + ('django.contrib.staticfiles',) - settings.ROOT_URLCONF = 'barbeque.tests.test_static_files' - - def test_unhash_file_name(self): - middleware = ServeStaticFileMiddleware() - assert middleware.unhash_file_name( - '/static/test_hash.11aa22bb33cc.jpg') == ('/static/test_hash.jpg') - assert middleware.unhash_file_name('test_hash.jpg') == 'test_hash.jpg' - assert middleware.unhash_file_name( - 'test_hash.11aa22bb33cc.11aa22bb33cc.jpg') == ('test_hash.11aa22bb33cc.jpg') - assert middleware.unhash_file_name('test_hash.11aa22bb33cc') == 'test_hash' - assert middleware.unhash_file_name('11aa22bb33cc') == '11aa22bb33cc' - assert middleware.unhash_file_name('11aa22bb33cc.jpg') == '11aa22bb33cc.jpg' - assert middleware.unhash_file_name('.11aa22bb33cc.jpg') == '.11aa22bb33cc.jpg' - - def test_hash_file_exists(self, rf): - request = rf.get('/static/test_hash.11aa22bb33cc.jpg') - middleware = ServeStaticFileMiddleware() - response = middleware.process_response(request, HttpResponseNotFound('')) - assert response.status_code == 200 - assert response['Content-Type'] == 'image/jpeg' - assert len(response.items()) == 3 - assert response.has_header('Content-Length') - assert response.has_header('Last-Modified') - - def test_hash_file_original_exists(self, rf): - request = rf.get('/static/test_hash.jpg') - middleware = ServeStaticFileMiddleware() - response = middleware.process_response(request, HttpResponseNotFound('')) - assert response.status_code == 200 - assert response['Content-Type'] == 'image/jpeg' - assert len(response.items()) == 3 - assert response.has_header('Content-Length') - assert response.has_header('Last-Modified') - - def test_old_hash(self, rf): - request = rf.get('/static/test_hash.44dd55ee66ff.jpg') - middleware = ServeStaticFileMiddleware() - response = middleware.process_response(request, HttpResponseNotFound('')) - assert len(response.items()) == 3 - assert response.has_header('Content-Length') - assert response.has_header('Last-Modified') - - def test_hash_file_exists_with_client_hit(self, client, patch_settings): - response = client.get('/static/test_hash.11aa22bb33cc.jpg') - assert response.status_code == 200 - - def test_hash_file_original_exists_with_client_hit(self, client, patch_settings): - response = client.get('/static/test_hash.jpg') - assert response.status_code == 200 - - def test_hash_old_hash_with_client_hit(self, client, patch_settings): - response = client.get('/static/test_hash.44dd55ee66ff.jpg') - assert response.status_code == 200 - - @mock.patch('django.contrib.staticfiles.storage.ManifestStaticFilesStorage.load_manifest') - def test_no_staticfiles_manifest(self, manifest_mock, rf): - manifest_mock.return_value = OrderedDict() - request = rf.get('/static/test_hash.jpg') - middleware = ServeStaticFileMiddleware() - response = middleware.process_response(request, HttpResponseNotFound('')) - assert response.status_code == 404 diff --git a/barbeque/tests/test_staticfiles.py b/barbeque/tests/test_staticfiles.py index 59818e2..ed2eb04 100644 --- a/barbeque/tests/test_staticfiles.py +++ b/barbeque/tests/test_staticfiles.py @@ -50,7 +50,7 @@ def patch_settings(self, settings): 'barbeque.staticfiles.middleware.ServeStaticFileMiddleware', ] settings.INSTALLED_APPS = settings.INSTALLED_APPS + ('django.contrib.staticfiles',) - settings.ROOT_URLCONF = 'barbeque.tests.test_static_files' + settings.ROOT_URLCONF = 'barbeque.tests.test_staticfiles' def test_file_exists(self, rf): request = rf.get('/static/test.jpg') @@ -68,6 +68,12 @@ def test_file_missing(self, rf): response = middleware.process_response(request, HttpResponseNotFound('')) assert response.status_code == 404 + def test_static_folder(self, rf): + request = rf.get('/static/doesnotexist/') + middleware = ServeStaticFileMiddleware() + response = middleware.process_response(request, HttpResponseNotFound('')) + assert response.status_code == 404 + def test_unknown_prefix(self, rf): request = rf.get('/foo/test.jpg') middleware = ServeStaticFileMiddleware() @@ -137,7 +143,7 @@ def patch_settings(self, settings): 'barbeque.staticfiles.middleware.ServeStaticFileMiddleware', ] settings.INSTALLED_APPS = settings.INSTALLED_APPS + ('django.contrib.staticfiles',) - settings.ROOT_URLCONF = 'barbeque.tests.test_static_files' + settings.ROOT_URLCONF = 'barbeque.tests.test_staticfiles' def test_unhash_file_name(self): middleware = ServeStaticFileMiddleware() diff --git a/barbeque/tests/test_templatetags.py b/barbeque/tests/test_templatetags.py index 071ebef..8da9789 100644 --- a/barbeque/tests/test_templatetags.py +++ b/barbeque/tests/test_templatetags.py @@ -1,7 +1,11 @@ import mock +import pytest +from cms.api import create_page, publish_page +from django.contrib.auth.models import User from django.template import Context, Node, Template from barbeque.templatetags.barbeque_tags import starspan +from barbeque.tests.resources.cmsapp.models import ExtensionModel class TestTemplateTags: @@ -48,3 +52,89 @@ def test_buildcompress_tag_no_debug(self, node_mock, settings): template.render(Context()) assert node_mock.called is True + + +@pytest.mark.django_db +class TestPageTitleExtensionTemplateTag: + + @mock.patch('barbeque.templatetags.barbeque_tags.Page.objects.get') + def test_no_cms(self, page_mock, activate_cms, rf): + page_mock.side_effect = NameError + template = Template( + '{% load barbeque_tags %}{% page_titleextension 1 "extensionmodel" %}') + context = Context({'request': rf.get('/')}) + with pytest.raises(ImportError): + assert template.render(context) == '' + + def test_page_not_found(self, activate_cms, rf): + template = Template( + '{% load barbeque_tags %}{% page_titleextension 1 "extensionmodel" %}') + context = Context({'request': rf.get('/')}) + assert template.render(context) == 'None' + + def test_no_page(self, activate_cms, rf): + request = rf.get('/') + request.user = User() + page = create_page('Test Page', 'INHERIT', 'en-us') + template = Template(( + '{%% load barbeque_tags %%}' + '{%% page_titleextension %s "extensionmodel" %%}' + ) % page.pk) + context = Context({'request': request}) + assert template.render(context) == 'None' + + def test_extension_not_found(self, activate_cms, rf): + request = rf.get('/') + request.user = User.objects.create(username='admin', is_superuser=True) + + page = create_page('Test Page', 'INHERIT', 'en-us') + publish_page(page, request.user, 'en-us') + page.refresh_from_db() + + template = Template(( + '{%% load barbeque_tags %%}' + '{%% page_titleextension %s "extensionmodel" %%}' + ) % page.pk) + context = Context({'request': request}) + assert template.render(context) == 'None' + + def test_extension_found_public(self, activate_cms, rf): + request = rf.get('/') + request.user = User.objects.create(username='admin', is_superuser=True) + + page = create_page('Test Page', 'INHERIT', 'en-us') + publish_page(page, request.user, 'en-us') + page.refresh_from_db() + + ExtensionModel.objects.create( + extended_object=page.get_public_object().get_title_obj(), name='public') + ExtensionModel.objects.create( + extended_object=page.get_draft_object().get_title_obj(), name='draft') + + template = Template(( + '{%% load barbeque_tags %%}' + '{%% page_titleextension %s "extensionmodel" %%}' + ) % page.pk) + context = Context({'request': request}) + assert template.render(context) == 'public' + + def test_extension_found_draft(self, activate_cms, rf): + request = rf.get('/') + request.user = User.objects.create(username='admin', is_staff=True, is_superuser=True) + request.session = {'cms_edit': True} + + page = create_page('Test Page', 'INHERIT', 'en-us') + publish_page(page, request.user, 'en-us') + page.refresh_from_db() + + ExtensionModel.objects.create( + extended_object=page.get_public_object().get_title_obj(), name='public') + ExtensionModel.objects.create( + extended_object=page.get_draft_object().get_title_obj(), name='draft') + + template = Template(( + '{%% load barbeque_tags %%}' + '{%% page_titleextension %s "extensionmodel" %%}' + ) % page.pk) + context = Context({'request': request}) + assert template.render(context) == 'draft' diff --git a/barbeque/tests/test_validators.py b/barbeque/tests/test_validators.py index c207a0e..e249eae 100644 --- a/barbeque/tests/test_validators.py +++ b/barbeque/tests/test_validators.py @@ -4,7 +4,8 @@ import pytest from django.core.exceptions import ValidationError -from barbeque.validators import AgeValidator +from barbeque.validators import AgeValidator, UniqueEmailValidator +from barbeque.tests.resources.mockapp.models import DummyModel class TestAgeValidator: @@ -18,3 +19,22 @@ def test_invalid(self): def test_valid(self): self.validator(date.today() - relativedelta(years=10)) + + +@pytest.mark.django_db +class TestEmailValidator: + + def test_unique(self): + UniqueEmailValidator(DummyModel.objects.all())('foo@bar.baz') + + def test_not_unique(self): + DummyModel.objects.create(name='foo', slug='bar', email='foo@bar.baz') + + with pytest.raises(ValidationError): + UniqueEmailValidator(DummyModel.objects.all())('foo@bar.baz') + + def test_not_unique_uppercase(self): + DummyModel.objects.create(name='foo', slug='bar', email='foo@bar.baz') + + with pytest.raises(ValidationError): + UniqueEmailValidator(DummyModel.objects.all())('FOO@BAR.baz') diff --git a/conftest.py b/conftest.py index be5240d..62b6dcf 100644 --- a/conftest.py +++ b/conftest.py @@ -1,7 +1,6 @@ import shutil import tempfile -import django import pytest @@ -18,22 +17,19 @@ def activate_cms(settings): settings.ROOT_URLCONF = 'barbeque.tests.cms_urls' settings.MIDDLEWARE_CLASSES = ( 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'cms.middleware.toolbar.ToolbarMiddleware', 'cms.middleware.page.CurrentPageMiddleware', ) - if django.VERSION[:2] >= (1, 7): - settings.MIDDLEWARE_CLASSES += ( - 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', - ) - settings.CMS_TEMPLATES = (('empty_template.html', 'empty'),) settings.CMS_TOOLBARS = [ 'cms.cms_toolbars.PlaceholderToolbar', 'cms.cms_toolbars.BasicToolbar', 'cms.cms_toolbars.PageToolbar', 'barbeque.cms.toolbar.ForceModalDialogToolbar', + 'barbeque.tests.resources.cmsapp.cms_toolbars.ExtensionToolbar', ] yield diff --git a/pytest.ini b/pytest.ini index 15b1c6c..119ee56 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,5 +1,5 @@ [pytest] -addopts = -vs --cache-clear --tb=short --pep8 --flakes -p no:doctest +addopts = -vs --cache-clear --tb=short --pep8 --flakes -p no:doctest --no-migrations norecursedirs = .tox build docs python_files =