From 2e4710d8b848b6f6633ccac2a75ecc2584d8a880 Mon Sep 17 00:00:00 2001 From: Patrick Hetu Date: Wed, 9 Sep 2015 15:29:53 -0400 Subject: [PATCH] mise-en-production --- project/aldryn_search/__init__.py | 1 - project/aldryn_search/base.py | 150 ----------- project/aldryn_search/cms_app.py | 20 -- project/aldryn_search/conf.py | 17 -- project/aldryn_search/helpers.py | 72 ------ .../aldryn_search/locale/de/LC_MESSAGES/django.mo | Bin 891 -> 0 bytes .../aldryn_search/locale/de/LC_MESSAGES/django.po | 53 ---- .../aldryn_search/locale/en/LC_MESSAGES/django.mo | Bin 378 -> 0 bytes .../aldryn_search/locale/en/LC_MESSAGES/django.po | 47 ---- project/aldryn_search/router.py | 21 -- project/aldryn_search/search_indexes.py | 89 ------- .../templates/aldryn_search/base.html | 7 - .../aldryn_search/includes/pagination.html | 29 --- .../aldryn_search/includes/search_items.html | 13 - .../templates/aldryn_search/search_results.html | 17 -- project/aldryn_search/tests.py | 123 --------- project/aldryn_search/utils.py | 148 ----------- project/aldryn_search/views.py | 85 ------ project/cmsplugin_contact_plus/__init__.py | 1 + project/cmsplugin_contact_plus/actions.py | 104 ++++++++ project/cmsplugin_contact_plus/admin.py | 30 +++ project/cmsplugin_contact_plus/cms_plugins.py | 68 +++++ project/cmsplugin_contact_plus/forms.py | 129 +++++++++ .../inline_ordering/admin.py | 21 ++ .../cmsplugin_contact_plus/inline_ordering/media | 1 + .../inline_ordering/models.py | 23 ++ .../inline_ordering/settings.py | 4 + .../inline_ordering/static/inline_ordering.js | 73 ++++++ .../cmsplugin_contact_plus/jsonfield/__init__.py | 1 + .../cmsplugin_contact_plus/jsonfield/encoder.py | 59 +++++ project/cmsplugin_contact_plus/jsonfield/fields.py | 170 ++++++++++++ project/cmsplugin_contact_plus/jsonfield/models.py | 1 + .../jsonfield/subclassing.py | 60 +++++ project/cmsplugin_contact_plus/jsonfield/tests.py | 273 ++++++++++++++++++++ .../locale/es/LC_MESSAGES/django.mo | Bin 0 -> 2386 bytes .../locale/es/LC_MESSAGES/django.po | 126 +++++++++ .../migrations/0001_initial.py | 69 +++++ project/cmsplugin_contact_plus/models.py | 134 ++++++++++ project/cmsplugin_contact_plus/signals.py | 3 + .../simplemathcaptcha/__init__.py | 16 ++ .../simplemathcaptcha/fields.py | 63 +++++ .../locale/es/LC_MESSAGES/django.mo | Bin 0 -> 1188 bytes .../locale/es/LC_MESSAGES/django.po | 45 ++++ .../simplemathcaptcha/utils.py | 45 ++++ .../simplemathcaptcha/widgets.py | 80 ++++++ .../templates/cmsplugin_contact_plus/contact.html | 16 ++ .../templates/cmsplugin_contact_plus/email.txt | 5 + project/cmsplugin_contact_plus/utils.py | 69 +++++ project/framonde/models.py | 2 +- project/templates/accueil.html | 8 +- project/templates/archives.html | 23 +- project/templates/base.html | 15 +- project/templates/detail.html | 6 +- project/templates/pageAppel.html | 5 +- project/templates/pageCom.html | 5 +- project/templates/pageContri.html | 5 +- project/templates/search.html | 35 --- .../search/indexes/framonde/communication_text.txt | 1 + .../search/indexes/framonde/contribution_text.txt | 1 + .../search/indexes/framonde/offre_text.txt | 1 + project/templates/search/search.html | 66 +++++ project/urls/60-framonde.py | 14 +- project/views.py | 2 +- 63 files changed, 1812 insertions(+), 958 deletions(-) delete mode 100644 project/aldryn_search/__init__.py delete mode 100644 project/aldryn_search/base.py delete mode 100644 project/aldryn_search/cms_app.py delete mode 100644 project/aldryn_search/conf.py delete mode 100644 project/aldryn_search/helpers.py delete mode 100644 project/aldryn_search/locale/de/LC_MESSAGES/django.mo delete mode 100644 project/aldryn_search/locale/de/LC_MESSAGES/django.po delete mode 100644 project/aldryn_search/locale/en/LC_MESSAGES/django.mo delete mode 100644 project/aldryn_search/locale/en/LC_MESSAGES/django.po delete mode 100644 project/aldryn_search/models.py delete mode 100644 project/aldryn_search/router.py delete mode 100644 project/aldryn_search/search_indexes.py delete mode 100644 project/aldryn_search/templates/aldryn_search/base.html delete mode 100644 project/aldryn_search/templates/aldryn_search/includes/pagination.html delete mode 100644 project/aldryn_search/templates/aldryn_search/includes/search_items.html delete mode 100644 project/aldryn_search/templates/aldryn_search/search_results.html delete mode 100644 project/aldryn_search/tests.py delete mode 100644 project/aldryn_search/utils.py delete mode 100644 project/aldryn_search/views.py create mode 100755 project/cmsplugin_contact_plus/__init__.py create mode 100644 project/cmsplugin_contact_plus/actions.py create mode 100755 project/cmsplugin_contact_plus/admin.py create mode 100755 project/cmsplugin_contact_plus/cms_plugins.py create mode 100755 project/cmsplugin_contact_plus/forms.py create mode 100644 project/cmsplugin_contact_plus/inline_ordering/__init__.py create mode 100644 project/cmsplugin_contact_plus/inline_ordering/admin.py create mode 120000 project/cmsplugin_contact_plus/inline_ordering/media create mode 100644 project/cmsplugin_contact_plus/inline_ordering/models.py create mode 100644 project/cmsplugin_contact_plus/inline_ordering/settings.py create mode 100644 project/cmsplugin_contact_plus/inline_ordering/static/inline_ordering.js create mode 100644 project/cmsplugin_contact_plus/jsonfield/__init__.py create mode 100644 project/cmsplugin_contact_plus/jsonfield/encoder.py create mode 100644 project/cmsplugin_contact_plus/jsonfield/fields.py create mode 100644 project/cmsplugin_contact_plus/jsonfield/models.py create mode 100644 project/cmsplugin_contact_plus/jsonfield/subclassing.py create mode 100644 project/cmsplugin_contact_plus/jsonfield/tests.py create mode 100644 project/cmsplugin_contact_plus/local_settings.py create mode 100644 project/cmsplugin_contact_plus/locale/es/LC_MESSAGES/django.mo create mode 100644 project/cmsplugin_contact_plus/locale/es/LC_MESSAGES/django.po create mode 100644 project/cmsplugin_contact_plus/migrations/0001_initial.py create mode 100644 project/cmsplugin_contact_plus/migrations/__init__.py create mode 100755 project/cmsplugin_contact_plus/models.py create mode 100644 project/cmsplugin_contact_plus/signals.py create mode 100644 project/cmsplugin_contact_plus/simplemathcaptcha/__init__.py create mode 100644 project/cmsplugin_contact_plus/simplemathcaptcha/fields.py create mode 100644 project/cmsplugin_contact_plus/simplemathcaptcha/locale/es/LC_MESSAGES/django.mo create mode 100644 project/cmsplugin_contact_plus/simplemathcaptcha/locale/es/LC_MESSAGES/django.po create mode 100644 project/cmsplugin_contact_plus/simplemathcaptcha/utils.py create mode 100644 project/cmsplugin_contact_plus/simplemathcaptcha/widgets.py create mode 100644 project/cmsplugin_contact_plus/south_migrations/__init__.py create mode 100755 project/cmsplugin_contact_plus/templates/cmsplugin_contact_plus/contact.html create mode 100755 project/cmsplugin_contact_plus/templates/cmsplugin_contact_plus/email.txt create mode 100644 project/cmsplugin_contact_plus/utils.py delete mode 100644 project/templates/search.html create mode 100644 project/templates/search/indexes/framonde/communication_text.txt create mode 100644 project/templates/search/indexes/framonde/contribution_text.txt create mode 100644 project/templates/search/indexes/framonde/offre_text.txt create mode 100644 project/templates/search/search.html diff --git a/project/aldryn_search/__init__.py b/project/aldryn_search/__init__.py deleted file mode 100644 index 44b1806..0000000 --- a/project/aldryn_search/__init__.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = '0.2.6' diff --git a/project/aldryn_search/base.py b/project/aldryn_search/base.py deleted file mode 100644 index c667dec..0000000 --- a/project/aldryn_search/base.py +++ /dev/null @@ -1,150 +0,0 @@ -# -*- coding: utf-8 -*- -import warnings -from django.utils.translation import override - -from haystack import indexes - -from .conf import settings -from .helpers import get_request -from .utils import clean_join, _get_language_from_alias_func - - -language_from_alias = _get_language_from_alias_func() - - -class AbstractIndex(indexes.SearchIndex): - text = indexes.CharField(document=True, use_template=False) - - def _get_backend(self, using): - """ - We set the backend to allow easy access for things like document search. - """ - self._backend = super(AbstractIndex, self)._get_backend(using) - self._backend_alias = using - return self._backend - - def index_queryset(self, using=None): - self._get_backend(using) - language = self.get_current_language(using) - filter_kwargs = self.get_index_kwargs(language) - qs = self.get_index_queryset(language) - return (qs.filter(**filter_kwargs)) - - def get_index_queryset(self, language): - return self.get_model().objects.all() - - def prepare(self, obj): - current_language = self.get_current_language( - using=self._backend_alias, obj=obj) - - with override(current_language): - request = self.get_request_instance(obj, current_language) - self.prepared_data = super(AbstractIndex, self).prepare(obj) - self.prepared_data['text'] = self.get_search_data( - obj, current_language, request) - self.prepare_fields(obj, current_language, request) - return self.prepared_data - - def get_request_instance(self, obj, language): - return get_request(language) - - def get_language(self, obj): - """ - Equivalent to self.prepare_language. - """ - return None - - def get_current_language(self, using=None, obj=None): - """ - Helper method bound to ALWAYS return a language. - - When obj is not None, this calls self.get_language to try and get a language from obj, - this is useful when the object itself defines it's language in a "language" field. - - If no language was found or obj is None, then we call self.get_default_language to try and get a fallback language. - """ - language = self.get_language(obj) if obj else None - return language or self.get_default_language(using) - - def get_default_language(self, using): - """ - When using multiple languages, this allows us to specify a fallback based on the - backend being used. - """ - language = None - - if using and language_from_alias: - language = language_from_alias(using) - return language or settings.ALDRYN_SEARCH_DEFAULT_LANGUAGE - - def get_index_kwargs(self, language): - """ - This is called to filter the index queryset. - """ - return {} - - def get_search_data(self, obj, language, request): - """ - Returns a string that will be used to populate the text field (primary field). - """ - return '' - - def prepare_fields(self, obj, language, request): - """ - This is called to prepare any extra fields. - """ - pass - - -class AldrynIndexBase(AbstractIndex): - # For some apps it makes sense to turn on the title indexing. - index_title = False - - language = indexes.CharField() - description = indexes.CharField(indexed=False, stored=True, null=True) - pub_date = indexes.DateTimeField(null=True) - login_required = indexes.BooleanField(default=False) - url = indexes.CharField(stored=True, indexed=False) - title = indexes.CharField(stored=True, indexed=False) - site_id = indexes.IntegerField(stored=True, indexed=True, null=True) - - def __init__(self): - if hasattr(self, 'INDEX_TITLE'): - warning_message = 'AldrynIndexBase.INDEX_TITLE is deprecated; use AldrynIndexBase.index_title instead' - warnings.warn(warning_message, PendingDeprecationWarning) - super(AldrynIndexBase, self).__init__() - - def get_url(self, obj): - """ - Equivalent to self.prepare_url. - """ - if not obj.get_absolute_url(): - print obj - return obj.get_absolute_url() - - def get_title(self, obj): - """ - Equivalent to self.prepare_title. - """ - return None - - def get_description(self, obj): - """ - Equivalent to self.prepare_description. - """ - return None - - def prepare_fields(self, obj, language, request): - self.prepared_data['language'] = language - # We set the following fields here because on some models, - # the value of these fields is dependent on the active language - # this being the case we extrapolate the language hacks. - self.prepared_data['url'] = self.get_url(obj) - self.prepared_data['title'] = self.get_title(obj) - self.prepared_data['description'] = self.get_description(obj) - - if self.index_title or getattr(self, 'INDEX_TITLE', False): - prepared_text = self.prepared_data['text'] - prepared_title = self.prepared_data['title'] - self.prepared_data['text'] = clean_join( - ' ', [prepared_title, prepared_text]) diff --git a/project/aldryn_search/cms_app.py b/project/aldryn_search/cms_app.py deleted file mode 100644 index c6366b1..0000000 --- a/project/aldryn_search/cms_app.py +++ /dev/null @@ -1,20 +0,0 @@ -from django.conf.urls import patterns, url -from django.utils.translation import ugettext_lazy as _ - -from cms.app_base import CMSApp -from cms.apphook_pool import apphook_pool - -from .views import AldrynSearchView - -from .conf import settings - - -class AldrynSearchApphook(CMSApp): - name = _("aldryn search") - urls = [patterns('', - url('^$', AldrynSearchView.as_view(), name='aldryn-search'), - ),] - - -if settings.ALDRYN_SEARCH_REGISTER_APPHOOK: - apphook_pool.register(AldrynSearchApphook) diff --git a/project/aldryn_search/conf.py b/project/aldryn_search/conf.py deleted file mode 100644 index c293732..0000000 --- a/project/aldryn_search/conf.py +++ /dev/null @@ -1,17 +0,0 @@ -# -*- coding: utf-8 -*- -from django.conf import settings - -from appconf import AppConf - - -class AldrynSearchAppConf(AppConf): - - CMS_PAGE = True - DEFAULT_LANGUAGE = settings.LANGUAGE_CODE - INDEX_BASE_CLASS = 'aldryn_search.base.AldrynIndexBase' - LANGUAGE_FROM_ALIAS = 'aldryn_search.utils.language_from_alias' - PAGINATION = 10 - REGISTER_APPHOOK = True - - class Meta: - prefix = 'ALDRYN_SEARCH' diff --git a/project/aldryn_search/helpers.py b/project/aldryn_search/helpers.py deleted file mode 100644 index f6a4638..0000000 --- a/project/aldryn_search/helpers.py +++ /dev/null @@ -1,72 +0,0 @@ -# -*- coding: utf-8 -*- -from django.contrib.auth.models import AnonymousUser -from django.template import RequestContext -from django.test import RequestFactory -from django.utils.text import smart_split -try: - from django.utils.encoding import force_unicode -except ImportError: - from django.utils.encoding import force_text as force_unicode - -from .conf import settings -from .utils import get_field_value, strip_tags - - -def get_cleaned_bits(data): - decoded = force_unicode(data) - stripped = strip_tags(decoded) - return smart_split(stripped) - - -def get_plugin_index_data(base_plugin, request): - text_bits = [] - - instance, plugin_type = base_plugin.get_plugin_instance() - - if instance is None: - # this is an empty plugin - return text_bits - - search_fields = getattr(instance, 'search_fields', []) - - if hasattr(instance, 'search_fulltext'): - # check if the plugin instance has search enabled - search_contents = instance.search_fulltext - elif hasattr(base_plugin, 'search_fulltext'): - # now check in the base plugin instance (CMSPlugin) - search_contents = base_plugin.search_fulltext - elif hasattr(plugin_type, 'search_fulltext'): - # last check in the plugin class (CMSPluginBase) - search_contents = plugin_type.search_fulltext - else: - # disabled if there's search fields defined, - # otherwise it's enabled. - search_contents = not bool(search_fields) - - if search_contents: - plugin_contents = instance.render_plugin( - context=RequestContext(request)) - - if plugin_contents: - text_bits = get_cleaned_bits(plugin_contents) - else: - values = (get_field_value(instance, field) for field in search_fields) - - for value in values: - cleaned_bits = get_cleaned_bits(value or '') - text_bits.extend(cleaned_bits) - return text_bits - - -def get_request(language=None): - """ - Returns a Request instance populated with cms specific attributes. - """ - request_factory = RequestFactory(HTTP_HOST=settings.ALLOWED_HOSTS[0]) - request = request_factory.get("/") - request.session = {} - request.LANGUAGE_CODE = language or settings.LANGUAGE_CODE - # Needed for plugin rendering. - request.current_page = None - request.user = AnonymousUser() - return request diff --git a/project/aldryn_search/locale/de/LC_MESSAGES/django.mo b/project/aldryn_search/locale/de/LC_MESSAGES/django.mo deleted file mode 100644 index 39bab2167aa33951c239b4f7c80ca8d2c2b5b737..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 891 zcmbV~&u-H|5XKiMAO*M}!HL5PDI%!a;I^oWOhGY;TO#bZC`fL1kT&^B0rF8nVS8EZm+LaY4xCC08pL+CB&DYOP%K#{^A>U_O{P+~(hyIwVv)-w zC9L~rg-=}O`Npixai~qPEtt#`vsAnwY2q0pWy$cRF3tHH4u8%J6=ja#W237q(M!Y$ znIhB5<;Py3QI2wtLA!(5$y3&%%cb%O* z;k1OakEOf!aNF5+90ZC1on=+=^SGD3HxOYYx*mTPxnXbnH0t}cKo(Yvj8sJ~tu`({ z90vjKd!f${{Lt$KZLixM_`~57gsQZZDTxuuxyu8ue>ARIJ_v$#o;9^e*D+`{?1ldN zd^=62*3tMc3fnCg=-bje`v7yu)FU3xq$$Wg9*+)1YaLg$H6gcKXB J?g56U*l!Ut_dWms diff --git a/project/aldryn_search/locale/de/LC_MESSAGES/django.po b/project/aldryn_search/locale/de/LC_MESSAGES/django.po deleted file mode 100644 index 5d53494..0000000 --- a/project/aldryn_search/locale/de/LC_MESSAGES/django.po +++ /dev/null @@ -1,53 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# FIRST AUTHOR , YEAR. -# -#, fuzzy -msgid "" -msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2014-07-24 16:57+0200\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" - -#: cms_app.py:13 -msgid "aldryn search" -msgstr "Aldryn Suche" - -#: templates/aldryn_search/search_results.html:6 -msgid "enter your search term" -msgstr "Suchbegriffe eingeben" - -#: templates/aldryn_search/search_results.html:16 -msgid "results" -msgstr "Resultaten" - -#: templates/aldryn_search/includes/pagination.html:7 -#, python-format -msgid "%(from)s – %(until)s of %(count)s %(obj_string)s are displayed" -msgstr "%(from)s – %(until)s von %(count)s %(obj_string)s werden angezeigt" - -#: templates/aldryn_search/includes/pagination.html:11 -msgid "previous" -msgstr "" - -#: templates/aldryn_search/includes/pagination.html:25 -msgid "next" -msgstr "" - -#: templates/aldryn_search/includes/search_items.html:12 -msgid "No results found for" -msgstr "" - -#~ msgid "Displaying all %(count)s %(obj_string)s" -#~ msgstr "Alle %(count)s %(obj_string)s werden angezeigt" - -#~ msgid "Displaying all %(count)s objects" -#~ msgstr "Alle %(count)s Objekte werden angezeigt" diff --git a/project/aldryn_search/locale/en/LC_MESSAGES/django.mo b/project/aldryn_search/locale/en/LC_MESSAGES/django.mo deleted file mode 100644 index 31092e8b91c34bdb4c71dd7a9be8ac72e9f5153d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 378 zcmYL^K~KUk7=|@^+R?Lz9=zd)1rjqW8Y(Ne*luJD61`QZa|T<|6{A1Izvpl9TVmu* zp0r7yetr9Ma{PUOIYds8Gvo+4N7}^52oJAtZJqzjyn8dN0O=Z*#y7RIjLi7>flmcl ziejGTSyY&<^8r*-)oZxhRSlGmk!Y5eF!n|Vyab63hLJw)4S>c#FLBETgAVOeiXs%| zqi%=)d5?k@pmIn!xzCwOJ5PBgBB`4IrPsFE=-@mflZBKd<4TaBP&}0*o+O2s&u>w+ z)lOFiN~7Nykvy9&+R8&IN1M9qe6@G5s!EmE|F74j(b(BwEI#+O#oobuhKub+*Y=vM gUbSybxLK4Fi1%Jw>&18wcIDQ!tr&@)>ku9M0#j#ZK>z>% diff --git a/project/aldryn_search/locale/en/LC_MESSAGES/django.po b/project/aldryn_search/locale/en/LC_MESSAGES/django.po deleted file mode 100644 index 1b400f8..0000000 --- a/project/aldryn_search/locale/en/LC_MESSAGES/django.po +++ /dev/null @@ -1,47 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# FIRST AUTHOR , YEAR. -# -#, fuzzy -msgid "" -msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2014-07-24 16:57+0200\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" - -#: cms_app.py:13 -msgid "aldryn search" -msgstr "" - -#: templates/aldryn_search/search_results.html:6 -msgid "enter your search term" -msgstr "" - -#: templates/aldryn_search/search_results.html:16 -msgid "results" -msgstr "" - -#: templates/aldryn_search/includes/pagination.html:7 -#, python-format -msgid "%(from)s – %(until)s of %(count)s %(obj_string)s are displayed" -msgstr "" - -#: templates/aldryn_search/includes/pagination.html:11 -msgid "previous" -msgstr "" - -#: templates/aldryn_search/includes/pagination.html:25 -msgid "next" -msgstr "" - -#: templates/aldryn_search/includes/search_items.html:12 -msgid "No results found for" -msgstr "" diff --git a/project/aldryn_search/models.py b/project/aldryn_search/models.py deleted file mode 100644 index e69de29..0000000 diff --git a/project/aldryn_search/router.py b/project/aldryn_search/router.py deleted file mode 100644 index 9da94c2..0000000 --- a/project/aldryn_search/router.py +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding: utf-8 -*- -from django.conf import settings -from django.utils.translation import get_language - -from haystack import routers -from haystack.constants import DEFAULT_ALIAS - - -class LanguageRouter(routers.BaseRouter): - - def for_read(self, **hints): - language = get_language() - if language not in settings.HAYSTACK_CONNECTIONS: - return DEFAULT_ALIAS - return language - - def for_write(self, **hints): - language = get_language() - if language not in settings.HAYSTACK_CONNECTIONS: - return DEFAULT_ALIAS - return language diff --git a/project/aldryn_search/search_indexes.py b/project/aldryn_search/search_indexes.py deleted file mode 100644 index 8e671d0..0000000 --- a/project/aldryn_search/search_indexes.py +++ /dev/null @@ -1,89 +0,0 @@ -from django.db.models import Q -from django.utils import timezone - -from cms.models import CMSPlugin, Title - -from .conf import settings -from .helpers import get_plugin_index_data -from .utils import clean_join, get_index_base, strip_tags - - -# Backwards compatibility -_strip_tags = strip_tags - - -class TitleIndex(get_index_base()): - index_title = True - - haystack_use_for_indexing = settings.ALDRYN_SEARCH_CMS_PAGE - - def prepare_pub_date(self, obj): - return obj.page.publication_date - - def prepare_login_required(self, obj): - return obj.page.login_required - - def prepare_site_id(self, obj): - return obj.page.site_id - - def get_language(self, obj): - return obj.language - - def get_url(self, obj): - return obj.page.get_absolute_url() - - def get_title(self, obj): - return obj.title - - def get_description(self, obj): - return obj.meta_description or None - - def get_plugin_queryset(self, language): - queryset = CMSPlugin.objects.filter(language=language) - return queryset - - def get_search_data(self, obj, language, request): - current_page = obj.page - placeholders = current_page.placeholders.all() - plugins = self.get_plugin_queryset( - language).filter(placeholder__in=placeholders) - text_bits = [] - - for base_plugin in plugins: - plugin_text_content = self.get_plugin_search_text( - base_plugin, request) - text_bits.append(plugin_text_content) - - page_meta_description = current_page.get_meta_description( - fallback=False, language=language) - - if page_meta_description: - text_bits.append(page_meta_description) - - page_meta_keywords = getattr(current_page, 'get_meta_keywords', None) - - if callable(page_meta_keywords): - text_bits.append(page_meta_keywords()) - - return clean_join(' ', text_bits) - - def get_plugin_search_text(self, base_plugin, request): - try: - plugin_content_bits = get_plugin_index_data(base_plugin, request) - except: - return '' - return clean_join(' ', plugin_content_bits) - - def get_model(self): - return Title - - def get_index_queryset(self, language): - queryset = Title.objects.public().filter( - Q(page__publication_date__lt=timezone.now()) | Q( - page__publication_date__isnull=True), - Q(page__publication_end_date__gte=timezone.now()) | Q( - page__publication_end_date__isnull=True), - Q(redirect__exact='') | Q(redirect__isnull=True), - language=language - ).select_related('page').distinct() - return queryset diff --git a/project/aldryn_search/templates/aldryn_search/base.html b/project/aldryn_search/templates/aldryn_search/base.html deleted file mode 100644 index 527e7de..0000000 --- a/project/aldryn_search/templates/aldryn_search/base.html +++ /dev/null @@ -1,7 +0,0 @@ -{% extends "base.html" %} - -{% block content %} - -{% endblock %} \ No newline at end of file diff --git a/project/aldryn_search/templates/aldryn_search/includes/pagination.html b/project/aldryn_search/templates/aldryn_search/includes/pagination.html deleted file mode 100644 index 942b4f7..0000000 --- a/project/aldryn_search/templates/aldryn_search/includes/pagination.html +++ /dev/null @@ -1,29 +0,0 @@ -{% load i18n spurl %} - -{% if page_obj.has_other_pages %} - - -{% endif %} \ No newline at end of file diff --git a/project/aldryn_search/templates/aldryn_search/includes/search_items.html b/project/aldryn_search/templates/aldryn_search/includes/search_items.html deleted file mode 100644 index 0d65bff..0000000 --- a/project/aldryn_search/templates/aldryn_search/includes/search_items.html +++ /dev/null @@ -1,13 +0,0 @@ -{% load i18n highlight %} - -{% for result in page_obj.object_list %} -
  • -

    {{ result.title }}

    - {% if result.text %} -

    {% highlight result.text with request.GET.q %}

    - {% endif %} -

    {{ result.url }}

    -
  • -{% empty %} -
  • {% trans "No results found for" %} "{{ request.GET.q }}"
  • -{% endfor %} \ No newline at end of file diff --git a/project/aldryn_search/templates/aldryn_search/search_results.html b/project/aldryn_search/templates/aldryn_search/search_results.html deleted file mode 100644 index ce5b5a1..0000000 --- a/project/aldryn_search/templates/aldryn_search/search_results.html +++ /dev/null @@ -1,17 +0,0 @@ -{% extends "aldryn_search/base.html" %} -{% load i18n standard_form %} - -{% block content_search %} - - -
    -
      - {% include "aldryn_search/includes/search_items.html" %} -
    -
    - -{% include "aldryn_search/includes/pagination.html" with obj_string=_('results') %} -{% endblock %} \ No newline at end of file diff --git a/project/aldryn_search/tests.py b/project/aldryn_search/tests.py deleted file mode 100644 index dabb191..0000000 --- a/project/aldryn_search/tests.py +++ /dev/null @@ -1,123 +0,0 @@ -from django.template import Template -from django.test import TestCase -from django.test.utils import override_settings - -from cms.plugin_base import CMSPluginBase -from cms.plugin_pool import plugin_pool -from cms.models.placeholdermodel import Placeholder -from cms.models import CMSPlugin - -from aldryn_search.search_indexes import TitleIndex -from .helpers import get_plugin_index_data, get_request - - -test_settings = { - 'ALLOWED_HOSTS': ['localhost'], - 'CMS_LANGUAGES': {1: [{'code': 'en', 'name': 'English'}]}, - 'CMS_TEMPLATES': (("whee.html", "Whee Template"),), - 'LANGUAGES': (('en', 'English'),), - 'LANGUAGE_CODE': 'en', - 'TEMPLATE_LOADERS': ('aldryn_search.tests.FakeTemplateLoader',), -} - - -class FakeTemplateLoader(object): - is_usable = True - - def __init__(self, name, dirs): - pass - - def __iter__(self): - yield self.__class__ - yield "{{baz}}" - - -class NotIndexedPlugin(CMSPluginBase): - model = CMSPlugin - plugin_content = 'rendered plugin content' - render_template = Template(plugin_content) - - def render(self, context, instance, placeholder): - return context - -plugin_pool.register_plugin(NotIndexedPlugin) - - -@override_settings(**test_settings) -class PluginIndexingTests(TestCase): - - def setUp(self): - self.index = TitleIndex() - self.request = get_request(language='en') - - def get_plugin(self): - instance = CMSPlugin( - language='en', - plugin_type="NotIndexedPlugin", - placeholder=Placeholder(id=1235) - ) - instance.cmsplugin_ptr = instance - instance.pk = 1234 # otherwise plugin_meta_context_processor() crashes - return instance - - def test_plugin_indexing_is_enabled_by_default(self): - cms_plugin = self.get_plugin() - indexed_content = self.index.get_plugin_search_text( - cms_plugin, self.request) - self.assertEqual(NotIndexedPlugin.plugin_content, indexed_content) - - def test_plugin_indexing_can_be_disabled_on_model(self): - cms_plugin = self.get_plugin() - cms_plugin.search_fulltext = False - indexed_content = self.index.get_plugin_search_text( - cms_plugin, self.request) - self.assertEqual('', indexed_content) - - def test_plugin_indexing_can_be_disabled_on_plugin(self): - NotIndexedPlugin.search_fulltext = False - - try: - self.assertEqual( - '', self.index.get_plugin_search_text(self.get_plugin(), self.request)) - finally: - del NotIndexedPlugin.search_fulltext - - def test_page_title_is_indexed_using_prepare(self): - """This tests the indexing path way used by update_index mgmt command""" - from cms.api import create_page - page = create_page( - title="Whoopee", template="whee.html", language="en") - - from haystack import connections - from haystack.constants import DEFAULT_ALIAS - search_conn = connections[DEFAULT_ALIAS] - unified_index = search_conn.get_unified_index() - - from cms.models import Title - index = unified_index.get_index(Title) - - title = Title.objects.get(pk=page.title_set.all()[0].pk) - index.index_queryset(DEFAULT_ALIAS) # initialises index._backend_alias - indexed = index.prepare(title) - self.assertEqual('Whoopee', indexed['title']) - self.assertEqual('Whoopee', indexed['text']) - - def test_page_title_is_indexed_using_update_object(self): - """This tests the indexing path way used by the RealTimeSignalProcessor""" - from cms.api import create_page - page = create_page( - title="Whoopee", template="whee.html", language="en") - - from haystack import connections - from haystack.constants import DEFAULT_ALIAS - search_conn = connections[DEFAULT_ALIAS] - unified_index = search_conn.get_unified_index() - - from cms.models import Title - index = unified_index.get_index(Title) - - title = Title.objects.get(pk=page.title_set.all()[0].pk) - index.update_object(title, using=DEFAULT_ALIAS) - indexed = index.prepared_data - self.assertEqual('Whoopee', indexed['title']) - self.assertEqual('Whoopee', indexed['text']) diff --git a/project/aldryn_search/utils.py b/project/aldryn_search/utils.py deleted file mode 100644 index e0a720a..0000000 --- a/project/aldryn_search/utils.py +++ /dev/null @@ -1,148 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -import six - -from lxml.html.clean import Cleaner as LxmlCleaner - -from django.core.exceptions import ImproperlyConfigured -from django.db import models -try: - from django.utils.encoding import force_unicode -except ImportError: - from django.utils.encoding import force_text as force_unicode -from django.utils.importlib import import_module -from django.utils.html import strip_tags as _strip_tags - -from haystack import DEFAULT_ALIAS -from haystack.indexes import SearchIndex - -from cms.utils.i18n import get_language_code - -from .conf import settings - - -def alias_from_language(language): - """ - Returns alias if alias is a valid language. - """ - language = get_language_code(language) - - if language == settings.ALDRYN_SEARCH_DEFAULT_LANGUAGE: - return DEFAULT_ALIAS - return language - - -def clean_join(separator, iterable): - """ - Filters out iterable to only join non empty items. - """ - return separator.join(filter(None, iterable)) - - -def get_callable(string_or_callable): - """ - If given a callable then it returns it, otherwise it resolves the path - and returns an object. - """ - if callable(string_or_callable): - return string_or_callable - else: - module_name, object_name = string_or_callable.rsplit('.', 1) - if module_name.startswith('aldryn_search'): - module_name = "project." + module_name - module = import_module(module_name) - return getattr(module, object_name) - - -def _get_language_from_alias_func(): - path_or_callable = settings.ALDRYN_SEARCH_LANGUAGE_FROM_ALIAS - - if path_or_callable: - try: - func = get_callable(path_or_callable) - except AttributeError as error: - raise ImproperlyConfigured( - 'ALDRYN_SEARCH_LANGUAGE_FROM_ALIAS: %s' % (str(error))) - if not callable(func): - raise ImproperlyConfigured( - 'ALDRYN_SEARCH_LANGUAGE_FROM_ALIAS: %s is not callable' % func) - else: - func = None - return func - - -def get_index_base(): - index_string = settings.ALDRYN_SEARCH_INDEX_BASE_CLASS - try: - BaseClass = get_callable(index_string) - except AttributeError as error: - raise ImproperlyConfigured( - 'ALDRYN_SEARCH_INDEX_BASE_CLASS: %s' % (str(error))) - - if not issubclass(BaseClass, SearchIndex): - raise ImproperlyConfigured( - 'ALDRYN_SEARCH_INDEX_BASE_CLASS: %s is not a subclass of haystack.indexes.SearchIndex' % index_string) - - required_fields = ['text', 'language'] - - if not all(field in BaseClass.fields for field in required_fields): - raise ImproperlyConfigured('ALDRYN_SEARCH_INDEX_BASE_CLASS: %s must contain at least these fields: %s' % ( - index_string, required_fields)) - return BaseClass - - -def language_from_alias(alias): - """ - Returns alias if alias is a valid language. - """ - languages = [language[0] for language in settings.LANGUAGES] - - return alias if alias in languages else None - - -def get_field_value(obj, name): - """ - Given a model instance and a field name (or attribute), - returns the value of the field or an empty string. - """ - fields = name.split('__') - - name = fields[0] - - try: - obj._meta.get_field(name) - except (AttributeError, models.FieldDoesNotExist): - # we catch attribute error because obj will not always be a model - # specially when going through multiple relationships. - value = getattr(obj, name, None) or '' - else: - value = getattr(obj, name) - - if len(fields) > 1: - remaining = '__'.join(fields[1:]) - return get_field_value(value, remaining) - return value - - -def get_model_path(model_or_string): - if not isinstance(model_or_string, six.string_types): - # it's a model class - app_label = model_or_string._meta.app_label - model_name = model_or_string._meta.object_name - model_or_string = '{0}.{1}'.format(app_label, model_name) - return model_or_string.lower() - - -def strip_tags(value): - """ - Returns the given HTML with all tags stripped. - We use lxml to strip all js tags and then hand the result to django's strip tags. - """ - # strip any new lines - value = value.strip() - - if value: - partial_strip = LxmlCleaner().clean_html(value) - value = _strip_tags(partial_strip) - return value diff --git a/project/aldryn_search/views.py b/project/aldryn_search/views.py deleted file mode 100644 index ea4d75b..0000000 --- a/project/aldryn_search/views.py +++ /dev/null @@ -1,85 +0,0 @@ -# -*- coding: utf-8 -*- -import datetime - -from django.views.generic import ListView -from django.views.generic.edit import FormMixin - -from django import forms - -from haystack.forms import SearchForm -from haystack.query import SearchQuerySet - - -from aldryn_common.paginator import DiggPaginator - -from .conf import settings - - -class AldrynFacetedSearchForm(SearchForm): - selected_facets = forms.CharField(required=False, widget=forms.HiddenInput) - courant = forms.BooleanField(required=False, widget=forms.HiddenInput) - cloture = forms.BooleanField(required=False, widget=forms.HiddenInput) - - def search(self): - sqs = SearchQuerySet() - sqs = sqs.facet('bureaux').facet( - 'section').facet('annee').facet('partenaire') - - if self.q: - sqs = sqs.filter(content=sqs.query.clean(self.q)) - - if self.courant: - sqs = sqs.filter(date_fin__gte=datetime.date.today()) - if self.cloture: - sqs = sqs.filter(date_fin__lt=datetime.date.today()) - - self.selected_facets = list( - set(self.selected_facets.split('&') + self.selected_facets_get)) - - for facet in self.selected_facets: - if "__" not in facet: - continue - - field, value = facet.split("__", 1) - - if value: - sqs = sqs.narrow(u'%s:"%s"' % (field, sqs.query.clean(value))) - - return sqs - - -class AldrynSearchView(FormMixin, ListView): - form_class = AldrynFacetedSearchForm - - paginate_by = settings.ALDRYN_SEARCH_PAGINATION - paginator_class = DiggPaginator - - template_name = 'aldryn_search/search_results.html' - - def get(self, request, *args, **kwargs): - self.form = AldrynFacetedSearchForm(self.request.GET) - self.form.q = self.request.GET.get('q', '') - self.form.courant = self.request.GET.get('courant', '') - self.form.cloture = self.request.GET.get('cloture', '') - self.form.selected_facets = self.request.GET.get('selected_facets', '') - self.form.selected_facets_get = self.request.GET.getlist( - 'selected_facets', []) - return super(AldrynSearchView, self).get(request, *args, **kwargs) - - def get_queryset(self): - self.queryset = self.form.search() - if not self.request.user.is_authenticated(): - self.queryset = self.queryset.exclude(login_required=True) - self.facet_counts = self.queryset.facet_counts() - return self.queryset.order_by('-date_pub') - - def get_context_data(self, **kwargs): - context = super(AldrynSearchView, self).get_context_data(**kwargs) - - context['form'] = self.form - context['facets'] = self.facet_counts - context['selected_facets'] = self.request.GET.getlist( - 'selected_facets', []) - if self.object_list.query.backend.include_spelling: - context['suggestion'] = self.form.get_suggestion() - return context diff --git a/project/cmsplugin_contact_plus/__init__.py b/project/cmsplugin_contact_plus/__init__.py new file mode 100755 index 0000000..923b987 --- /dev/null +++ b/project/cmsplugin_contact_plus/__init__.py @@ -0,0 +1 @@ +__version__ = '1.2.2' diff --git a/project/cmsplugin_contact_plus/actions.py b/project/cmsplugin_contact_plus/actions.py new file mode 100644 index 0000000..51ed0cb --- /dev/null +++ b/project/cmsplugin_contact_plus/actions.py @@ -0,0 +1,104 @@ +import csv +from django.http import HttpResponse +#from setuptools.compat import unicode + + +class LUT(object): + + def __init__(self): + self.lut = [] + + def add_field(self, field_name): + """ """ + if field_name not in self.lut: + self.lut.append(field_name) + + def get_idx(self, field_name): + """ """ + return self.lut.index(field_name) + + + +def export_as_csv_action(description="Export selected objects as CSV file", + fields=None, exclude=None, header=True, json_fields=None): + """ + This function returns an export csv action + 'fields' and 'exclude' work like in django ModelForm + 'header' is whether or not to output the column names as the first row + + json_fields will be exploaded to rows. + """ + + from itertools import chain + + def export_as_csv(modeladmin, request, queryset): + """ + Generic csv export admin action. + based on http://djangosnippets.org/snippets/2369/ + """ + opts = modeladmin.model._meta + field_names = set([field.name for field in opts.fields]) + many_to_many_field_names = set([many_to_many_field.name for many_to_many_field in opts.many_to_many]) + if fields: + fieldset = set(fields) + field_names = field_names & fieldset + elif exclude: + excludeset = set(exclude) + field_names = field_names - excludeset + + response = HttpResponse(content_type='text/csv') + response['Content-Disposition'] = 'attachment; filename=%s.csv' % unicode(opts).replace('.', '_') + + writer = csv.writer(response) + """ + if header: + writer.writerow(list(chain(field_names, many_to_many_field_names))) + # default: does not split json_fields to rows. (json is in one row) + if not json_fields: + for obj in queryset: + row = [] + for field in field_names: + row.append(unicode(getattr(obj, field))) + for field in many_to_many_field_names: + row.append(unicode(getattr(obj, field).all())) + writer.writerow(row) + else: + """ + # build LUT + lut = LUT() + for obj in queryset: + for field in field_names: + if field in json_fields: + j = getattr(obj, field) + for l in j: + try: + for k, v in l.iteritems(): + lut.add_field(k) + except AttributeError: + pass + else: + lut.add_field(field) + + if header: + writer.writerow(lut.lut) + for obj in queryset: + row = ['']*len(lut.lut) + for field in field_names: + if field in json_fields: + j = getattr(obj, field) + for l in j: + for k, v in l.iteritems(): + try: + row[lut.get_idx(k)] = v.encode('utf-8') + except AttributeError: + pass + else: + row[lut.get_idx(field)] = unicode(getattr(obj, field)) + for field in many_to_many_field_names: + row[lut.get_idx(field)] = unicode(getattr(obj, field).all()) + writer.writerow(row) + + + return response + export_as_csv.short_description = description + return export_as_csv diff --git a/project/cmsplugin_contact_plus/admin.py b/project/cmsplugin_contact_plus/admin.py new file mode 100755 index 0000000..a4ff8d0 --- /dev/null +++ b/project/cmsplugin_contact_plus/admin.py @@ -0,0 +1,30 @@ +from django.contrib import admin +from django.http import HttpResponse + +from inline_ordering.admin import OrderableStackedInline +from .models import ExtraField, ContactPlus, ContactRecord + +from .actions import export_as_csv_action + +class ExtraFieldInline(OrderableStackedInline): + model = ExtraField + + +class ContactFormPlusAdmin(admin.ModelAdmin): + model = ContactPlus + inlines = (ExtraFieldInline, ) + + +class ContactRecordAdmin(admin.ModelAdmin): + model = ContactRecord + actions = [export_as_csv_action("CSV Export", + fields = ['contact_form', 'date_of_entry', 'date_processed', 'data'], + header = True, + json_fields = ['data']), # + ] + + +admin.site.register(ExtraField) + +admin.site.register(ContactRecord, ContactRecordAdmin) +admin.site.register(ContactPlus, ContactFormPlusAdmin) diff --git a/project/cmsplugin_contact_plus/cms_plugins.py b/project/cmsplugin_contact_plus/cms_plugins.py new file mode 100755 index 0000000..738ea6d --- /dev/null +++ b/project/cmsplugin_contact_plus/cms_plugins.py @@ -0,0 +1,68 @@ +from django.utils.translation import ugettext_lazy as _ +from django.conf import settings +from cms.plugin_base import CMSPluginBase +from cms.plugin_pool import plugin_pool + +from .admin import ExtraFieldInline +from .models import ContactPlus +from .forms import ContactFormPlus + + +import time + +def handle_uploaded_file(f, ts): + destination = open('%s/%s' % (settings.MEDIA_ROOT, ts + '-' + f.name), 'wb+') + + for chunk in f.chunks(): + destination.write(chunk) + destination.close() + + +class CMSContactPlusPlugin(CMSPluginBase): + """ + """ + model = ContactPlus + inlines = [ExtraFieldInline, ] + name = _('Contact Form') + render_template = "cmsplugin_contact_plus/contact.html" + cache = False + + def render(self, context, instance, placeholder): + request = context['request'] + + if instance and instance.template: + self.render_template = instance.template + + if request.method == "POST": + form = ContactFormPlus(contactFormInstance=instance, + request=request, + data=request.POST, + files=request.FILES) + if form.is_valid(): + ts = str(int(time.time())) + + for fl in request.FILES: + for f in request.FILES.getlist(fl): + handle_uploaded_file(f, ts) + + form.send(instance.recipient_email, request, ts, instance, form.is_multipart) + context.update({ + 'contact': instance, + }) + return context + else: + context.update({ + 'contact': instance, + 'form': form, + }) + + else: + form = ContactFormPlus(contactFormInstance=instance, request=request) + context.update({ + 'contact': instance, + 'form': form, + }) + return context + + +plugin_pool.register_plugin(CMSContactPlusPlugin) diff --git a/project/cmsplugin_contact_plus/forms.py b/project/cmsplugin_contact_plus/forms.py new file mode 100755 index 0000000..90651bd --- /dev/null +++ b/project/cmsplugin_contact_plus/forms.py @@ -0,0 +1,129 @@ +from django import forms +from django.core.mail import EmailMessage +from django.template.loader import render_to_string +from django.contrib.sites.models import Site +from django.template.defaultfilters import slugify +from django.conf import settings +from django.utils.translation import ugettext_lazy as _ + +from simplemathcaptcha.fields import MathCaptchaField +from .models import ContactPlus, ContactRecord +from .signals import contact_message_sent + + +class ContactFormPlus(forms.Form): + + def __init__(self, contactFormInstance, request, *args, **kwargs): + super(ContactFormPlus, self).__init__(*args, **kwargs) + if 'instance' not in kwargs: + for extraField in contactFormInstance.extrafield_set.all(): + if extraField.fieldType == 'CharField': + self.fields[slugify(extraField.label)] = forms.CharField(label=extraField.label, + initial=extraField.initial, + required=extraField.required) + elif extraField.fieldType == 'BooleanField': + self.fields[slugify(extraField.label)] = forms.BooleanField(label=extraField.label, + initial=extraField.initial, + required=extraField.required) + elif extraField.fieldType == 'EmailField': + self.fields[slugify(extraField.label)] = forms.EmailField(label=extraField.label, + initial=extraField.initial, + required=extraField.required) + elif extraField.fieldType == 'DecimalField': + self.fields[slugify(extraField.label)] = forms.DecimalField(label=extraField.label, + initial=extraField.initial, + required=extraField.required) + elif extraField.fieldType == 'FloatField': + self.fields[slugify(extraField.label)] = forms.FloatField(label=extraField.label, + initial=extraField.initial, + required=extraField.required) + elif extraField.fieldType == 'FileField': + self.fields[slugify(extraField.label)] = forms.FileField(label=extraField.label, + initial=extraField.initial, + required=extraField.required) + elif extraField.fieldType == 'ImageField': + self.fields[slugify(extraField.label)] = forms.ImageField(label=extraField.label, + initial=extraField.initial, + required=extraField.required) + elif extraField.fieldType == 'IntegerField': + self.fields[slugify(extraField.label)] = forms.IntegerField(label=extraField.label, + initial=extraField.initial, + required=extraField.required) + elif extraField.fieldType == 'IPAddressField': + self.fields[slugify(extraField.label)] = forms.IPAddressField(label=extraField.label, + initial=extraField.initial, + required=extraField.required) + elif extraField.fieldType == 'auto_Textarea': + self.fields[slugify(extraField.label)] = forms.CharField(label=extraField.label, + initial=extraField.initial, + widget=forms.Textarea, + required=extraField.required) + elif extraField.fieldType == 'auto_hidden_input': + self.fields[slugify(extraField.label)] = forms.CharField(label=extraField.label, + initial=extraField.initial, + widget=forms.HiddenInput, + required=False) + elif extraField.fieldType == 'auto_referral_page': + lInitial = _("No referral available.") + if request: + lInitial = request.META.get('HTTP_REFERER', _('No referral available.')) + self.fields[slugify(extraField.label)] = forms.CharField(label=extraField.label, + initial=lInitial, # NOTE: This overwrites extraField.initial! + widget=forms.HiddenInput, + required=False) + elif extraField.fieldType == 'MathCaptcha': + self.fields[slugify(extraField.label)] = MathCaptchaField( + label=extraField.label, + initial=extraField.initial, + required=True) + elif extraField.fieldType == 'auto_GET_parameter': + lInitial = _("Key/value parameter not available.") + if request: + lInitial = request.GET.get(slugify(extraField.label), 'n/a') + self.fields[slugify(extraField.label)] = forms.CharField(label=extraField.label, + initial=lInitial, # NOTE: This overwrites extraField.initial! + widget=forms.HiddenInput, + required=False) + + def send(self, recipient_email, request, ts, instance=None, multipart=False): + current_site = Site.objects.get_current() + if instance: + order = ContactPlus.objects.get(id=instance.id).extrafield_set.order_by('inline_ordering_position') + ordered_dic_list = [] + for field in order: + key = slugify(field.label) + value = self.cleaned_data.get(key, '(no input)') + # redefine value for files... + if field.fieldType in ["FileField", "ImageField"]: + val = ts + '-' + str(value) + if settings.MEDIA_URL.startswith("http"): + value = "%s%s" % (settings.MEDIA_URL, val) + else: + value = "http://%s%s%s" % (current_site, settings.MEDIA_URL, val) + ordered_dic_list.append({field.label: value}) + + # Automatically match reply-to email adress in form + tmp_headers = {} + try: + reply_email_label = getattr(settings, 'CONTACT_PLUS_REPLY_EMAIL_LABEL', None) + if reply_email_label is not None: + tmp_headers.update({'Reply-To': self.cleaned_data[reply_email_label]}) + except: + pass + + email_message = EmailMessage( + "[" + current_site.domain.upper() + "]", + render_to_string("cmsplugin_contact_plus/email.txt", {'data': self.cleaned_data, + 'ordered_data': ordered_dic_list, + 'instance': instance, + }), + from_email=getattr(settings, 'DEFAULT_FROM_EMAIL', None), + to=[recipient_email, ], + headers=tmp_headers,) + email_message.send(fail_silently=True) + + if instance.collect_records:# and not multipart: + record = ContactRecord(contact_form=instance, data=ordered_dic_list)#self.cleaned_data) + record.save() + + contact_message_sent.send(sender=self, data=self.cleaned_data) diff --git a/project/cmsplugin_contact_plus/inline_ordering/__init__.py b/project/cmsplugin_contact_plus/inline_ordering/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/project/cmsplugin_contact_plus/inline_ordering/admin.py b/project/cmsplugin_contact_plus/inline_ordering/admin.py new file mode 100644 index 0000000..5735067 --- /dev/null +++ b/project/cmsplugin_contact_plus/inline_ordering/admin.py @@ -0,0 +1,21 @@ +from django.contrib.admin import TabularInline, StackedInline +from django.conf import settings + +INLINE_ORDERING_JS = getattr(settings, + 'INLINE_ORDERING_JS', 'inline_ordering.js') + + +class OrderableStackedInline(StackedInline): + + """Adds necessary media files to regular Django StackedInline""" + + class Media: + js = (INLINE_ORDERING_JS,) + + +class OrderableTabularInline(TabularInline): + + """Adds necessary media files to regular Django TabularInline""" + + class Media: + js = (INLINE_ORDERING_JS,) diff --git a/project/cmsplugin_contact_plus/inline_ordering/media b/project/cmsplugin_contact_plus/inline_ordering/media new file mode 120000 index 0000000..93a464b --- /dev/null +++ b/project/cmsplugin_contact_plus/inline_ordering/media @@ -0,0 +1 @@ +static/ \ No newline at end of file diff --git a/project/cmsplugin_contact_plus/inline_ordering/models.py b/project/cmsplugin_contact_plus/inline_ordering/models.py new file mode 100644 index 0000000..1b773aa --- /dev/null +++ b/project/cmsplugin_contact_plus/inline_ordering/models.py @@ -0,0 +1,23 @@ +from django.db import models + +class Orderable(models.Model): + + """Add extra field and default ordering column for and inline orderable model""" + + inline_ordering_position = models.IntegerField(blank = True, + null = True, + editable = True) + + class Meta: + abstract = True + ordering = ('inline_ordering_position',) + + def save(self, force_insert=False, force_update=False, using=None): + """Calculate position (max+1) for new records""" + if not self.inline_ordering_position: + max = self.__class__.objects.filter().aggregate(models.Max('inline_ordering_position')) + try: + self.inline_ordering_position = max['inline_ordering_position__max'] + 1 + except TypeError: + self.inline_ordering_position = 1 + return super(Orderable, self).save(force_insert=force_insert, force_update=force_update, using=using) \ No newline at end of file diff --git a/project/cmsplugin_contact_plus/inline_ordering/settings.py b/project/cmsplugin_contact_plus/inline_ordering/settings.py new file mode 100644 index 0000000..c8b07b8 --- /dev/null +++ b/project/cmsplugin_contact_plus/inline_ordering/settings.py @@ -0,0 +1,4 @@ +from django.conf import settings + +# path to inline_ordering.js +INLINE_ORDERING_JS = getattr(settings, 'INLINE_ORDERING_JS', settings.MEDIA_URL + 'inline_ordering.js') diff --git a/project/cmsplugin_contact_plus/inline_ordering/static/inline_ordering.js b/project/cmsplugin_contact_plus/inline_ordering/static/inline_ordering.js new file mode 100644 index 0000000..63ccf93 --- /dev/null +++ b/project/cmsplugin_contact_plus/inline_ordering/static/inline_ordering.js @@ -0,0 +1,73 @@ +var InlineOrdering = { + + /** + * Get list of elements that can be reordered + * + * At this point, only already existent records can be reordered (ie. where pk != '') + * + * @return Array + * @todo Check if given record changed, and if so, make it reorderable + * @todo Primary key might not be 'id' - better selector needed + * + */ + getOrderables: function () { + var allInlineRows = InlineOrdering.jQuery('.inline-related'), + i = 0, + ids = []; + + for (i = 0; i < allInlineRows.length; i = i + 1) { + if (InlineOrdering.jQuery('.inline_ordering_position input, .field-inline_ordering_position input', allInlineRows[i]).val()) { + ids.push('#' + allInlineRows[i].id); + } + } + + // this redundant way is required, so that proper order is maintained, + // otherwise orderables were returned in more or less random order + return InlineOrdering.jQuery(ids.join(', ')); + }, + + /** + * Inits the jQuery UI D&D + * + */ + init: function (jQuery) { + InlineOrdering.jQuery = jQuery; + InlineOrdering.jQuery("div.inline-group").sortable({ + axis: 'y', + placeholder: 'ui-state-highlight', + forcePlaceholderSize: 'true', + items: InlineOrdering.getOrderables(), + update: InlineOrdering.update + }); + //jQuery("div.inline-group").disableSelection(); + + InlineOrdering.jQuery('div.field-inline_ordering_position').hide(); + InlineOrdering.jQuery('div.inline_ordering_position').hide(); + InlineOrdering.jQuery('td.inline_ordering_position input').hide(); + + InlineOrdering.jQuery('.add-row a').click(InlineOrdering.update); + + InlineOrdering.getOrderables().css('cursor', 'move'); + + InlineOrdering.update(); + }, + + jQuery: null, + + /** + * Updates the position field + * + */ + update: function () { + InlineOrdering.getOrderables().each(function (i) { + InlineOrdering.jQuery('input[id$=inline_ordering_position]', this).val(i + 1); + InlineOrdering.jQuery(this).find('h3 > span.position').remove(); + InlineOrdering.jQuery(this).find('h3').append('#' + (i + 1).toFixed() + ''); + }); + } + +}; + +django.jQuery(function () { + InlineOrdering.init(django.jQuery); +}); diff --git a/project/cmsplugin_contact_plus/jsonfield/__init__.py b/project/cmsplugin_contact_plus/jsonfield/__init__.py new file mode 100644 index 0000000..9c2a0ed --- /dev/null +++ b/project/cmsplugin_contact_plus/jsonfield/__init__.py @@ -0,0 +1 @@ +from .fields import JSONField, JSONCharField diff --git a/project/cmsplugin_contact_plus/jsonfield/encoder.py b/project/cmsplugin_contact_plus/jsonfield/encoder.py new file mode 100644 index 0000000..2b41e34 --- /dev/null +++ b/project/cmsplugin_contact_plus/jsonfield/encoder.py @@ -0,0 +1,59 @@ +from django.db.models.query import QuerySet +from django.utils import six, timezone +from django.utils.encoding import force_text +from django.utils.functional import Promise +import datetime +import decimal +import types +import json +import uuid + + +class JSONEncoder(json.JSONEncoder): + """ + JSONEncoder subclass that knows how to encode date/time/timedelta, + decimal types, generators and other basic python objects. + + Taken from https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/utils/encoders.py + """ + def default(self, obj): + # For Date Time string spec, see ECMA 262 + # http://ecma-international.org/ecma-262/5.1/#sec-15.9.1.15 + if isinstance(obj, Promise): + return force_text(obj) + elif isinstance(obj, datetime.datetime): + representation = obj.isoformat() + if obj.microsecond: + representation = representation[:23] + representation[26:] + if representation.endswith('+00:00'): + representation = representation[:-6] + 'Z' + return representation + elif isinstance(obj, datetime.date): + return obj.isoformat() + elif isinstance(obj, datetime.time): + if timezone and timezone.is_aware(obj): + raise ValueError("JSON can't represent timezone-aware times.") + representation = obj.isoformat() + if obj.microsecond: + representation = representation[:12] + return representation + elif isinstance(obj, datetime.timedelta): + return six.text_type(total_seconds(obj)) + elif isinstance(obj, decimal.Decimal): + # Serializers will coerce decimals to strings by default. + return float(obj) + elif isinstance(obj, uuid.UUID): + return six.text_type(obj) + elif isinstance(obj, QuerySet): + return tuple(obj) + elif hasattr(obj, 'tolist'): + # Numpy arrays and array scalars. + return obj.tolist() + elif hasattr(obj, '__getitem__'): + try: + return dict(obj) + except: + pass + elif hasattr(obj, '__iter__'): + return tuple(item for item in obj) + return super(JSONEncoder, self).default(obj) diff --git a/project/cmsplugin_contact_plus/jsonfield/fields.py b/project/cmsplugin_contact_plus/jsonfield/fields.py new file mode 100644 index 0000000..01dd6bc --- /dev/null +++ b/project/cmsplugin_contact_plus/jsonfield/fields.py @@ -0,0 +1,170 @@ +import copy +from django.db import models +from django.utils.translation import ugettext_lazy as _ +try: + from django.utils import six +except ImportError: + import six + +try: + import json +except ImportError: + from django.utils import simplejson as json + +from django.forms import fields +try: + from django.forms.utils import ValidationError +except ImportError: + from django.forms.util import ValidationError + +from .subclassing import SubfieldBase +from .encoder import JSONEncoder + + +class JSONFormFieldBase(object): + + def to_python(self, value): + if isinstance(value, six.string_types): + try: + return json.loads(value, **self.load_kwargs) + except ValueError: + raise ValidationError(_("Enter valid JSON")) + return value + + def clean(self, value): + + if not value and not self.required: + return None + + # Trap cleaning errors & bubble them up as JSON errors + try: + return super(JSONFormFieldBase, self).clean(value) + except TypeError: + raise ValidationError(_("Enter valid JSON")) + + +class JSONFormField(JSONFormFieldBase, fields.CharField): + pass + +class JSONCharFormField(JSONFormFieldBase, fields.CharField): + pass + + +class JSONFieldBase(six.with_metaclass(SubfieldBase, models.Field)): + + def __init__(self, *args, **kwargs): + self.dump_kwargs = kwargs.pop('dump_kwargs', { + 'cls': JSONEncoder, + 'separators': (',', ':') + }) + self.load_kwargs = kwargs.pop('load_kwargs', {}) + + super(JSONFieldBase, self).__init__(*args, **kwargs) + + def pre_init(self, value, obj): + """Convert a string value to JSON only if it needs to be deserialized. + + SubfieldBase metaclass has been modified to call this method instead of + to_python so that we can check the obj state and determine if it needs to be + deserialized""" + + try: + if obj._state.adding: + # Make sure the primary key actually exists on the object before + # checking if it's empty. This is a special case for South datamigrations + # see: https://github.com/bradjasper/django-jsonfield/issues/52 + if getattr(obj, "pk", None) is not None: + if isinstance(value, six.string_types): + try: + return json.loads(value, **self.load_kwargs) + except ValueError: + raise ValidationError(_("Enter valid JSON")) + + except AttributeError: + # south fake meta class doesn't create proper attributes + # see this: + # https://github.com/bradjasper/django-jsonfield/issues/52 + pass + + return value + + def to_python(self, value): + """The SubfieldBase metaclass calls pre_init instead of to_python, however to_python + is still necessary for Django's deserializer""" + return value + + def get_db_prep_value(self, value, connection, prepared=False): + """Convert JSON object to a string""" + if self.null and value is None: + return None + return json.dumps(value, **self.dump_kwargs) + + def value_to_string(self, obj): + value = self._get_val_from_obj(obj) + return self.get_db_prep_value(value, None) + + def value_from_object(self, obj): + value = super(JSONFieldBase, self).value_from_object(obj) + if self.null and value is None: + return None + return self.dumps_for_display(value) + + def dumps_for_display(self, value): + return json.dumps(value, **self.dump_kwargs) + + def formfield(self, **kwargs): + + if "form_class" not in kwargs: + kwargs["form_class"] = self.form_class + + field = super(JSONFieldBase, self).formfield(**kwargs) + + if isinstance(field, JSONFormFieldBase): + field.load_kwargs = self.load_kwargs + + if not field.help_text: + field.help_text = "Enter valid JSON" + + return field + + def get_default(self): + """ + Returns the default value for this field. + + The default implementation on models.Field calls force_unicode + on the default, which means you can't set arbitrary Python + objects as the default. To fix this, we just return the value + without calling force_unicode on it. Note that if you set a + callable as a default, the field will still call it. It will + *not* try to pickle and encode it. + + """ + if self.has_default(): + if callable(self.default): + return self.default() + return copy.deepcopy(self.default) + # If the field doesn't have a default, then we punt to models.Field. + return super(JSONFieldBase, self).get_default() + +class JSONField(JSONFieldBase, models.TextField): + """JSONField is a generic textfield that serializes/deserializes JSON objects""" + form_class = JSONFormField + + def dumps_for_display(self, value): + kwargs = { "indent": 2 } + kwargs.update(self.dump_kwargs) + return json.dumps(value, **kwargs) + + +class JSONCharField(JSONFieldBase, models.CharField): + """JSONCharField is a generic textfield that serializes/deserializes JSON objects, + stored in the database like a CharField, which enables it to be used + e.g. in unique keys""" + form_class = JSONCharFormField + + +try: + from south.modelsinspector import add_introspection_rules + add_introspection_rules([], ["^jsonfield\.fields\.(JSONField|JSONCharField)"]) +except ImportError: + pass diff --git a/project/cmsplugin_contact_plus/jsonfield/models.py b/project/cmsplugin_contact_plus/jsonfield/models.py new file mode 100644 index 0000000..e5faf1b --- /dev/null +++ b/project/cmsplugin_contact_plus/jsonfield/models.py @@ -0,0 +1 @@ +# Django needs this to see it as a project diff --git a/project/cmsplugin_contact_plus/jsonfield/subclassing.py b/project/cmsplugin_contact_plus/jsonfield/subclassing.py new file mode 100644 index 0000000..21d0fc6 --- /dev/null +++ b/project/cmsplugin_contact_plus/jsonfield/subclassing.py @@ -0,0 +1,60 @@ +## This file was copied from django.db.models.fields.subclassing so that we could +## change the Creator.__set__ behavior. Read the comment below for full details. + +""" +Convenience routines for creating non-trivial Field subclasses, as well as +backwards compatibility utilities. + +Add SubfieldBase as the __metaclass__ for your Field subclass, implement +to_python() and the other necessary methods and everything will work seamlessly. +""" + +class SubfieldBase(type): + """ + A metaclass for custom Field subclasses. This ensures the model's attribute + has the descriptor protocol attached to it. + """ + def __new__(cls, name, bases, attrs): + new_class = super(SubfieldBase, cls).__new__(cls, name, bases, attrs) + new_class.contribute_to_class = make_contrib( + new_class, attrs.get('contribute_to_class') + ) + return new_class + +class Creator(object): + """ + A placeholder class that provides a way to set the attribute on the model. + """ + def __init__(self, field): + self.field = field + + def __get__(self, obj, type=None): + if obj is None: + raise AttributeError('Can only be accessed via an instance.') + return obj.__dict__[self.field.name] + + def __set__(self, obj, value): + # Usually this would call to_python, but we've changed it to pre_init + # so that we can tell which state we're in. By passing an obj, + # we can definitively tell if a value has already been deserialized + # More: https://github.com/bradjasper/django-jsonfield/issues/33 + obj.__dict__[self.field.name] = self.field.pre_init(value, obj) + + +def make_contrib(superclass, func=None): + """ + Returns a suitable contribute_to_class() method for the Field subclass. + + If 'func' is passed in, it is the existing contribute_to_class() method on + the subclass and it is called before anything else. It is assumed in this + case that the existing contribute_to_class() calls all the necessary + superclass methods. + """ + def contribute_to_class(self, cls, name): + if func: + func(self, cls, name) + else: + super(superclass, self).contribute_to_class(cls, name) + setattr(cls, self.name, Creator(self)) + + return contribute_to_class diff --git a/project/cmsplugin_contact_plus/jsonfield/tests.py b/project/cmsplugin_contact_plus/jsonfield/tests.py new file mode 100644 index 0000000..5faae20 --- /dev/null +++ b/project/cmsplugin_contact_plus/jsonfield/tests.py @@ -0,0 +1,273 @@ +from decimal import Decimal +from django.core.serializers import deserialize, serialize +from django.core.serializers.base import DeserializationError +from django.db import models +from django.test import TestCase +try: + import json +except ImportError: + from django.utils import simplejson as json + +from .fields import JSONField, JSONCharField +from django.forms.util import ValidationError + +from collections import OrderedDict + +class JsonModel(models.Model): + json = JSONField() + default_json = JSONField(default={"check":12}) + complex_default_json = JSONField(default=[{"checkcheck": 1212}]) + empty_default = JSONField(default={}) + +class JsonCharModel(models.Model): + json = JSONCharField(max_length=100) + default_json = JSONCharField(max_length=100, default={"check":34}) + +class ComplexEncoder(json.JSONEncoder): + def default(self, obj): + if isinstance(obj, complex): + return { + '__complex__': True, + 'real': obj.real, + 'imag': obj.imag, + } + + return json.JSONEncoder.default(self, obj) + +def as_complex(dct): + if '__complex__' in dct: + return complex(dct['real'], dct['imag']) + return dct + +class JSONModelCustomEncoders(models.Model): + # A JSON field that can store complex numbers + json = JSONField( + dump_kwargs={'cls': ComplexEncoder, "indent": 4}, + load_kwargs={'object_hook': as_complex}, + ) + +class JSONFieldTest(TestCase): + """JSONField Wrapper Tests""" + + json_model = JsonModel + + def test_json_field_create(self): + """Test saving a JSON object in our JSONField""" + json_obj = { + "item_1": "this is a json blah", + "blergh": "hey, hey, hey"} + + obj = self.json_model.objects.create(json=json_obj) + new_obj = self.json_model.objects.get(id=obj.id) + + self.assertEqual(new_obj.json, json_obj) + + def test_string_in_json_field(self): + """Test saving an ordinary Python string in our JSONField""" + json_obj = 'blah blah' + obj = self.json_model.objects.create(json=json_obj) + new_obj = self.json_model.objects.get(id=obj.id) + + self.assertEqual(new_obj.json, json_obj) + + def test_float_in_json_field(self): + """Test saving a Python float in our JSONField""" + json_obj = 1.23 + obj = self.json_model.objects.create(json=json_obj) + new_obj = self.json_model.objects.get(id=obj.id) + + self.assertEqual(new_obj.json, json_obj) + + def test_int_in_json_field(self): + """Test saving a Python integer in our JSONField""" + json_obj = 1234567 + obj = self.json_model.objects.create(json=json_obj) + new_obj = self.json_model.objects.get(id=obj.id) + + self.assertEqual(new_obj.json, json_obj) + + def test_decimal_in_json_field(self): + """Test saving a Python Decimal in our JSONField""" + json_obj = Decimal(12.34) + obj = self.json_model.objects.create(json=json_obj) + new_obj = self.json_model.objects.get(id=obj.id) + + # here we must know to convert the returned string back to Decimal, + # since json does not support that format + self.assertEqual(Decimal(new_obj.json), json_obj) + + def test_json_field_modify(self): + """Test modifying a JSON object in our JSONField""" + json_obj_1 = {'a': 1, 'b': 2} + json_obj_2 = {'a': 3, 'b': 4} + + obj = self.json_model.objects.create(json=json_obj_1) + self.assertEqual(obj.json, json_obj_1) + obj.json = json_obj_2 + + self.assertEqual(obj.json, json_obj_2) + obj.save() + self.assertEqual(obj.json, json_obj_2) + + self.assertTrue(obj) + + def test_json_field_load(self): + """Test loading a JSON object from the DB""" + json_obj_1 = {'a': 1, 'b': 2} + obj = self.json_model.objects.create(json=json_obj_1) + new_obj = self.json_model.objects.get(id=obj.id) + + self.assertEqual(new_obj.json, json_obj_1) + + def test_json_list(self): + """Test storing a JSON list""" + json_obj = ["my", "list", "of", 1, "objs", {"hello": "there"}] + + obj = self.json_model.objects.create(json=json_obj) + new_obj = self.json_model.objects.get(id=obj.id) + self.assertEqual(new_obj.json, json_obj) + + def test_empty_objects(self): + """Test storing empty objects""" + for json_obj in [{}, [], 0, '', False]: + obj = self.json_model.objects.create(json=json_obj) + new_obj = self.json_model.objects.get(id=obj.id) + self.assertEqual(json_obj, obj.json) + self.assertEqual(json_obj, new_obj.json) + + def test_custom_encoder(self): + """Test encoder_cls and object_hook""" + value = 1 + 3j # A complex number + + obj = JSONModelCustomEncoders.objects.create(json=value) + new_obj = JSONModelCustomEncoders.objects.get(pk=obj.pk) + self.assertEqual(value, new_obj.json) + + def test_django_serializers(self): + """Test serializing/deserializing jsonfield data""" + for json_obj in [{}, [], 0, '', False, {'key': 'value', 'num': 42, + 'ary': list(range(5)), + 'dict': {'k': 'v'}}]: + obj = self.json_model.objects.create(json=json_obj) + new_obj = self.json_model.objects.get(id=obj.id) + self.assert_(new_obj) + + queryset = self.json_model.objects.all() + ser = serialize('json', queryset) + for dobj in deserialize('json', ser): + obj = dobj.object + pulled = self.json_model.objects.get(id=obj.pk) + self.assertEqual(obj.json, pulled.json) + + def test_default_parameters(self): + """Test providing a default value to the model""" + model = JsonModel() + model.json = {"check": 12} + self.assertEqual(model.json, {"check": 12}) + self.assertEqual(type(model.json), dict) + + self.assertEqual(model.default_json, {"check": 12}) + self.assertEqual(type(model.default_json), dict) + + def test_invalid_json(self): + # invalid json data {] in the json and default_json fields + ser = '[{"pk": 1, "model": "jsonfield.jsoncharmodel", ' \ + '"fields": {"json": "{]", "default_json": "{]"}}]' + with self.assertRaises(DeserializationError) as cm: + next(deserialize('json', ser)) + inner = cm.exception.args[0] + self.assertTrue(isinstance(inner, ValidationError)) + self.assertEqual('Enter valid JSON', inner.messages[0]) + + def test_integer_in_string_in_json_field(self): + """Test saving the Python string '123' in our JSONField""" + json_obj = '123' + obj = self.json_model.objects.create(json=json_obj) + new_obj = self.json_model.objects.get(id=obj.id) + + self.assertEqual(new_obj.json, json_obj) + + def test_boolean_in_string_in_json_field(self): + """Test saving the Python string 'true' in our JSONField""" + json_obj = 'true' + obj = self.json_model.objects.create(json=json_obj) + new_obj = self.json_model.objects.get(id=obj.id) + + self.assertEqual(new_obj.json, json_obj) + + + def test_pass_by_reference_pollution(self): + """Make sure the default parameter is copied rather than passed by reference""" + model = JsonModel() + model.default_json["check"] = 144 + model.complex_default_json[0]["checkcheck"] = 144 + self.assertEqual(model.default_json["check"], 144) + self.assertEqual(model.complex_default_json[0]["checkcheck"], 144) + + # Make sure when we create a new model, it resets to the default value + # and not to what we just set it to (it would be if it were passed by reference) + model = JsonModel() + self.assertEqual(model.default_json["check"], 12) + self.assertEqual(model.complex_default_json[0]["checkcheck"], 1212) + + def test_normal_regex_filter(self): + """Make sure JSON model can filter regex""" + + JsonModel.objects.create(json={"boom": "town"}) + JsonModel.objects.create(json={"move": "town"}) + JsonModel.objects.create(json={"save": "town"}) + + self.assertEqual(JsonModel.objects.count(), 3) + + self.assertEqual(JsonModel.objects.filter(json__regex=r"boom").count(), 1) + self.assertEqual(JsonModel.objects.filter(json__regex=r"town").count(), 3) + + def test_save_blank_object(self): + """Test that JSON model can save a blank object as none""" + + model = JsonModel() + self.assertEqual(model.empty_default, {}) + + model.save() + self.assertEqual(model.empty_default, {}) + + model1 = JsonModel(empty_default={"hey": "now"}) + self.assertEqual(model1.empty_default, {"hey": "now"}) + + model1.save() + self.assertEqual(model1.empty_default, {"hey": "now"}) + + + +class JSONCharFieldTest(JSONFieldTest): + json_model = JsonCharModel + + +class OrderedJsonModel(models.Model): + json = JSONField(load_kwargs={'object_pairs_hook': OrderedDict}) + + +class OrderedDictSerializationTest(TestCase): + ordered_dict = OrderedDict([ + ('number', [1, 2, 3, 4]), + ('notes', True), + ]) + expected_key_order = ['number', 'notes'] + + def test_ordered_dict_differs_from_normal_dict(self): + self.assertEqual(list(self.ordered_dict.keys()), self.expected_key_order) + self.assertNotEqual(dict(self.ordered_dict).keys(), self.expected_key_order) + + def test_default_behaviour_loses_sort_order(self): + mod = JsonModel.objects.create(json=self.ordered_dict) + self.assertEqual(list(mod.json.keys()), self.expected_key_order) + mod_from_db = JsonModel.objects.get(id=mod.id) + + # mod_from_db lost ordering information during json.loads() + self.assertNotEqual(mod_from_db.json.keys(), self.expected_key_order) + + def test_load_kwargs_hook_does_not_lose_sort_order(self): + mod = OrderedJsonModel.objects.create(json=self.ordered_dict) + self.assertEqual(list(mod.json.keys()), self.expected_key_order) + mod_from_db = OrderedJsonModel.objects.get(id=mod.id) + self.assertEqual(list(mod_from_db.json.keys()), self.expected_key_order) diff --git a/project/cmsplugin_contact_plus/local_settings.py b/project/cmsplugin_contact_plus/local_settings.py new file mode 100644 index 0000000..e69de29 diff --git a/project/cmsplugin_contact_plus/locale/es/LC_MESSAGES/django.mo b/project/cmsplugin_contact_plus/locale/es/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..21581f3dd1142c180636c56c5ed4d5bdce9b0649 GIT binary patch literal 2386 zcmZvc&u<$=6vqcz3YcGQ`ThHW2u*0$jY|-f+n|QTNloL#IBpRYm+|h{ooYQ}W@cRi zLh2vDp&(V_!XX@y;DW>faY0s3oZ(Ul{s9gMi7Vp!b~nE&Mp=I{yKmm_dA}b$cvE0K zgXcLsf8v?K^V41U!D`e}S&?BbbK#ujDLqfa;D)4FW2FQB9Dfd5vk70is_j~CZ#dvT5*z=QPUi+$&~c^xf~ z*NN)wWv&g&#mrfEi55c@uK)ki@(CB>pmQxym5sMxy1 zV{d}Qe%ZR=&ZeDaK7T`f>u73MsyMYHcc&6a4{4~pmjgw8XNUC3r(#J4Md6AnP0}^^ zIdV3Jm`bXmF_bzb+b5@DJ<`eqU%vBM?8O!) zA!_-7PNKK7fseYjPL=k0H(Eqz1}KWSSL$?Xwl){lYEf;DYH!wOPaU7df?(89W6cIj zx%V3jov5`QEiKUb^?Iv${POx*qp~V}h`LT1pK^8Tw3?w0E>E@`H{#h8yN_jU?%aSI zuNvFoTR36{8Il)ul^o*0!rID3cKz(?>bX=`tsCshz~xr6)!2=*R-LVI){u~>yE%gY z0XaX37SQGl#kihch53u!<>>UT9J`0AM-3C(1f5Z*(>)z3xOixUh%ckBlNse(KY!{B z75~mp8+vJ;YBOglVo?r9mJ&rV?naD8Q=V*`rNRF1Cj@FKGlV=S$;a4Bi{^Pam4%mn_J5=H^bDIRzZ@#)x# zwsey%OcF#$JXng1d9k4hdL~=NG_fZr*aRYMU?(N)5`^~o>=pDGLi@Nx@(E=qmBe#} z#d$z@fx&_~s^Nf^DZ9Y4%-MXvP@KxK;)w$h1IIBvtkIY=q7n3mnFfbw&Zq{=6RHY$4wa*%=c_4+p2I$bSK}2EVbJ#Z0_@uwU+bFiVaRK<-><#EK-{f M9af8`&yddl0Ai7&f&c&j literal 0 HcmV?d00001 diff --git a/project/cmsplugin_contact_plus/locale/es/LC_MESSAGES/django.po b/project/cmsplugin_contact_plus/locale/es/LC_MESSAGES/django.po new file mode 100644 index 0000000..11d87fc --- /dev/null +++ b/project/cmsplugin_contact_plus/locale/es/LC_MESSAGES/django.po @@ -0,0 +1,126 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# Luis Zárate , 2014. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2014-11-14 17:02+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Luis Zárate \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" + +#: cmsplugin_contact_plus/cms_plugins.py:14 +#: cmsplugin_contact_plus/models.py:83 cmsplugin_contact_plus/models.py:100 +msgid "Contact Form" +msgstr "Formulario de contacto" + +#: cmsplugin_contact_plus/forms.py:58 cmsplugin_contact_plus/forms.py:60 +msgid "No referral available." +msgstr "Remisión no disponible" + +#: cmsplugin_contact_plus/forms.py:71 +msgid "Key/value parameter not available." +msgstr "Parametro para clave/valor no disponible" + +#: cmsplugin_contact_plus/models.py:31 +msgid "Contact form message from {}" +msgstr "Mensaje de formulario para {}" + +#: cmsplugin_contact_plus/models.py:34 +msgid "Title" +msgstr "Título" + +#: cmsplugin_contact_plus/models.py:34 +msgid "Title for the Contact Form." +msgstr "Título para el formulario de contacto" + +#: cmsplugin_contact_plus/models.py:36 +msgid "Email subject" +msgstr "Asunto del correo electrónico" + +#: cmsplugin_contact_plus/models.py:39 +msgid "Email of recipients" +msgstr "Correo de los destinatarios " + +#: cmsplugin_contact_plus/models.py:41 +msgid "Collect Records" +msgstr "Recolección de registros" + +#: cmsplugin_contact_plus/models.py:41 +msgid "If active, all records for this Form will be stored in the Database." +msgstr "Si está activo, todos los registros de este formulario serán guardados en la base de datos" + +#: cmsplugin_contact_plus/models.py:43 +msgid "Message displayed after submitting the contact form." +msgstr "Mensaje mostrado despúes de enviar el formulario" + +#: cmsplugin_contact_plus/models.py:45 +msgid "Text for the Submit button." +msgstr "Texto para el botón de enviar" + +#: cmsplugin_contact_plus/models.py:65 +#, python-format +msgid "Contact Plus Form for %s" +msgstr "Contact Plus Form para %s" + +#: cmsplugin_contact_plus/models.py:75 +msgid "CharField as Textarea" +msgstr "Campo de caracteres como un área de texto" + +#: cmsplugin_contact_plus/models.py:76 +msgid "CharField as HiddenInput" +msgstr "Campo de caracteres oculto" + +#: cmsplugin_contact_plus/models.py:77 +msgid "Referral page as HiddenInput" +msgstr "Página de remisión oculta" + +#: cmsplugin_contact_plus/models.py:78 +msgid "GET parameter as HiddenInput" +msgstr "Parámetros GET ocultos" + +#: cmsplugin_contact_plus/models.py:84 +msgid "Label" +msgstr "Etiqueta" + +#: cmsplugin_contact_plus/models.py:87 +msgid "Inital Value" +msgstr "Valor inicial" + +#: cmsplugin_contact_plus/models.py:89 +msgid "Mandatory field" +msgstr "Campo Obligatorio" + +#: cmsplugin_contact_plus/models.py:91 +msgid "Widget" +msgstr "" + +#: cmsplugin_contact_plus/models.py:92 +msgid "Will be ignored in the current version." +msgstr "Será ignorado en la versión actual" + +#: cmsplugin_contact_plus/models.py:102 +msgid "Date the Record was processed." +msgstr "Fecha de registro fue procesada" + +#: cmsplugin_contact_plus/models.py:107 +msgid "Contact Record" +msgstr "Registro de contacto" + +#: cmsplugin_contact_plus/models.py:108 +msgid "Contact Records" +msgstr "Registros de contacto" + +#: cmsplugin_contact_plus/models.py:118 +#, python-format +msgid "Record for %(contact)s recorded on %(date)s" +msgstr "Registro para %(contact)s registrados el %(date)s" diff --git a/project/cmsplugin_contact_plus/migrations/0001_initial.py b/project/cmsplugin_contact_plus/migrations/0001_initial.py new file mode 100644 index 0000000..63168f8 --- /dev/null +++ b/project/cmsplugin_contact_plus/migrations/0001_initial.py @@ -0,0 +1,69 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +import django.db.models.deletion +import cmsplugin_contact_plus.models +import jsonfield.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('cms', '0003_auto_20140926_2347'), + ] + + operations = [ + migrations.CreateModel( + name='ContactPlus', + fields=[ + ('cmsplugin_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='cms.CMSPlugin')), + ('title', models.CharField(help_text='Title for the Contact Form.', max_length=100, null=True, verbose_name='Title', blank=True)), + ('email_subject', models.CharField(default=cmsplugin_contact_plus.models.get_current_site, max_length=256, verbose_name='Email subject')), + ('recipient_email', models.EmailField(default=b'', max_length=75, verbose_name='Email of recipients')), + ('collect_records', models.BooleanField(default=True, help_text='If active, all records for this Form will be stored in the Database.', verbose_name='Collect Records')), + ('thanks', models.TextField(verbose_name='Message displayed after submitting the contact form.')), + ('submit', models.CharField(max_length=30, verbose_name='Text for the Submit button.', blank=True)), + ('template', models.CharField(default=b'cmsplugin_contact_plus/contact.html', max_length=255, editable=False, choices=[(b'cmsplugin_contact_plus/contact.html', b'contact.html')])), + ], + options={ + 'verbose_name': 'Contact Plus Form', + 'verbose_name_plural': 'Contact Plus Forms', + }, + bases=('cms.cmsplugin',), + ), + migrations.CreateModel( + name='ContactRecord', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('date_of_entry', models.DateTimeField(auto_now_add=True)), + ('date_processed', models.DateTimeField(help_text=b'Date the Record was processed.', null=True, blank=True)), + ('data', jsonfield.fields.JSONField(default={}, null=True, blank=True)), + ('contact_form', models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, verbose_name='Contact Form', to='cmsplugin_contact_plus.ContactPlus', null=True)), + ], + options={ + 'ordering': ['date_of_entry', 'contact_form'], + 'verbose_name': 'Contact Record', + 'verbose_name_plural': 'Contact Records', + }, + bases=(models.Model,), + ), + migrations.CreateModel( + name='ExtraField', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('inline_ordering_position', models.IntegerField(null=True, blank=True)), + ('label', models.CharField(max_length=100, verbose_name='Label')), + ('fieldType', models.CharField(max_length=100, choices=[(b'CharField', b'CharField'), (b'BooleanField', b'BooleanField'), (b'EmailField', b'EmailField'), (b'DecimalField', b'DecimalField'), (b'FloatField', b'FloatField'), (b'IntegerField', b'IntegerField'), (b'IPAddressField', b'IPAddressField'), (b'auto_Textarea', b'CharField as Textarea'), (b'auto_hidden_input', b'CharField as HiddenInput'), (b'auto_referral_page', b'Referral page as HiddenInput'), (b'auto_GET_parameter', b'GET parameter as HiddenInput')])), + ('initial', models.CharField(max_length=250, null=True, verbose_name='Inital Value', blank=True)), + ('required', models.BooleanField(default=True, verbose_name='Mandatory field')), + ('widget', models.CharField(help_text='Will be ignored in the current version.', max_length=250, null=True, verbose_name='Widget', blank=True)), + ('form', models.ForeignKey(verbose_name='Contact Form', to='cmsplugin_contact_plus.ContactPlus')), + ], + options={ + 'ordering': ('inline_ordering_position',), + 'abstract': False, + }, + bases=(models.Model,), + ), + ] diff --git a/project/cmsplugin_contact_plus/migrations/__init__.py b/project/cmsplugin_contact_plus/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/project/cmsplugin_contact_plus/models.py b/project/cmsplugin_contact_plus/models.py new file mode 100755 index 0000000..ee2df83 --- /dev/null +++ b/project/cmsplugin_contact_plus/models.py @@ -0,0 +1,134 @@ +import threading + +from django.conf import settings +from django.db import models +from django.utils.translation import ugettext_lazy as _ +from django.contrib.sites.models import Site +from django.utils.encoding import python_2_unicode_compatible +from django.db.models import Model + +from cms.models import CMSPlugin +from inline_ordering.models import Orderable +from jsonfield import JSONField + +try: + DEFAULT_FROM_EMAIL_ADDRESS = settings.ADMINS[0][1] +except: + DEFAULT_FROM_EMAIL_ADDRESS = '' + +from . import utils + + +localdata = threading.local() +localdata.TEMPLATE_CHOICES = utils.autodiscover_templates() +TEMPLATE_CHOICES = localdata.TEMPLATE_CHOICES + +def get_current_site(): + try: + current_site = Site.objects.get(id=1).name + except: + current_site = 'example.com' + return _('Contact form message from %s') % (current_site) +@python_2_unicode_compatible +class ContactPlus(CMSPlugin): + title = models.CharField(_('Title'), + null=True, + blank=True, + max_length=100, + help_text=_("Title for the Contact Form.")) + email_subject = models.CharField( + max_length=256, + verbose_name=_("Email subject"), + default=get_current_site) + recipient_email = models.EmailField(_("Email of recipients"), + default=DEFAULT_FROM_EMAIL_ADDRESS) + collect_records = models.BooleanField(_('Collect Records'), + default=True, + help_text=_("If active, all records for this Form will be stored in the Database.")) + thanks = models.TextField(_('Message displayed after submitting the contact form.')) + submit = models.CharField(_('Text for the Submit button.'), + blank=True, + max_length=30) + template = models.CharField( + max_length=255, + choices=TEMPLATE_CHOICES, + default='cmsplugin_contact_plus/contact.html', + editable=len(TEMPLATE_CHOICES) > 1) + + class Meta: + verbose_name = "Contact Plus Form" + verbose_name_plural = "Contact Plus Forms" + + def copy_relations(self, oldinstance): + for extrafield in ExtraField.objects.filter(form__pk=oldinstance.pk): + extrafield.pk = None + extrafield.save() + self.extrafield_set.add( + extrafield) + + def __str__(self): + if self.title: + return self.title + return _("Contact Plus Form for %s") % self.recipient_email + + +FIELD_TYPE = (('CharField', 'CharField'), + ('BooleanField', 'BooleanField'), + ('EmailField', 'EmailField'), + ('DecimalField', 'DecimalField'), + ('FloatField', 'FloatField'), + ('IntegerField', 'IntegerField'), + ('FileField', 'FileField'), + ('ImageField', 'ImageField'), + ('IPAddressField', 'IPAddressField'), + ('MathCaptcha','Math Captcha'), + ('auto_Textarea', _('CharField as Textarea')), + ('auto_hidden_input', _('CharField as HiddenInput')), + ('auto_referral_page', _('Referral page as HiddenInput')), + ('auto_GET_parameter', _('GET parameter as HiddenInput'))) + + +@python_2_unicode_compatible +class ExtraField(Orderable): + """ + """ + form = models.ForeignKey(ContactPlus, verbose_name=_("Contact Form")) + label = models.CharField(_('Label'), max_length=100) + fieldType = models.CharField(max_length=100, choices=FIELD_TYPE) + initial = models.CharField( + _('Inital Value'), max_length=250, blank=True, null=True) + required = models.BooleanField( + _('Mandatory field'), default=True) + widget = models.CharField( + _('Widget'), max_length=250, blank=True, null=True, + help_text=_("Will be ignored in the current version.")) + + def __str__(self): + return self.label + + +@python_2_unicode_compatible +class ContactRecord(Model): + """ + """ + contact_form = models.ForeignKey(ContactPlus, verbose_name=_("Contact Form"), null=True, on_delete=models.SET_NULL) + date_of_entry = models.DateTimeField(auto_now_add=True) + date_processed = models.DateTimeField(null=True, blank=True, help_text=_("Date the Record was processed.")) + data = JSONField(null=True, blank=True, default={}) + + class Meta(): + ordering = ['date_of_entry', 'contact_form', ] + verbose_name = _("Contact Record") + verbose_name_plural = _("Contact Records") + + @property + def is_processed(self): + if self.date_processed: + return True + else: + return False + + def __str__(self): + return _("Record for %(contact)s recorded on %(date)s") % {'contact':self.contact_form, + 'date': self.date_of_entry.strftime('%d. %b %Y') } + diff --git a/project/cmsplugin_contact_plus/signals.py b/project/cmsplugin_contact_plus/signals.py new file mode 100644 index 0000000..6bfd831 --- /dev/null +++ b/project/cmsplugin_contact_plus/signals.py @@ -0,0 +1,3 @@ +from django.dispatch import Signal + +contact_message_sent = Signal() diff --git a/project/cmsplugin_contact_plus/simplemathcaptcha/__init__.py b/project/cmsplugin_contact_plus/simplemathcaptcha/__init__.py new file mode 100644 index 0000000..6a6cd9a --- /dev/null +++ b/project/cmsplugin_contact_plus/simplemathcaptcha/__init__.py @@ -0,0 +1,16 @@ +VERSION = (1, 0, 5, "f", 0) # following PEP 386 +DEV_N = None + + +def get_version(): # pragma: nocover + version = "%s.%s" % (VERSION[0], VERSION[1]) + if VERSION[2]: + version = "%s.%s" % (version, VERSION[2]) + if VERSION[3] != "f": + version = "%s%s%s" % (version, VERSION[3], VERSION[4]) + if DEV_N: + version = "%s.dev%s" % (version, DEV_N) + return version + + +__version__ = get_version() diff --git a/project/cmsplugin_contact_plus/simplemathcaptcha/fields.py b/project/cmsplugin_contact_plus/simplemathcaptcha/fields.py new file mode 100644 index 0000000..4328b9c --- /dev/null +++ b/project/cmsplugin_contact_plus/simplemathcaptcha/fields.py @@ -0,0 +1,63 @@ +from __future__ import absolute_import +from __future__ import unicode_literals + +from django import forms +from django.utils.translation import ugettext_lazy as _ +from django.core.exceptions import ValidationError + +from .widgets import MathCaptchaWidget +from .utils import hash_answer + + +class MathCaptchaField(forms.MultiValueField): + default_error_messages = { + 'invalid': _('Please check your math and try again.'), + 'invalid_number': _('Enter a whole number.'), + } + + def __init__(self, *args, **kwargs): + self._ensure_widget(kwargs) + kwargs['required'] = True + # we skip MultiValueField handling of fields and setup ourselves + super(MathCaptchaField, self).__init__((), *args, **kwargs) + self._setup_fields() + + def compress(self, data_list): + """Compress takes the place of clean with MultiValueFields""" + if data_list: + answer = data_list[0] + real_hashed_answer = data_list[1] + hashed_answer = hash_answer(answer) + if hashed_answer != real_hashed_answer: + raise ValidationError(self.error_messages['invalid']) + return None + + def _ensure_widget(self, kwargs): + widget_params = self._extract_widget_params(kwargs) + + if 'widget' not in kwargs or not kwargs['widget']: + kwargs['widget'] = MathCaptchaWidget(**widget_params) + elif widget_params: + msg = '%s must be omitted when widget is provided for %s.' + msg = msg % (' and '.join(list(widget_params)), + self.__class__.__name__) + raise TypeError(msg) + + def _extract_widget_params(self, kwargs): + params = {} + for key in ('start_int', 'end_int'): + if key in kwargs: + params[key] = kwargs.pop(key) + return params + + def _setup_fields(self): + error_messages = {'invalid': self.error_messages['invalid_number']} + # set fields + fields = ( + forms.IntegerField(error_messages=error_messages, + localize=self.localize), + forms.CharField() + ) + for field in fields: + field.required = False + self.fields = fields diff --git a/project/cmsplugin_contact_plus/simplemathcaptcha/locale/es/LC_MESSAGES/django.mo b/project/cmsplugin_contact_plus/simplemathcaptcha/locale/es/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..901b006c327ddeac93903c345d5d9baa188a294e GIT binary patch literal 1188 zcmah|%Z?LA6m0?tlG!0;5sAfRF=8wz#~zUYJs2>ap23P=@pwS6K{efX+6CQJO;xoA z{{vfAtdRHvHVF$5ng7T>Kag#5+6*%YAt>qe>05no-AA4N`|PRb0oG~Y954mW0MCGG z{Q*S4pTLj6e?Yr>KM@35;BD{<_yza^_&xYB@DDJAy>l`MegeM&Uje@b{|^2a+yLJ> z6$F>S`(T6w-viac=Kx#5ca`zUs_OiqvR76MX=U!}j`GrZ+GR4i@}3jgpRuNWm89%R zIV!A~s|5Rd#?s1aoJr@j%(?CwL8Lufk_a&wlP>dJwvAvQ{j4hsAJ62y564)3QYy=i zSSJsaUZzVe%PQK>q$cO3_0^ZJMi<5@ujWi}MPg-i zNaIfTx6W2XccaO6Z#1k;xG>g>flE~)ZkMSOQxj5cJerDb)hvR<26_%@t=YOPT20Zq zOs!wS<~7m$wb_KDnDAUxhadXgk2(`E7>SJz-5G_0-o*!_VN~l&=f%`Y?Xs%QkT!Sv zeHwNK5nYc4onHS|XJca$ZExRzt4>RqvY4{WL+W>iTRTc65}W?amR*muaG6C9b;BZ z<&j=1QGYJdo`%#A0WW>(SDNsMM=b?{o#f;<$-lvMv1&rR^xyKafOcyw|o(yvJOfBUCs A0RR91 literal 0 HcmV?d00001 diff --git a/project/cmsplugin_contact_plus/simplemathcaptcha/locale/es/LC_MESSAGES/django.po b/project/cmsplugin_contact_plus/simplemathcaptcha/locale/es/LC_MESSAGES/django.po new file mode 100644 index 0000000..ccb239b --- /dev/null +++ b/project/cmsplugin_contact_plus/simplemathcaptcha/locale/es/LC_MESSAGES/django.po @@ -0,0 +1,45 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2014-10-14 15:09-0600\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" + +#: fields.py:14 +msgid "Please check your math and try again." +msgstr "Por favor revise la ecuación y trate de nuevo" + +#: fields.py:15 +msgid "Enter a whole number." +msgstr "Ingrese un número entero" + +#: fields.py:41 +#, python-format +msgid "%(params)s must be omitted when widget is provided for %(classname)s." +msgstr "%(params)s serán omitidos cuando se proporcione un widget para %(classname)s." + +#: widgets.py:17 +#, python-format +msgid "What is %(num1)i %(operator)s %(num2)i?" +msgstr "¿Cuánto es %(num1)i %(operator)s %(num2)i?" + +#: widgets.py:75 +msgid "MathCaptchaWidget requires positive integers for start_int and end_int." +msgstr "MathCaptchaWidget requiere un número entero positivo para start_int and end_int." + +#: widgets.py:78 +msgid "MathCaptchaWidget requires end_int be greater than start_int." +msgstr "MathCaptchaWidget end_int debe ser mayor que start_int." diff --git a/project/cmsplugin_contact_plus/simplemathcaptcha/utils.py b/project/cmsplugin_contact_plus/simplemathcaptcha/utils.py new file mode 100644 index 0000000..5122010 --- /dev/null +++ b/project/cmsplugin_contact_plus/simplemathcaptcha/utils.py @@ -0,0 +1,45 @@ +from __future__ import absolute_import +from __future__ import unicode_literals + +from random import randint, choice +from hashlib import sha1 + +from django.conf import settings +from django.utils import six + +MULTIPLY = '*' +ADD = '+' +SUBTRACT = '-' +CALCULATIONS = { + MULTIPLY: lambda a, b: a * b, + ADD: lambda a, b: a + b, + SUBTRACT: lambda a, b: a - b, +} +OPERATORS = tuple(CALCULATIONS) + + +def hash_answer(value): + answer = six.text_type(value) + to_encode = (settings.SECRET_KEY + answer).encode('utf-8') + return sha1(to_encode).hexdigest() + + +def get_operator(): + return choice(OPERATORS) + + +def get_numbers(start_int, end_int, operator): + x = randint(start_int, end_int) + y = randint(start_int, end_int) + + #avoid negative results for subtraction + if y > x and operator == SUBTRACT: + x, y = y, x + + return x, y + + +def calculate(x, y, operator): + func = CALCULATIONS[operator] + total = func(x, y) + return total diff --git a/project/cmsplugin_contact_plus/simplemathcaptcha/widgets.py b/project/cmsplugin_contact_plus/simplemathcaptcha/widgets.py new file mode 100644 index 0000000..84a09cc --- /dev/null +++ b/project/cmsplugin_contact_plus/simplemathcaptcha/widgets.py @@ -0,0 +1,80 @@ +from __future__ import absolute_import +from __future__ import unicode_literals + +from django import forms +from django.template.defaultfilters import mark_safe +from django.utils.translation import ugettext_lazy as _ + +from .utils import hash_answer, get_operator, get_numbers, calculate + + +class MathCaptchaWidget(forms.MultiWidget): + def __init__(self, start_int=1, end_int=10, question_tmpl=None, + question_class=None, attrs=None): + self.start_int, self.end_int = self.verify_numbers(start_int, end_int) + self.question_class = question_class or 'captcha-question' + self.question_tmpl = ( + question_tmpl or _('What is %(num1)i %(operator)s %(num2)i? ')) + self.question_html = None + widget_attrs = {'size': '5'} + widget_attrs.update(attrs or {}) + widgets = ( + # this is the answer input field + forms.TextInput(attrs=widget_attrs), + + # this is the hashed answer field to compare to + forms.HiddenInput() + ) + super(MathCaptchaWidget, self).__init__(widgets, attrs) + + def decompress(self, value): + return [None, None] + + def format_output(self, rendered_widgets): + output = super(MathCaptchaWidget, self).format_output(rendered_widgets) + output = '%s%s' % (self.question_html, output) + return output + + def render(self, name, value, attrs=None): + # hash answer and set as the hidden value of form + hashed_answer = self.generate_captcha() + value = ['', hashed_answer] + + return super(MathCaptchaWidget, self).render(name, value, attrs=attrs) + + def generate_captcha(self): + # get operator for calculation + operator = get_operator() + + # get integers for calculation + x, y = get_numbers(self.start_int, self.end_int, operator) + + # set question to display in output + self.set_question(x, y, operator) + + # preform the calculation + total = calculate(x, y, operator) + + return hash_answer(total) + + def set_question(self, x, y, operator): + # make multiplication operator more human-readable + operator_for_label = '×' if operator == '*' else operator + question = self.question_tmpl % { + 'num1': x, + 'operator': operator_for_label, + 'num2': y + } + + html = '%s' % (self.question_class, question) + self.question_html = mark_safe(html) + + def verify_numbers(self, start_int, end_int): + start_int, end_int = int(start_int), int(end_int) + if start_int < 0 or end_int < 0: + raise Warning('MathCaptchaWidget requires positive integers ' + 'for start_int and end_int.') + elif end_int < start_int: + raise Warning('MathCaptchaWidget requires end_int be greater ' + 'than start_int.') + return start_int, end_int diff --git a/project/cmsplugin_contact_plus/south_migrations/__init__.py b/project/cmsplugin_contact_plus/south_migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/project/cmsplugin_contact_plus/templates/cmsplugin_contact_plus/contact.html b/project/cmsplugin_contact_plus/templates/cmsplugin_contact_plus/contact.html new file mode 100755 index 0000000..43ddcbd --- /dev/null +++ b/project/cmsplugin_contact_plus/templates/cmsplugin_contact_plus/contact.html @@ -0,0 +1,16 @@ +{% if form %} + {% if form.is_multipart %} +
    + {% else %} + + {% endif %} + {% csrf_token %} + {{ form.as_p }} + +
    +{% else %} + {{ contact.thanks|safe }} +{% endif %} + + + \ No newline at end of file diff --git a/project/cmsplugin_contact_plus/templates/cmsplugin_contact_plus/email.txt b/project/cmsplugin_contact_plus/templates/cmsplugin_contact_plus/email.txt new file mode 100755 index 0000000..ce48522 --- /dev/null +++ b/project/cmsplugin_contact_plus/templates/cmsplugin_contact_plus/email.txt @@ -0,0 +1,5 @@ +New message from the Web + +{% for dic in ordered_data %}{% for key, value in dic.items %} + {{ key }}: {{ value }} +{% endfor %}{% endfor %} diff --git a/project/cmsplugin_contact_plus/utils.py b/project/cmsplugin_contact_plus/utils.py new file mode 100644 index 0000000..a5d8d44 --- /dev/null +++ b/project/cmsplugin_contact_plus/utils.py @@ -0,0 +1,69 @@ +import glob +import os +import threading + +from django.conf import settings + +localdata = threading.local() +localdata.TEMPLATES = tuple() +TEMPLATES = localdata.TEMPLATES + + +def autodiscover_templates(): + ''' + Autodiscovers cmsplugin_contact_plus templates the way + 'django.template.loaders.filesystem.Loader' and + 'django.template.loaders.app_directories.Loader' work. + ''' + def sorted_templates(templates): + ''' + Sorts templates + ''' + TEMPLATES = sorted(templates, key=lambda template: template[1]) + return TEMPLATES + + # obviously, cache for better performance + global TEMPLATES + if TEMPLATES: + return TEMPLATES + + # override templates from settings + override_dir = getattr(settings, 'CMSPLUGIN_CONTACT_PLUS_TEMPLATES', None) + if override_dir: + return sorted_templates(override_dir) + + templates = [] +# templates = [ +# ('cmsplugin_contact_plus/hello.html', 'hello.html'), +# ] + + dirs_to_scan = [] + if 'django.template.loaders.app_directories.Loader' in settings.TEMPLATE_LOADERS: + for app in settings.INSTALLED_APPS: + _ = __import__(app) + dir = os.path.dirname(_.__file__) + if not dir in dirs_to_scan: + # append 'templates' for app directories + dirs_to_scan.append(os.path.join(dir, 'templates')) + + if 'django.template.loaders.filesystem.Loader' in settings.TEMPLATE_LOADERS: + for dir in settings.TEMPLATE_DIRS: + if not dir in dirs_to_scan: + # filesystem loader assumes our templates in 'templates' + # already + dirs_to_scan.append(dir) + + for dir in dirs_to_scan: + found = glob.glob(os.path.join(dir, 'cmsplugin_contact_plus/*.html')) + for file in found: + dir, file = os.path.split(file) + key, value = os.path.join(dir.split('/')[-1], file), file + f = False + for _, template in templates: + if template == file: + f = True + if not f: + templates.append((key, value,)) + # print os.path.basename(file) + + return sorted_templates(templates) diff --git a/project/framonde/models.py b/project/framonde/models.py index a66b96d..61b89f9 100755 --- a/project/framonde/models.py +++ b/project/framonde/models.py @@ -179,7 +179,7 @@ class Offre(models.Model): texte = models.TextField() image = models.ImageField(null=True, blank=True, upload_to='offres') date_pub = models.DateField('date de publication') - date_limite = models.DateField('date limite') + date_limite = models.DateField('date limite', blank=True, upload_to='offres') date_event = models.DateField('date de l\'événement') date_mod = models.DateTimeField( 'date de derniere modification', diff --git a/project/templates/accueil.html b/project/templates/accueil.html index 2ce82a1..478c798 100644 --- a/project/templates/accueil.html +++ b/project/templates/accueil.html @@ -2,7 +2,7 @@ {% load i18n cms_tags%} {% block Contenu %} -

    {% show_placeholder "Texter" "accueil"%}

    +

    {# show_placeholder "Texter" "accueil"#}

    Derniers appels à communications

    @@ -10,7 +10,7 @@ {% if comm_list %} {% for item in comm_list %}

    {{item.titre}}

    -

    {{item.lieu}}, le {{item.date_event|date:"d F Y"}}

    +

    {{item.lieu}} {{item.date_event}}

    Date Limite: {{item.date_limite|date:"d F Y"}}


    {% endfor %} @@ -26,7 +26,7 @@ {% if contri_list %} {% for item in contri_list %}

    {{item.titre}}

    -

    {{item.lieu}}, {{item.date_event|date:"d F Y"}}

    +

    {{item.lieu}} {{item.date_event}}

    Date Limite: {{item.date_limite|date:"d F Y"}}


    {% endfor %} @@ -42,7 +42,7 @@ {% if offre_list %} {% for item in offre_list %}

    {{item.titre}}

    -

    {{item.lieu}}, {{item.date_event|date:"d F Y"}}

    +

    {{item.lieu}} {{item.date_event}}

    Date Limite: {{item.date_limite|date:"d F Y"}}


    {% endfor %} diff --git a/project/templates/archives.html b/project/templates/archives.html index 72d0e27..99a2bc6 100644 --- a/project/templates/archives.html +++ b/project/templates/archives.html @@ -5,16 +5,17 @@
    Archives Framonde

    {% show_placeholder "Texter" "archives"%}

    - - {% if archives_list %} - - - {% for item in archives_list %} - - {% endfor %} -
    NuméroDateTitreTélécharger
    {{item.numero}}{{item.date_pub|date:"d F Y"}}{{item.titre}}
  • - {% else%} -

    Aucune archives

    - {% endif %} + +{% if archives_list %} +

    Pour les archives antérieures à l'année 2015, merci de consulter cette adresse : https://listes.auf.org/pipermail/liste-framonde/

    + + + {% for item in archives_list %} + + {% endfor %} +
    TitreTélécharger
    {{item.titre}}
  • +{% else%} +

    Aucune archives

    +{% endif %} {% endblock %} diff --git a/project/templates/base.html b/project/templates/base.html index a7732bc..429f18b 100644 --- a/project/templates/base.html +++ b/project/templates/base.html @@ -1,4 +1,4 @@ -{% load cms_tags%} +{% load cms_tags sekizai_tags menu_tags %} @@ -8,6 +8,7 @@ + {% render_block "css" %} @@ -15,6 +16,8 @@ + + {% cms_toolbar %}
    @@ -30,7 +33,7 @@