From f1e4776b191d2794efab95cc84d9ff650cc6b002 Mon Sep 17 00:00:00 2001 From: bohare Date: Fri, 9 Dec 2016 14:15:47 +0000 Subject: [PATCH 1/4] Fix #861 -- Add language dropdown --- cadasta/config/settings/default.py | 1 + cadasta/config/settings/languages.py | 78 ++++ cadasta/core/form_mixins.py | 58 ++- cadasta/core/static/css/forms.scss | 46 +++ cadasta/core/static/css/main.css | 382 ++++++++++-------- cadasta/core/static/css/main.scss | 12 +- cadasta/core/static/css/single.scss | 103 ++--- cadasta/core/static/js/xlang.js | 25 ++ cadasta/core/tests/test_form_mixins.py | 169 +++++++- cadasta/core/tests/test_models.py | 2 - cadasta/core/tests/test_widgets.py | 11 + cadasta/core/widgets.py | 22 + cadasta/organization/tests/test_validators.py | 2 - .../tests/test_views_default_projects.py | 29 +- cadasta/organization/views/mixins.py | 19 + cadasta/party/forms.py | 19 +- cadasta/party/tests/test_forms.py | 37 +- .../questionnaires/tests/test_validators.py | 1 - cadasta/spatial/forms.py | 21 +- .../tests/test_views_api_spatial_units.py | 1 - .../organization/organization_wrapper.html | 29 +- .../organization/project_attrs_import.html | 5 +- .../organization/project_dashboard.html | 1 + .../organization/project_defaults_import.html | 2 +- .../organization/project_edit_details.html | 1 + .../organization/project_edit_geometry.html | 1 + .../project_edit_permissions.html | 1 + .../organization/project_select_import.html | 1 + .../organization/project_wrapper.html | 39 +- cadasta/templates/party/party_attrs.html | 2 +- cadasta/templates/party/party_detail.html | 4 +- cadasta/templates/party/party_form.html | 6 +- .../templates/party/relationship_detail.html | 4 +- .../templates/party/relationship_edit.html | 4 +- cadasta/templates/party/resources_add.html | 1 + cadasta/templates/party/resources_new.html | 3 +- cadasta/templates/resources/edit.html | 1 + .../resources/project_add_existing.html | 1 + .../templates/resources/project_add_new.html | 1 + cadasta/templates/spatial/location_add.html | 1 + .../templates/spatial/location_detail.html | 4 +- cadasta/templates/spatial/location_edit.html | 1 + cadasta/templates/spatial/location_form.html | 5 +- cadasta/templates/spatial/location_map.html | 1 + .../templates/spatial/location_wrapper.html | 1 + .../templates/spatial/relationship_add.html | 8 +- requirements/common.txt | 2 +- 47 files changed, 883 insertions(+), 285 deletions(-) create mode 100644 cadasta/config/settings/languages.py create mode 100644 cadasta/core/static/js/xlang.js create mode 100644 cadasta/core/tests/test_widgets.py create mode 100644 cadasta/core/widgets.py diff --git a/cadasta/config/settings/default.py b/cadasta/config/settings/default.py index ecf236284..e74219e9f 100644 --- a/cadasta/config/settings/default.py +++ b/cadasta/config/settings/default.py @@ -14,6 +14,7 @@ import os from django.utils.translation import ugettext_lazy as _ +from .languages import FORM_LANGS # noqa BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) diff --git a/cadasta/config/settings/languages.py b/cadasta/config/settings/languages.py new file mode 100644 index 000000000..b40ccf292 --- /dev/null +++ b/cadasta/config/settings/languages.py @@ -0,0 +1,78 @@ +from django.utils.translation import ugettext_lazy as _ + + +FORM_LANGS = { + 'af': _('Afrikaans'), + 'ar': _('Arabic'), + 'az': _('Azerbaijani'), + 'be': _('Belarusian'), + 'bg': _('Bulgarian'), + 'bn': _('Bengali'), + 'br': _('Breton'), + 'bs': _('Bosnian'), + 'ca': _('Catalan'), + 'cs': _('Czech'), + 'cy': _('Welsh'), + 'da': _('Danish'), + 'de': _('German'), + 'el': _('Greek'), + 'en': _('English'), + 'eo': _('Esperanto'), + 'es': _('Spanish'), + 'et': _('Estonian'), + 'eu': _('Basque'), + 'fa': _('Persian (Farsi)'), + 'fi': _('Finnish'), + 'fr': _('French'), + 'fy': _('Western Frisian'), + 'ga': _('Irish'), + 'gd': _('Scottish Gaelic'), + 'gl': _('Galician'), + 'he': _('Hebrew'), + 'hi': _('Hindi'), + 'hr': _('Croatian'), + 'hu': _('Hungarian'), + 'ia': _('Interlingua'), + 'id': _('Indonesian'), + 'io': _('Ido'), + 'is': _('Icelandic'), + 'it': _('Italian'), + 'ja': _('Japanese'), + 'ka': _('Georgian'), + 'kk': _('Kazakh'), + 'km': _('Khmer'), + 'kn': _('Kannada'), + 'ko': _('Korean'), + 'lb': _('Luxembourgish'), + 'lt': _('Lithuanian'), + 'lv': _('Latvian'), + 'mk': _('Macedonian'), + 'ml': _('Malayalam'), + 'mn': _('Mongolian'), + 'mr': _('Marathi'), + 'my': _('Burmese'), + 'nb': _('Norwegian (Bokmål)'), + 'ne': _('Nepali'), + 'nl': _('Dutch'), + 'nn': _('Norwegian (Nynorsk)'), + 'os': _('Ossetian'), + 'pa': _('Eastern Punjabi'), + 'pl': _('Polish'), + 'pt': _('Portuguese'), + 'ro': _('Romanian'), + 'ru': _('Russian'), + 'sk': _('Slovak'), + 'sl': _('Slovene'), + 'sq': _('Albanian'), + 'sr': _('Serbian'), + 'sv': _('Swedish'), + 'sw': _('Swahili'), + 'ta': _('Tamil'), + 'te': _('Telugu'), + 'th': _('Thai'), + 'tr': _('Turkish'), + 'tt': _('Tatar'), + 'uk': _('Ukrainian'), + 'ur': _('Urdu'), + 'vi': _('Vietnamese') +} diff --git a/cadasta/core/form_mixins.py b/cadasta/core/form_mixins.py index aafc3ddf2..f5b48476c 100644 --- a/cadasta/core/form_mixins.py +++ b/cadasta/core/form_mixins.py @@ -1,10 +1,12 @@ -from django.forms import Form, ModelForm - +from django.forms import Form, ModelForm, MultipleChoiceField +from jsonattrs.mixins import template_xlang_labels from jsonattrs.forms import form_field_from_name from django.contrib.contenttypes.models import ContentType from tutelary.models import Role +from questionnaires.models import Questionnaire, Question, QuestionOption from .mixins import SchemaSelectorMixin +from .widgets import XLangSelect, XLangSelectMultiple class SuperUserCheck: @@ -22,12 +24,39 @@ def is_superuser(self, user): class AttributeFormMixin(SchemaSelectorMixin): + def set_standard_field(self, name, empty_choice=None, field_name=None): + if not field_name: + field_name = name + q = Questionnaire.objects.get(id=self.project.current_questionnaire) + default_lang = q.default_language + try: + question = Question.objects.get(name=name, questionnaire=q) + self.fields[field_name].labels_xlang = template_xlang_labels( + question.label_xlat) + + if question.has_options: + choices = QuestionOption.objects.filter( + question=question).values_list('name', 'label_xlat') + choices, xlang_labels = zip(*[((c[0], c[1].get(default_lang)), + (c[0], c[1])) for c in choices]) + + choices = ([('', empty_choice)] + list(choices) + if empty_choice else list(choices)) + self.fields[field_name].widget = XLangSelect( + attrs=self.fields[field_name].widget.attrs, + choices=choices, + xlang_labels=dict(xlang_labels) + ) + except Question.DoesNotExist: + pass + def create_model_fields(self, field_prefix, attribute_map, new_item=False): for selector, attributes in attribute_map.items(): for name, attr in attributes.items(): fieldname = '{}::{}::{}'.format( field_prefix, selector.lower(), name) atype = attr.attr_type + field_kwargs = { 'label': attr.long_name, 'required': attr.required } @@ -40,6 +69,7 @@ def create_model_fields(self, field_prefix, attribute_map, new_item=False): chs = list(zip(attr.choices, attr.choice_labels)) else: chs = [(c, c) for c in attr.choices] + field_kwargs['choices'] = chs if atype.form_field == 'BooleanField': field_kwargs['required'] = attr.required @@ -51,7 +81,29 @@ def create_model_fields(self, field_prefix, attribute_map, new_item=False): if len(attr.default) > 0 and len(str( field_kwargs.get('initial', ''))) == 0: self.set_default(field_kwargs, attr) - self.fields[fieldname] = field(**field_kwargs) + + f = field(**field_kwargs) + + if hasattr(f.widget, 'choices'): + try: + xlang_labels = dict(zip(attr.choices, + attr.choice_labels_xlat)) + except TypeError: + xlang_labels = {} + + widget_args = { + 'attrs': f.widget.attrs, + 'choices': f.widget.choices, + 'xlang_labels': xlang_labels + } + + if isinstance(f, MultipleChoiceField): + f.widget = XLangSelectMultiple(**widget_args) + else: + f.widget = XLangSelect(**widget_args) + + f.labels_xlang = template_xlang_labels(attr.long_name_xlat) + self.fields[fieldname] = f def set_default(self, field_kwargs, attr, boolean=False): if len(attr.default) > 0: diff --git a/cadasta/core/static/css/forms.scss b/cadasta/core/static/css/forms.scss index b099c5943..7564f4b78 100644 --- a/cadasta/core/static/css/forms.scss +++ b/cadasta/core/static/css/forms.scss @@ -48,6 +48,52 @@ tr.contacts-error { } } +/* =Project translation select dropdown +-------------------------------------------------------------- */ + +.langs-select { + overflow: hidden; + height: 34px; + position: relative; + margin-right: 30px; + margin-bottom: 0; + select { + color: #fff; + padding: 4px; + padding-right: 25px; + background-color: transparent; + border: 0; + border-radius: 0; + outline: none; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + box-shadow: unset; + -webkit-box-shadow: unset; + > option { + background: #fff; + padding: 6px; + color: $gray-dark; + } + } + &:after { + font-family: 'Glyphicons Halflings'; + font-size: 9px; + content:"\e252"; + color: #fff; + padding: 12px 8px; + position: absolute; + right: 10px; + top: 0; + z-index: 1; + text-align: center; + width: 10%; + height: 100%; + pointer-events: none; + box-sizing: border-box; + } +} + /* =Required field label -------------------------------------------------------------- */ diff --git a/cadasta/core/static/css/main.css b/cadasta/core/static/css/main.css index 45fea9566..24d7306ba 100644 --- a/cadasta/core/static/css/main.css +++ b/cadasta/core/static/css/main.css @@ -4880,7 +4880,7 @@ ul.resource-actions > li { text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.2); max-width: 80%; } .page-title h1.short, .page-title h2.short { - width: 60%; } + width: 50%; } .page-title h1 .org-name, .page-title h2 .org-name { font-size: 14px; display: block; @@ -4889,11 +4889,16 @@ ul.resource-actions > li { .page-title h1 a, .page-title h2 a { color: #a3b7e5; } +@media (min-width: 992px) { + .page-header .page-title h1, .page-header .page-title h2 { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 500px; } } + @media (max-width: 991px) { .page-header { - padding-top: 24px; } } - -@media (max-width: 767px) { + padding-top: 24px; } .page-title h1, .page-title h2, h1, h2 { font-size: 1.8em; } .page-title h2, h2 { @@ -4901,22 +4906,26 @@ ul.resource-actions > li { h3 { font-size: 1.4em; } } -@media (min-width: 992px) { - .page-header .page-title h1, .page-header .page-title h2 { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; } } +@media (max-width: 767px) { + .page-title h1, .page-title h2, h1, h2 { + font-size: 1.4em; } + .page-title h2, h2 { + font-size: 1.2em; } + h3 { + font-size: 1.0em; } } /* =Page header single project or organizations -------------------------------------------------------------- */ #project-single .page-header, #organization-single .page-header { + display: table; background: #0e305e; height: 110px; min-height: 110px; border-bottom: solid 1px #fff; margin: 0; - padding-top: 18px; + padding-top: 0; + padding-bottom: 0; position: fixed; top: 70px; z-index: 999; } @@ -4927,164 +4936,161 @@ ul.resource-actions > li { #organization-single .page-header div > a:hover, #organization-single .page-header div > a:focus { color: #fff; } - #project-single .page-header .index-link, - #organization-single .page-header .index-link { - padding: 4px 10px; - margin-right: 10px; - border: 1px solid #a3b7e5; - border-radius: 8px; - border-top-left-radius: 0; - border-bottom-left-radius: 0; - border-left: 0; - position: absolute; - top: 55px; - display: inline-block; } - #project-single .page-header .index-link:hover, #project-single .page-header .index-link:focus, - #organization-single .page-header .index-link:hover, - #organization-single .page-header .index-link:focus { - color: #fff; - background-color: #345bb7; - border-color: #fff; - transition: all 0.3s ease 0s; } - #project-single .page-header .index-link:hover:focus, #project-single .page-header .index-link:hover.focus, #project-single .page-header .index-link:focus:focus, #project-single .page-header .index-link:focus.focus, - #organization-single .page-header .index-link:hover:focus, - #organization-single .page-header .index-link:hover.focus, - #organization-single .page-header .index-link:focus:focus, - #organization-single .page-header .index-link:focus.focus { - color: #fff; - background-color: #28478f; - border-color: #bfbfbf; } - #project-single .page-header .index-link:hover:hover, #project-single .page-header .index-link:focus:hover, - #organization-single .page-header .index-link:hover:hover, - #organization-single .page-header .index-link:focus:hover { - color: #fff; - background-color: #28478f; - border-color: #e0e0e0; } - #project-single .page-header .index-link:hover:active, #project-single .page-header .index-link:hover.active, - .open > #project-single .page-header .index-link:hover.dropdown-toggle, #project-single .page-header .index-link:focus:active, #project-single .page-header .index-link:focus.active, - .open > #project-single .page-header .index-link:focus.dropdown-toggle, - #organization-single .page-header .index-link:hover:active, - #organization-single .page-header .index-link:hover.active, - .open > - #organization-single .page-header .index-link:hover.dropdown-toggle, - #organization-single .page-header .index-link:focus:active, - #organization-single .page-header .index-link:focus.active, - .open > - #organization-single .page-header .index-link:focus.dropdown-toggle { + #project-single .page-header .page-title, + #organization-single .page-header .page-title { + display: table-row; + padding-top: 14px; + padding-bottom: 8px; } + #project-single .page-header .page-title > div, + #organization-single .page-header .page-title > div { + display: table-cell; + vertical-align: middle; } + #project-single .page-header .page-title .index-link, + #organization-single .page-header .page-title .index-link { + padding: 4px 10px; + margin-right: 10px; + border: 1px solid #a3b7e5; + border-radius: 8px; + border-top-left-radius: 0; + border-bottom-left-radius: 0; + border-left: 0; } + #project-single .page-header .page-title .index-link:hover, #project-single .page-header .page-title .index-link:focus, + #organization-single .page-header .page-title .index-link:hover, + #organization-single .page-header .page-title .index-link:focus { color: #fff; - background-color: #28478f; - border-color: #e0e0e0; } - #project-single .page-header .index-link:hover:active:hover, #project-single .page-header .index-link:hover:active:focus, #project-single .page-header .index-link:hover:active.focus, #project-single .page-header .index-link:hover.active:hover, #project-single .page-header .index-link:hover.active:focus, #project-single .page-header .index-link:hover.active.focus, - .open > #project-single .page-header .index-link:hover.dropdown-toggle:hover, - .open > #project-single .page-header .index-link:hover.dropdown-toggle:focus, - .open > #project-single .page-header .index-link:hover.dropdown-toggle.focus, #project-single .page-header .index-link:focus:active:hover, #project-single .page-header .index-link:focus:active:focus, #project-single .page-header .index-link:focus:active.focus, #project-single .page-header .index-link:focus.active:hover, #project-single .page-header .index-link:focus.active:focus, #project-single .page-header .index-link:focus.active.focus, - .open > #project-single .page-header .index-link:focus.dropdown-toggle:hover, - .open > #project-single .page-header .index-link:focus.dropdown-toggle:focus, - .open > #project-single .page-header .index-link:focus.dropdown-toggle.focus, - #organization-single .page-header .index-link:hover:active:hover, - #organization-single .page-header .index-link:hover:active:focus, - #organization-single .page-header .index-link:hover:active.focus, - #organization-single .page-header .index-link:hover.active:hover, - #organization-single .page-header .index-link:hover.active:focus, - #organization-single .page-header .index-link:hover.active.focus, - .open > - #organization-single .page-header .index-link:hover.dropdown-toggle:hover, - .open > - #organization-single .page-header .index-link:hover.dropdown-toggle:focus, + background-color: #345bb7; + border-color: #fff; + transition: all 0.3s ease 0s; } + #project-single .page-header .page-title .index-link:hover:focus, #project-single .page-header .page-title .index-link:hover.focus, #project-single .page-header .page-title .index-link:focus:focus, #project-single .page-header .page-title .index-link:focus.focus, + #organization-single .page-header .page-title .index-link:hover:focus, + #organization-single .page-header .page-title .index-link:hover.focus, + #organization-single .page-header .page-title .index-link:focus:focus, + #organization-single .page-header .page-title .index-link:focus.focus { + color: #fff; + background-color: #28478f; + border-color: #bfbfbf; } + #project-single .page-header .page-title .index-link:hover:hover, #project-single .page-header .page-title .index-link:focus:hover, + #organization-single .page-header .page-title .index-link:hover:hover, + #organization-single .page-header .page-title .index-link:focus:hover { + color: #fff; + background-color: #28478f; + border-color: #e0e0e0; } + #project-single .page-header .page-title .index-link:hover:active, #project-single .page-header .page-title .index-link:hover.active, + .open > #project-single .page-header .page-title .index-link:hover.dropdown-toggle, #project-single .page-header .page-title .index-link:focus:active, #project-single .page-header .page-title .index-link:focus.active, + .open > #project-single .page-header .page-title .index-link:focus.dropdown-toggle, + #organization-single .page-header .page-title .index-link:hover:active, + #organization-single .page-header .page-title .index-link:hover.active, .open > - #organization-single .page-header .index-link:hover.dropdown-toggle.focus, - #organization-single .page-header .index-link:focus:active:hover, - #organization-single .page-header .index-link:focus:active:focus, - #organization-single .page-header .index-link:focus:active.focus, - #organization-single .page-header .index-link:focus.active:hover, - #organization-single .page-header .index-link:focus.active:focus, - #organization-single .page-header .index-link:focus.active.focus, + #organization-single .page-header .page-title .index-link:hover.dropdown-toggle, + #organization-single .page-header .page-title .index-link:focus:active, + #organization-single .page-header .page-title .index-link:focus.active, .open > - #organization-single .page-header .index-link:focus.dropdown-toggle:hover, + #organization-single .page-header .page-title .index-link:focus.dropdown-toggle { + color: #fff; + background-color: #28478f; + border-color: #e0e0e0; } + #project-single .page-header .page-title .index-link:hover:active:hover, #project-single .page-header .page-title .index-link:hover:active:focus, #project-single .page-header .page-title .index-link:hover:active.focus, #project-single .page-header .page-title .index-link:hover.active:hover, #project-single .page-header .page-title .index-link:hover.active:focus, #project-single .page-header .page-title .index-link:hover.active.focus, + .open > #project-single .page-header .page-title .index-link:hover.dropdown-toggle:hover, + .open > #project-single .page-header .page-title .index-link:hover.dropdown-toggle:focus, + .open > #project-single .page-header .page-title .index-link:hover.dropdown-toggle.focus, #project-single .page-header .page-title .index-link:focus:active:hover, #project-single .page-header .page-title .index-link:focus:active:focus, #project-single .page-header .page-title .index-link:focus:active.focus, #project-single .page-header .page-title .index-link:focus.active:hover, #project-single .page-header .page-title .index-link:focus.active:focus, #project-single .page-header .page-title .index-link:focus.active.focus, + .open > #project-single .page-header .page-title .index-link:focus.dropdown-toggle:hover, + .open > #project-single .page-header .page-title .index-link:focus.dropdown-toggle:focus, + .open > #project-single .page-header .page-title .index-link:focus.dropdown-toggle.focus, + #organization-single .page-header .page-title .index-link:hover:active:hover, + #organization-single .page-header .page-title .index-link:hover:active:focus, + #organization-single .page-header .page-title .index-link:hover:active.focus, + #organization-single .page-header .page-title .index-link:hover.active:hover, + #organization-single .page-header .page-title .index-link:hover.active:focus, + #organization-single .page-header .page-title .index-link:hover.active.focus, + .open > + #organization-single .page-header .page-title .index-link:hover.dropdown-toggle:hover, + .open > + #organization-single .page-header .page-title .index-link:hover.dropdown-toggle:focus, + .open > + #organization-single .page-header .page-title .index-link:hover.dropdown-toggle.focus, + #organization-single .page-header .page-title .index-link:focus:active:hover, + #organization-single .page-header .page-title .index-link:focus:active:focus, + #organization-single .page-header .page-title .index-link:focus:active.focus, + #organization-single .page-header .page-title .index-link:focus.active:hover, + #organization-single .page-header .page-title .index-link:focus.active:focus, + #organization-single .page-header .page-title .index-link:focus.active.focus, + .open > + #organization-single .page-header .page-title .index-link:focus.dropdown-toggle:hover, + .open > + #organization-single .page-header .page-title .index-link:focus.dropdown-toggle:focus, + .open > + #organization-single .page-header .page-title .index-link:focus.dropdown-toggle.focus { + color: #fff; + background-color: #213973; + border-color: #bfbfbf; } + #project-single .page-header .page-title .index-link:hover:active, #project-single .page-header .page-title .index-link:hover.active, + .open > #project-single .page-header .page-title .index-link:hover.dropdown-toggle, #project-single .page-header .page-title .index-link:focus:active, #project-single .page-header .page-title .index-link:focus.active, + .open > #project-single .page-header .page-title .index-link:focus.dropdown-toggle, + #organization-single .page-header .page-title .index-link:hover:active, + #organization-single .page-header .page-title .index-link:hover.active, .open > - #organization-single .page-header .index-link:focus.dropdown-toggle:focus, + #organization-single .page-header .page-title .index-link:hover.dropdown-toggle, + #organization-single .page-header .page-title .index-link:focus:active, + #organization-single .page-header .page-title .index-link:focus.active, .open > - #organization-single .page-header .index-link:focus.dropdown-toggle.focus { - color: #fff; - background-color: #213973; - border-color: #bfbfbf; } - #project-single .page-header .index-link:hover:active, #project-single .page-header .index-link:hover.active, - .open > #project-single .page-header .index-link:hover.dropdown-toggle, #project-single .page-header .index-link:focus:active, #project-single .page-header .index-link:focus.active, - .open > #project-single .page-header .index-link:focus.dropdown-toggle, - #organization-single .page-header .index-link:hover:active, - #organization-single .page-header .index-link:hover.active, - .open > - #organization-single .page-header .index-link:hover.dropdown-toggle, - #organization-single .page-header .index-link:focus:active, - #organization-single .page-header .index-link:focus.active, - .open > - #organization-single .page-header .index-link:focus.dropdown-toggle { - background-image: none; } - #project-single .page-header .index-link:hover.disabled:hover, #project-single .page-header .index-link:hover.disabled:focus, #project-single .page-header .index-link:hover.disabled.focus, #project-single .page-header .index-link:hover[disabled]:hover, #project-single .page-header .index-link:hover[disabled]:focus, #project-single .page-header .index-link:hover[disabled].focus, - fieldset[disabled] #project-single .page-header .index-link:hover:hover, - fieldset[disabled] #project-single .page-header .index-link:hover:focus, - fieldset[disabled] #project-single .page-header .index-link:hover.focus, #project-single .page-header .index-link:focus.disabled:hover, #project-single .page-header .index-link:focus.disabled:focus, #project-single .page-header .index-link:focus.disabled.focus, #project-single .page-header .index-link:focus[disabled]:hover, #project-single .page-header .index-link:focus[disabled]:focus, #project-single .page-header .index-link:focus[disabled].focus, - fieldset[disabled] #project-single .page-header .index-link:focus:hover, - fieldset[disabled] #project-single .page-header .index-link:focus:focus, - fieldset[disabled] #project-single .page-header .index-link:focus.focus, - #organization-single .page-header .index-link:hover.disabled:hover, - #organization-single .page-header .index-link:hover.disabled:focus, - #organization-single .page-header .index-link:hover.disabled.focus, - #organization-single .page-header .index-link:hover[disabled]:hover, - #organization-single .page-header .index-link:hover[disabled]:focus, - #organization-single .page-header .index-link:hover[disabled].focus, - fieldset[disabled] - #organization-single .page-header .index-link:hover:hover, - fieldset[disabled] - #organization-single .page-header .index-link:hover:focus, - fieldset[disabled] - #organization-single .page-header .index-link:hover.focus, - #organization-single .page-header .index-link:focus.disabled:hover, - #organization-single .page-header .index-link:focus.disabled:focus, - #organization-single .page-header .index-link:focus.disabled.focus, - #organization-single .page-header .index-link:focus[disabled]:hover, - #organization-single .page-header .index-link:focus[disabled]:focus, - #organization-single .page-header .index-link:focus[disabled].focus, - fieldset[disabled] - #organization-single .page-header .index-link:focus:hover, - fieldset[disabled] - #organization-single .page-header .index-link:focus:focus, - fieldset[disabled] - #organization-single .page-header .index-link:focus.focus { - background-color: #345bb7; - border-color: #fff; } - #project-single .page-header .index-link:hover .badge, #project-single .page-header .index-link:focus .badge, - #organization-single .page-header .index-link:hover .badge, - #organization-single .page-header .index-link:focus .badge { - color: #345bb7; - background-color: #fff; } - #project-single .page-header .page-title, - #organization-single .page-header .page-title { - padding-top: 14px; - padding-bottom: 8px; } - #project-single .page-header h1, #project-single .page-header h2, - #organization-single .page-header h1, - #organization-single .page-header h2 { - margin-left: 54px; - padding-bottom: 8px; } - #project-single .page-header .top-btn, - #organization-single .page-header .top-btn { - margin-top: 18px; } - #project-single .page-header .btn-add, - #organization-single .page-header .btn-add { - margin-right: 14px; } - -#organization-single .page-header { - padding-top: 35px; } - #organization-single .page-header .top-btn { - margin-top: 1px; } + #organization-single .page-header .page-title .index-link:focus.dropdown-toggle { + background-image: none; } + #project-single .page-header .page-title .index-link:hover.disabled:hover, #project-single .page-header .page-title .index-link:hover.disabled:focus, #project-single .page-header .page-title .index-link:hover.disabled.focus, #project-single .page-header .page-title .index-link:hover[disabled]:hover, #project-single .page-header .page-title .index-link:hover[disabled]:focus, #project-single .page-header .page-title .index-link:hover[disabled].focus, + fieldset[disabled] #project-single .page-header .page-title .index-link:hover:hover, + fieldset[disabled] #project-single .page-header .page-title .index-link:hover:focus, + fieldset[disabled] #project-single .page-header .page-title .index-link:hover.focus, #project-single .page-header .page-title .index-link:focus.disabled:hover, #project-single .page-header .page-title .index-link:focus.disabled:focus, #project-single .page-header .page-title .index-link:focus.disabled.focus, #project-single .page-header .page-title .index-link:focus[disabled]:hover, #project-single .page-header .page-title .index-link:focus[disabled]:focus, #project-single .page-header .page-title .index-link:focus[disabled].focus, + fieldset[disabled] #project-single .page-header .page-title .index-link:focus:hover, + fieldset[disabled] #project-single .page-header .page-title .index-link:focus:focus, + fieldset[disabled] #project-single .page-header .page-title .index-link:focus.focus, + #organization-single .page-header .page-title .index-link:hover.disabled:hover, + #organization-single .page-header .page-title .index-link:hover.disabled:focus, + #organization-single .page-header .page-title .index-link:hover.disabled.focus, + #organization-single .page-header .page-title .index-link:hover[disabled]:hover, + #organization-single .page-header .page-title .index-link:hover[disabled]:focus, + #organization-single .page-header .page-title .index-link:hover[disabled].focus, + fieldset[disabled] + #organization-single .page-header .page-title .index-link:hover:hover, + fieldset[disabled] + #organization-single .page-header .page-title .index-link:hover:focus, + fieldset[disabled] + #organization-single .page-header .page-title .index-link:hover.focus, + #organization-single .page-header .page-title .index-link:focus.disabled:hover, + #organization-single .page-header .page-title .index-link:focus.disabled:focus, + #organization-single .page-header .page-title .index-link:focus.disabled.focus, + #organization-single .page-header .page-title .index-link:focus[disabled]:hover, + #organization-single .page-header .page-title .index-link:focus[disabled]:focus, + #organization-single .page-header .page-title .index-link:focus[disabled].focus, + fieldset[disabled] + #organization-single .page-header .page-title .index-link:focus:hover, + fieldset[disabled] + #organization-single .page-header .page-title .index-link:focus:focus, + fieldset[disabled] + #organization-single .page-header .page-title .index-link:focus.focus { + background-color: #345bb7; + border-color: #fff; } + #project-single .page-header .page-title .index-link:hover .badge, #project-single .page-header .page-title .index-link:focus .badge, + #organization-single .page-header .page-title .index-link:hover .badge, + #organization-single .page-header .page-title .index-link:focus .badge { + color: #345bb7; + background-color: #fff; } + #project-single .page-header .page-title h1, #project-single .page-header .page-title h2, + #organization-single .page-header .page-title h1, + #organization-single .page-header .page-title h2 { + display: table-cell; + vertical-align: middle; } + #project-single .page-header .page-title .top-btn, + #organization-single .page-header .page-title .top-btn { + vertical-align: middle; } + #project-single .page-header .page-title .top-btn .btn-add, + #organization-single .page-header .page-title .top-btn .btn-add { + margin-right: 14px; } @media (max-width: 991px) { #project-single .page-header, #organization-single .page-header { overflow: visible; padding-top: 8px; - padding-bottom: 0; + padding-bottom: 8px; border-bottom: solid 1px #0b2548; margin-top: 0; margin-bottom: 0; @@ -5097,7 +5103,7 @@ ul.resource-actions > li { #project-single .page-header h1, #project-single .page-header h2, #organization-single .page-header h1, #organization-single .page-header h2 { - margin-left: 14px; } + padding-left: 14px; } #project-single .page-header .page-title, #organization-single .page-header .page-title { padding-bottom: 0; } } @@ -5382,6 +5388,9 @@ ul.resource-actions > li { #search-results.table h4 { margin-bottom: 12px; } +#search-results.table img.thumb-60 { + margin-left: 24px; } + #search-results.table img.thumb-60 ~ table { width: calc(100% - 100px); } @@ -5481,6 +5490,47 @@ tr.contacts-error { font-size: 0.85em; opacity: 0.75; } +/* =Project translation select dropdown +-------------------------------------------------------------- */ +.langs-select { + overflow: hidden; + height: 34px; + position: relative; + margin-right: 30px; + margin-bottom: 0; } + .langs-select select { + color: #fff; + padding: 4px; + padding-right: 25px; + background-color: transparent; + border: 0; + border-radius: 0; + outline: none; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + box-shadow: unset; + -webkit-box-shadow: unset; } + .langs-select select > option { + background: #fff; + padding: 6px; + color: #595a5c; } + .langs-select:after { + font-family: 'Glyphicons Halflings'; + font-size: 9px; + content: "\e252"; + color: #fff; + padding: 12px 8px; + position: absolute; + right: 10px; + top: 0; + z-index: 1; + text-align: center; + width: 10%; + height: 100%; + pointer-events: none; + box-sizing: border-box; } + /* =Required field label -------------------------------------------------------------- */ label.required:after { @@ -6301,7 +6351,7 @@ img.org-logo, img#org-logo { margin-top: 10px; margin-bottom: 10px; } .top-btn .btn { - min-width: 140px; } + min-width: 100px; } .top-btn .btn-rt, .top-btn .btn-action { min-width: 34px; } @@ -6342,14 +6392,24 @@ div.add-btn-btm { @media (max-width: 991px) { .top-btn .btn { - min-width: auto; } + min-width: auto; + font-size: 13px; + color: black; } #project-single .page-header .top-btn, #organization-single .page-header .top-btn { padding: 4px 10px 0; margin-top: 0; } #project-single .page-header .top-btn .btn, #organization-single .page-header .top-btn .btn { - min-width: auto; } } + min-width: auto; } + #project-single .page-header .top-btn .langs-select, + #organization-single .page-header .top-btn .langs-select { + margin-right: 0; + width: 50px; } + #project-single .page-header .top-btn .langs-select select, + #organization-single .page-header .top-btn .langs-select select { + font-size: 13px; + padding: 2px; } } @media (max-width: 767px) { .top-btn .btn { diff --git a/cadasta/core/static/css/main.scss b/cadasta/core/static/css/main.scss index eb6cf59f6..6aa873cd0 100644 --- a/cadasta/core/static/css/main.scss +++ b/cadasta/core/static/css/main.scss @@ -775,7 +775,7 @@ img.org-logo, img#org-logo { margin-top: 10px; margin-bottom: 10px; .btn { - min-width: 140px; + min-width: 100px; } .btn-rt, .btn-action { min-width: 34px; @@ -832,6 +832,8 @@ div.add-btn-btm { // add party link at bottom of table .top-btn { .btn { min-width: auto; + font-size: 13px; + color: black; } } #project-single .page-header, @@ -842,6 +844,14 @@ div.add-btn-btm { // add party link at bottom of table .btn { min-width: auto; } + .langs-select { + margin-right: 0; + width: 50px; + select { + font-size: 13px; + padding: 2px; + } + } } } } diff --git a/cadasta/core/static/css/single.scss b/cadasta/core/static/css/single.scss index f8708833e..88ae67afe 100644 --- a/cadasta/core/static/css/single.scss +++ b/cadasta/core/static/css/single.scss @@ -36,7 +36,7 @@ text-shadow: 1px 1px 3px rgba(0,0,0,.2); max-width: 80%; &.short { // provides room for admin buttons on right - width: 60%; + width: 50%; } .org-name { font-size: 14px; @@ -50,13 +50,21 @@ } } +@media (min-width: $screen-md-min) { + .page-header { // navy band on index pages + .page-title h1, .page-title h2 { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 500px; + } + } +} + @media (max-width: $screen-sm-max) { .page-header { padding-top: 24px; } -} - -@media (max-width: $screen-xs-max) { .page-title h1, .page-title h2, h1, h2 { font-size: 1.8em; } @@ -68,13 +76,15 @@ } } -@media (min-width: $screen-md-min) { - .page-header { // navy band on index pages - .page-title h1, .page-title h2 { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } +@media (max-width: $screen-xs-max) { + .page-title h1, .page-title h2, h1, h2 { + font-size: 1.4em; + } + .page-title h2, h2 { + font-size: 1.2em; + } + h3 { + font-size: 1.0em; } } @@ -83,12 +93,14 @@ #project-single .page-header, #organization-single .page-header { // navy band on pages for single project or organization + display: table; background: $brand-darkblue; height: 110px; min-height: 110px; border-bottom: solid 1px #fff; margin: 0; - padding-top: 18px; + padding-top: 0; + padding-bottom: 0; position: fixed; top: $header-height; z-index: 999; @@ -99,43 +111,38 @@ color: #fff; } } - .index-link { - padding: 4px 10px; - margin-right: 10px; - border: 1px solid $brand-lightblue; - border-radius: 8px; - border-top-left-radius: 0; - border-bottom-left-radius: 0; - border-left: 0; - position: absolute; - top: 55px; - display: inline-block; - &:hover, - &:focus { - @include button-variant(#fff, lighten($btn-blue-bg, 5%), #fff); - transition: all 0.3s ease 0s; - } - } .page-title { + display: table-row; padding-top: 14px; padding-bottom: 8px; - } - h1, h2 { - margin-left: 54px; - padding-bottom: 8px; - } - .top-btn { - margin-top: 18px; - } - .btn-add { - margin-right: 14px; - } -} - -#organization-single .page-header { - padding-top: 35px; // offset to match project - .top-btn { - margin-top: 1px; + > div { + display: table-cell; + vertical-align: middle; + } + .index-link { + padding: 4px 10px; + margin-right: 10px; + border: 1px solid $brand-lightblue; + border-radius: 8px; + border-top-left-radius: 0; + border-bottom-left-radius: 0; + border-left: 0; + &:hover, + &:focus { + @include button-variant(#fff, lighten($btn-blue-bg, 5%), #fff); + transition: all 0.3s ease 0s; + } + } + h1, h2 { + display: table-cell; + vertical-align: middle; + } + .top-btn { + vertical-align: middle; + .btn-add { + margin-right: 14px; + } + } } } @@ -144,7 +151,7 @@ #organization-single .page-header { overflow: visible; padding-top: 8px; - padding-bottom: 0; + padding-bottom: 8px; border-bottom: solid 1px $brand-darkerblue; margin-top: 0; margin-bottom: 0; @@ -155,7 +162,7 @@ height: 36px; } h1, h2 { - margin-left: 14px; + padding-left: 14px; } } #project-single .page-header .page-title, diff --git a/cadasta/core/static/js/xlang.js b/cadasta/core/static/js/xlang.js new file mode 100644 index 000000000..fe3dcaac8 --- /dev/null +++ b/cadasta/core/static/js/xlang.js @@ -0,0 +1,25 @@ +$(document).ready(function () { + function setLabels(lang) { + const labels = $('label, option, td'); + + for (var i = 0, l = labels.length; i < l; i++) { + const label = $(labels[i]); + const new_text = label.attr('data-label-' + lang) + if (new_text) { + label.text(new_text); + } + } + } + + $('#form-langs-select').change(function() { + const new_lang = $(this).val(); + sessionStorage.setItem("form_lang", new_lang); + setLabels(new_lang); + }); + + const form_lang = sessionStorage.getItem("form_lang"); + if (form_lang) { + $('#form-langs-select').val(form_lang); + setLabels(form_lang); + } +}); diff --git a/cadasta/core/tests/test_form_mixins.py b/cadasta/core/tests/test_form_mixins.py index 3f60b9bdd..98b5eeb57 100644 --- a/cadasta/core/tests/test_form_mixins.py +++ b/cadasta/core/tests/test_form_mixins.py @@ -1,14 +1,17 @@ from core.tests.utils.cases import FileStorageTestCase, UserTestCase from django.contrib.contenttypes.models import ContentType from django.test import TestCase +from django.forms import CharField, ChoiceField from jsonattrs.models import (Attribute, AttributeType, Schema, create_attribute_type) from organization.tests.factories import ProjectFactory from party.models import Party from party.tests.factories import PartyFactory +from questionnaires.tests import factories as q_factories from ..form_mixins import AttributeForm, AttributeFormMixin, AttributeModelForm from ..mixins import SchemaSelectorMixin +from ..widgets import XLangSelect class MockAttributeForm(AttributeForm): @@ -44,24 +47,29 @@ def setUp(self): app_label='party', model='party') schema = Schema.objects.create( content_type=content_type, - selectors=(self.project.organization.id, self.project.id, 'abc1')) + selectors=(self.project.organization.id, self.project.id, 'abc1'), + default_language='en') + create_attribute_type('text', 'Text', 'CharField', validator_type='str') create_attribute_type('boolean', 'boolean', 'BooleanField', validator_type='bool') create_attribute_type('integer', 'Integer', 'IntegerField', validator_re=r'[-+]?\d+') + create_attribute_type('select_one', 'Select one:', 'ChoiceField') + create_attribute_type('select_multiple', 'Select multiple:', + 'MultipleChoiceField') Attribute.objects.create( schema=schema, name='fname', - long_name='Test field', + long_name={'en': 'Test field', 'de': 'Test feld'}, attr_type=AttributeType.objects.get(name='text'), index=0 ) Attribute.objects.create( schema=schema, name='homeowner', - long_name='Homeowner', + long_name={'en': 'Homeowner', 'de': 'Besitzer'}, choices=['yes', 'no'], default='yes', required=True, attr_type=AttributeType.objects.get(name='boolean'), @@ -70,11 +78,33 @@ def setUp(self): Attribute.objects.create( schema=schema, name='number_of_something', - long_name='Number of something', + long_name={'en': 'Number of something', 'de': 'Eine Nummer'}, default='0', required=False, attr_type=AttributeType.objects.get(name='integer'), index=2 ) + Attribute.objects.create( + schema=schema, + name='bird_is_the_word', + long_name={'en': 'Have you heard?', 'de': 'Schon gehoert?'}, + choices=['yes', 'no'], + choice_labels=[{'en': 'yes', 'de': 'ja'}, + {'en': 'no', 'de': 'nein'}], + default='yes', required=True, + attr_type=AttributeType.objects.get(name='select_one'), + index=3 + ) + Attribute.objects.create( + schema=schema, + name='surfing_bird', + long_name={'en': 'Have you heard?', 'de': 'Schon gehoert?'}, + choices=['yes', 'no'], + choice_labels=[{'en': 'yes', 'de': 'ja'}, + {'en': 'no', 'de': 'nein'}], + default='yes', required=True, + attr_type=AttributeType.objects.get(name='select_multiple'), + index=4 + ) def test_create_model_fields(self): form_mixin = AttributeFormMixin() @@ -83,11 +113,25 @@ def test_create_model_fields(self): attributes = schema_mixin.get_model_attributes( self.project, 'party.party') form_mixin.create_model_fields('party', attributes, new_item=False) - assert len(form_mixin.fields) == 9 + assert len(form_mixin.fields) == 15 homeowner = form_mixin.fields['party::in::homeowner'] assert homeowner.initial assert homeowner.required + bird = form_mixin.fields['party::in::bird_is_the_word'] + assert isinstance(bird.widget, XLangSelect) + assert bird.widget.xlang_labels == { + 'yes': {'en': 'yes', 'de': 'ja'}, + 'no': {'en': 'no', 'de': 'nein'} + } + + surfing_bird = form_mixin.fields['party::in::surfing_bird'] + assert isinstance(surfing_bird.widget, XLangSelect) + assert surfing_bird.widget.xlang_labels == { + 'yes': {'en': 'yes', 'de': 'ja'}, + 'no': {'en': 'no', 'de': 'nein'} + } + fname = form_mixin.fields['party::in::fname'] assert not fname.required assert fname.initial is None @@ -103,7 +147,8 @@ def test_create_model_fields_with_new_item(self): attributes = schema_mixin.get_model_attributes( self.project, 'party.party') form_mixin.create_model_fields('party', attributes, new_item=True) - assert len(form_mixin.fields) == 9 + + assert len(form_mixin.fields) == 15 homeowner = form_mixin.fields['party::in::homeowner'] assert homeowner.initial @@ -140,6 +185,118 @@ def test_process_attributes(self): assert processed_attributes['homeowner'] assert processed_attributes['number_of_something'] == '12' + def test_set_standard_field(self): + form_mixin = AttributeFormMixin() + form_mixin.project = self.project + form_mixin.fields = {'party_name': CharField()} + questionnaire = q_factories.QuestionnaireFactory(project=self.project) + q_factories.QuestionFactory.create( + name='party_name', + questionnaire=questionnaire, + label={'en': 'Name', 'de': 'Name'}) + form_mixin.set_standard_field('party_name') + + assert 'en="Name"' in form_mixin.fields['party_name'].labels_xlang + assert 'de="Name"' in form_mixin.fields['party_name'].labels_xlang + + def test_set_standard_field_set_field_name(self): + form_mixin = AttributeFormMixin() + form_mixin.project = self.project + form_mixin.fields = {'name': CharField()} + questionnaire = q_factories.QuestionnaireFactory(project=self.project) + q_factories.QuestionFactory.create( + name='party_name', + questionnaire=questionnaire, + label={'en': 'Name', 'de': 'Name'}) + form_mixin.set_standard_field('party_name', field_name='name') + + assert 'en="Name"' in form_mixin.fields['name'].labels_xlang + assert 'de="Name"' in form_mixin.fields['name'].labels_xlang + + def test_set_standard_field_no_question(self): + form_mixin = AttributeFormMixin() + form_mixin.project = self.project + form_mixin.fields = {'name': CharField()} + questionnaire = q_factories.QuestionnaireFactory(project=self.project) + q_factories.QuestionFactory.create( + name='party_name', + questionnaire=questionnaire, + label={'en': 'Name', 'de': 'Name'}) + form_mixin.set_standard_field('name',) + + assert hasattr(form_mixin.fields['name'], 'labels_xlang') is False + + def test_set_standard_field_with_options(self): + form_mixin = AttributeFormMixin() + form_mixin.project = self.project + form_mixin.fields = {'building': ChoiceField( + choices=(('barn', 'Barn'), ('house', 'House')))} + questionnaire = q_factories.QuestionnaireFactory( + project=self.project, + default_language='de') + question = q_factories.QuestionFactory.create( + type='S1', + name='building', + questionnaire=questionnaire, + label={'en': 'Name', 'de': 'Name'}) + q_factories.QuestionOptionFactory( + question=question, + name='barn', + label={'de': 'Scheune', 'en': 'Barn'}, + index=0) + q_factories.QuestionOptionFactory( + question=question, + name='house', + label={'de': 'Haus', 'en': 'Haus'}, + index=1) + form_mixin.set_standard_field('building') + + widget = form_mixin.fields['building'].widget + assert isinstance(widget, XLangSelect) is True + assert widget.choices == [('barn', 'Scheune'), ('house', 'Haus')] + assert widget.xlang_labels == { + 'barn': {'de': 'Scheune', 'en': 'Barn'}, + 'house': {'de': 'Haus', 'en': 'Haus'} + } + + def test_set_standard_field_with_empty_choice(self): + form_mixin = AttributeFormMixin() + form_mixin.project = self.project + form_mixin.fields = {'building': ChoiceField( + choices=(('barn', 'Barn'), ('house', 'House')))} + questionnaire = q_factories.QuestionnaireFactory( + project=self.project, + default_language='de') + question = q_factories.QuestionFactory.create( + type='S1', + name='building', + questionnaire=questionnaire, + label={'en': 'Name', 'de': 'Name'}) + q_factories.QuestionOptionFactory( + question=question, + name='barn', + label={'de': 'Scheune', 'en': 'Barn'}, + index=0) + q_factories.QuestionOptionFactory( + question=question, + name='house', + label={'de': 'Haus', 'en': 'Haus'}, + index=1) + form_mixin.set_standard_field('building', + empty_choice='Select house type') + + widget = form_mixin.fields['building'].widget + assert isinstance(widget, XLangSelect) is True + assert widget.choices == [ + ('', 'Select house type'), + ('barn', 'Scheune'), + ('house', 'Haus') + ] + assert widget.xlang_labels == { + 'barn': {'de': 'Scheune', 'en': 'Barn'}, + 'house': {'de': 'Haus', 'en': 'Haus'} + } + class AttributeFormBaseTest(UserTestCase, FileStorageTestCase, TestCase): diff --git a/cadasta/core/tests/test_models.py b/cadasta/core/tests/test_models.py index d670d0b8e..b38a473db 100644 --- a/cadasta/core/tests/test_models.py +++ b/cadasta/core/tests/test_models.py @@ -142,8 +142,6 @@ def test_duplicate_slug_long_name(self): instance2.refresh_from_db() assert instance1.slug != instance2.slug assert instance2.slug[-2:] == '-1' - print(instance1.slug) - print(instance2.slug) def test_duplicate_slug_long_name_100_times(self): for i in range(0, 101): diff --git a/cadasta/core/tests/test_widgets.py b/cadasta/core/tests/test_widgets.py new file mode 100644 index 000000000..a9468d003 --- /dev/null +++ b/cadasta/core/tests/test_widgets.py @@ -0,0 +1,11 @@ +from django.test import TestCase +from ..widgets import XLangSelect + + +class XLangSelectTest(TestCase): + def test_render_option(self): + xlang_labels = {'field': {'en': 'Field', 'de': 'Feld'}} + widget = XLangSelect(xlang_labels=xlang_labels) + option = widget.render_option([], 'field', 'Feld') + assert 'data-label-en="Field"' in option + assert 'data-label-de="Feld"' in option diff --git a/cadasta/core/widgets.py b/cadasta/core/widgets.py new file mode 100644 index 000000000..52369c63c --- /dev/null +++ b/cadasta/core/widgets.py @@ -0,0 +1,22 @@ +from django.forms import Select, SelectMultiple +from jsonattrs.mixins import template_xlang_labels + + +class XLangSelect(Select): + def __init__(self, xlang_labels, *args, **kwargs): + super().__init__(*args, **kwargs) + self.xlang_labels = xlang_labels + + def render_option(self, selected_choices, option_value, option_label): + rendered = super().render_option( + selected_choices, option_value, option_label) + rendered = rendered.replace( + '1 party" in response.content assert "
1
resource" in response.content + def test_get_with_labels(self): + file = self.get_file( + '/questionnaires/tests/files/ok-multilingual.xlsx', 'rb') + form = self.storage.save('xls-forms/xls-form.xlsx', file) + Questionnaire.objects.create_from_form( + xls_form=form, + original_file='original.xls', + project=self.project + ) + response = self.request(user=self.user) + assert response.status_code == 200 + assert response.content == self.render_content( + form_lang_default='en', + form_langs=[('en', 'English'), ('fr', 'French')]) + @pytest.mark.usefixtures('make_dirs') class ProjectAddTest(UserTestCase, FileStorageTestCase, TestCase): @@ -707,7 +723,6 @@ def test_flow_with_org_is_chosen_function(self): ) assert extents_response.status_code == 200 form = extents_response.context_data['form'] - print(str(form.fields)) assert len(form.fields['organization'].choices) == 3 assert form.fields['organization'].choices[0] == ( '', "Please select an organization") @@ -928,7 +943,6 @@ class ProjectEditDetailsTest(ViewTestCase, UserTestCase, 'name': 'New Name', 'description': 'New Description', 'urls': '', - 'questionnaire': '', 'contacts-TOTAL_FORMS': 1, 'contacts-INITIAL_FORMS': 0, 'contacts-0-name': '', @@ -1027,7 +1041,8 @@ def test_get_with_archived_project(self): def test_post_with_authorized_user(self): user = UserFactory.create() assign_policies(user) - response = self.request(user=user, method='POST') + response = self.request(user=user, method='POST', + post_data={'questionnaire': ''}) assert response.status_code == 302 assert self.expected_success_url in response.location @@ -1040,7 +1055,8 @@ def test_post_with_blocked_questionnaire_upload(self): SpatialUnitFactory.create(project=self.project) user = UserFactory.create() assign_policies(user) - response = self.request(user=user, method='POST') + response = self.request(user=user, method='POST', + post_data={'questionnaire': ''}) assert response.status_code == 200 self.project.refresh_from_db() @@ -1052,8 +1068,7 @@ def test_post_empty_questionnaire_with_blocked_questionnaire_upload(self): SpatialUnitFactory.create(project=self.project) user = UserFactory.create() assign_policies(user) - response = self.request(user=user, method='POST', - post_data={'questionnaire': None}) + response = self.request(user=user, method='POST') assert response.status_code == 302 assert self.expected_success_url in response.location diff --git a/cadasta/organization/views/mixins.py b/cadasta/organization/views/mixins.py index 56e3856ff..0201037fc 100644 --- a/cadasta/organization/views/mixins.py +++ b/cadasta/organization/views/mixins.py @@ -8,6 +8,7 @@ from core.views.mixins import SuperUserCheckMixin from ..models import Organization, Project, OrganizationRole, ProjectRole +from questionnaires.models import Questionnaire class OrganizationMixin: @@ -113,7 +114,25 @@ def is_administrator(self): def get_context_data(self, *args, **kwargs): prj_member = self.is_administrator or self.get_prj_role() is not None + project = self.get_project() + + form_lang_default = None + form_langs = [] + + if project.current_questionnaire: + q = Questionnaire.objects.get(id=project.current_questionnaire) + form_lang_default = q.default_language + question = q.questions.filter(~Q(label_xlat={})).first() + + if (question and isinstance(question.label_xlat, dict)): + form_langs = sorted([(l, settings.FORM_LANGS.get(l)) + for l in question.label_xlat.keys()], + key=lambda x: x[1]) + return super().get_context_data(is_project_member=prj_member, + form_lang_default=form_lang_default, + form_langs=sorted(form_langs, + key=lambda x: x[1]), *args, **kwargs) diff --git a/cadasta/party/forms.py b/cadasta/party/forms.py index 85fbb8320..8a9ba9abe 100644 --- a/cadasta/party/forms.py +++ b/cadasta/party/forms.py @@ -19,6 +19,10 @@ def __init__(self, project, *args, **kwargs): self.project = project self.add_attribute_fields() + if self.project.current_questionnaire: + self.set_standard_field('party_name', field_name='name') + self.set_standard_field('party_type', field_name='type') + def clean(self): # remove validation errors for required fields # which are not related to the current type @@ -49,11 +53,16 @@ class Meta: def __init__(self, project=None, *args, **kwargs): super().__init__(*args, **kwargs) self.project = project - tenuretypes = sorted([ - (choice[0], _(choice[1])) for choice in - TenureRelationshipType.objects.values_list('id', 'label') - ]) - self.fields['tenure_type'].choices = tenuretypes + + if self.project.current_questionnaire: + self.set_standard_field('tenure_type') + else: + tenuretypes = sorted([ + (choice[0], _(choice[1])) for choice in + TenureRelationshipType.objects.values_list('id', 'label') + ]) + self.fields['tenure_type'].choices = tenuretypes + self.add_attribute_fields() def save(self, *args, **kwargs): diff --git a/cadasta/party/tests/test_forms.py b/cadasta/party/tests/test_forms.py index ae38aa93f..7113eed68 100644 --- a/cadasta/party/tests/test_forms.py +++ b/cadasta/party/tests/test_forms.py @@ -4,7 +4,7 @@ from jsonattrs.models import Attribute, AttributeType, Schema from organization.tests.factories import ProjectFactory from party.tests.factories import PartyFactory -from questionnaires.tests.factories import QuestionnaireFactory +from questionnaires.tests import factories as q_factories from .. import forms from ..models import Party, TenureRelationshipType @@ -12,6 +12,27 @@ class PartyFormTest(UserTestCase, TestCase): + def test_init_without_form(self): + project = ProjectFactory.create() + form = forms.PartyForm(project) + assert hasattr(form.fields['name'], 'labels_xlang') is False + assert hasattr(form.fields['type'], 'labels_xlang') is False + + def test_init_with_form(self): + project = ProjectFactory.create() + questionnaire = q_factories.QuestionnaireFactory(project=project) + q_factories.QuestionFactory.create( + name='party_type', + questionnaire=questionnaire, + label={'en': 'Type', 'de': 'Typ'}) + q_factories.QuestionFactory.create( + name='party_name', + questionnaire=questionnaire, + label={'en': 'Name', 'de': 'Name'}) + form = forms.PartyForm(project) + assert hasattr(form.fields['name'], 'labels_xlang') is True + assert hasattr(form.fields['type'], 'labels_xlang') is True + def test_create_party(self): data = { 'name': 'Cadasta', @@ -130,7 +151,8 @@ def test_clean(self): 'party::in::homeowner': True } project = ProjectFactory.create() - questionnaire = QuestionnaireFactory.create(project=project) + questionnaire = q_factories.QuestionnaireFactory.create( + project=project) content_type = ContentType.objects.get( app_label='party', model='party') @@ -188,3 +210,14 @@ def test_init(self): ) assert len(tenuretypes) > 0 assert list(form.fields['tenure_type'].choices) == list(tenuretypes) + assert hasattr(form.fields['tenure_type'], 'labels_xlang') is False + + def test_init_with_form(self): + project = ProjectFactory.create() + questionnaire = q_factories.QuestionnaireFactory(project=project) + q_factories.QuestionFactory.create( + name='tenure_type', + questionnaire=questionnaire, + label={'en': 'Type', 'de': 'Typ'}) + form = forms.TenureRelationshipEditForm(project) + assert hasattr(form.fields['tenure_type'], 'labels_xlang') is True diff --git a/cadasta/questionnaires/tests/test_validators.py b/cadasta/questionnaires/tests/test_validators.py index d13df3879..d7792a835 100644 --- a/cadasta/questionnaires/tests/test_validators.py +++ b/cadasta/questionnaires/tests/test_validators.py @@ -163,7 +163,6 @@ def test_invalid_nested_questiongroup(self): }] }] errors = validators.validate_question_groups(data) - print(errors) assert errors == [{'name': ['This field is required.'], 'questions': [ {'name': ['This field is required.']}], diff --git a/cadasta/spatial/forms.py b/cadasta/spatial/forms.py index e1c52cb3c..1679988d5 100644 --- a/cadasta/spatial/forms.py +++ b/cadasta/spatial/forms.py @@ -40,6 +40,11 @@ def __init__(self, project=None, *args, **kwargs): self.project = project self.add_attribute_fields() + if self.project.current_questionnaire: + self.set_standard_field('location_type', + _('Please select a location type'), + field_name='type') + def save(self, *args, **kwargs): entity_type = self.cleaned_data['type'] kwargs['entity_type'] = entity_type @@ -60,16 +65,26 @@ class Media: def __init__(self, project, spatial_unit, *args, **kwargs): super().__init__(*args, **kwargs) + self.project = project + self.fields['id'].widget = SelectPartyWidget(project.id) self.fields['party_type'].choices = ( - [('', _("Please select a party type"))] + - list(Party.TYPE_CHOICES)) + [('', _("Please select a party type"))] + list(Party.TYPE_CHOICES)) self.fields['tenure_type'].choices = ( [('', _("Please select a relationship type"))] + sorted([ (choice[0], _(choice[1])) for choice in TenureRelationshipType.objects.values_list('id', 'label')])) - self.project = project + + if self.project.current_questionnaire: + self.set_standard_field('party_name', + _('Please select a party type'), + field_name='name') + self.set_standard_field('party_type', + _('Please select a party type')) + self.set_standard_field('tenure_type', + _('Please select a relationship type')) + self.spatial_unit = spatial_unit self.party_ct = ContentType.objects.get( app_label='party', model='party') diff --git a/cadasta/spatial/tests/test_views_api_spatial_units.py b/cadasta/spatial/tests/test_views_api_spatial_units.py index d68519624..64870613a 100644 --- a/cadasta/spatial/tests/test_views_api_spatial_units.py +++ b/cadasta/spatial/tests/test_views_api_spatial_units.py @@ -319,7 +319,6 @@ def test_create_invalid_spatial_unit(self): response = self.request(user=self.user, method='POST', post_data=invalid_data) - print(response.content) assert response.status_code == 400 assert response.content['type'][0] == '"" is not a valid choice.' diff --git a/cadasta/templates/organization/organization_wrapper.html b/cadasta/templates/organization/organization_wrapper.html index 89d083e51..3d064ec3a 100644 --- a/cadasta/templates/organization/organization_wrapper.html +++ b/cadasta/templates/organization/organization_wrapper.html @@ -15,19 +15,20 @@ diff --git a/cadasta/templates/organization/project_attrs_import.html b/cadasta/templates/organization/project_attrs_import.html index 0cff25720..c588d0541 100644 --- a/cadasta/templates/organization/project_attrs_import.html +++ b/cadasta/templates/organization/project_attrs_import.html @@ -3,7 +3,10 @@ {% load i18n %} {% load widget_tweaks %} -{% block extra_script %} {{ form.media }} {% endblock %} +{% block extra_script %} +{{ block.super }} +{{ form.media }} +{% endblock %} {% block step_content %} diff --git a/cadasta/templates/organization/project_dashboard.html b/cadasta/templates/organization/project_dashboard.html index b388f9ead..ab0c6d71c 100644 --- a/cadasta/templates/organization/project_dashboard.html +++ b/cadasta/templates/organization/project_dashboard.html @@ -11,6 +11,7 @@ {% endblock %} {% block extra_script %} +{{ block.super }} {% leaflet_js plugins="groupedlayercontrol"%} diff --git a/cadasta/templates/organization/project_defaults_import.html b/cadasta/templates/organization/project_defaults_import.html index 3abe70776..c5993966e 100644 --- a/cadasta/templates/organization/project_defaults_import.html +++ b/cadasta/templates/organization/project_defaults_import.html @@ -1,4 +1,4 @@ -{% extends "organization/project_import_wrapper.html" %} {% load i18n %} {% load widget_tweaks %} {% block extra_script %} {{ form.media }} {% endblock %} {% block step_content %} {{ wizard.management_form }} {% if wizard.form.forms %} {{ wizard.form.management_form +{% extends "organization/project_import_wrapper.html" %} {% load i18n %} {% load widget_tweaks %} {% block extra_script %} {{ block.super }} {{ form.media }} {% endblock %} {% block step_content %} {{ wizard.management_form }} {% if wizard.form.forms %} {{ wizard.form.management_form }} {% for form in wizard.form.forms %} {{ form }} {% endfor %} {% else %}
diff --git a/cadasta/templates/organization/project_edit_details.html b/cadasta/templates/organization/project_edit_details.html index 2f37b4d09..86a6c58ab 100644 --- a/cadasta/templates/organization/project_edit_details.html +++ b/cadasta/templates/organization/project_edit_details.html @@ -5,6 +5,7 @@ {% load widget_tweaks %} {% block extra_script %} +{{ block.super }} {{ form.media }} {% endblock %} diff --git a/cadasta/templates/organization/project_edit_geometry.html b/cadasta/templates/organization/project_edit_geometry.html index 01a37488a..91a8029e2 100644 --- a/cadasta/templates/organization/project_edit_geometry.html +++ b/cadasta/templates/organization/project_edit_geometry.html @@ -14,6 +14,7 @@ {% endblock %} {% block extra_script %} +{{ block.super }} {% leaflet_js plugins="draw,forms" %} diff --git a/cadasta/templates/organization/project_edit_permissions.html b/cadasta/templates/organization/project_edit_permissions.html index 3cbc2bba4..816626f36 100644 --- a/cadasta/templates/organization/project_edit_permissions.html +++ b/cadasta/templates/organization/project_edit_permissions.html @@ -48,6 +48,7 @@

{% trans "Edit member permissions" %}

{% endblock %} {% block extra_script %} +{{ block.super }} +{% endblock %} + {% block sub-nav %} {% if is_project_member %} diff --git a/cadasta/templates/party/party_attrs.html b/cadasta/templates/party/party_attrs.html index db36c19d7..c3456364f 100644 --- a/cadasta/templates/party/party_attrs.html +++ b/cadasta/templates/party/party_attrs.html @@ -7,7 +7,7 @@ {% if field.name|slice:":7" == "party::" %} {% with type=field.name|slice:"7:"|slice:":2" %}