diff --git a/bower.json b/bower.json index 6026df924..eb398b69a 100644 --- a/bower.json +++ b/bower.json @@ -2,29 +2,30 @@ "name": "OpenSlides", "private": true, "dependencies": { - "lodash": "~3.0.1", + "lodash": "~3.10.0", "jquery": "~2.1.4", "jquery.cookie": "~1.4.1", - "bootstrap-css-only": "~3.3.4", - "angular": "~1.3.15", - "angular-bootstrap": "~0.14.2", - "angular-messages": "~1.3.15", - "angular-animate": "~1.3.15", - "angular-csv-import": "~0.0.15", - "angular-loading-bar": "~0.7.1", - "angular-ui-router": "~0.2.13", - "angular-ui-select": "~0.13", - "angular-ui-switch": "~0.1.0", - "angular-ui-tree": "~2.2.0", - "angular-gettext": "~2.0.2", - "angular-sanitize": "~1.3.15", + "bootstrap-css-only": "~3.3.5", + "angular": "~1.4.7", + "angular-messages": "~1.4.7", + "angular-animate": "~1.4.7", + "angular-sanitize": "~1.4.7", + "angular-bootstrap": "~0.14.3", + "angular-csv-import": "~0.0.26", + "angular-formly-templates-bootstrap": "~6.1.5", + "angular-formly": "~7.3.2", + "angular-loading-bar": "~0.8.0", + "angular-ui-router": "~0.2.15", + "angular-ui-select": "~0.13.1", + "angular-ui-switch": "~0.1.1", + "angular-ui-tree": "~2.10.0", + "angular-gettext": "~2.1.2", "angular-xeditable": "~0.1.9", - "ng-fab-form": "~1.2.7", - "ngBootbox": "~0.0.5", + "ngBootbox": "~0.1.2", "sockjs": "~0.3.4", - "font-awesome-bower": "4.3.0", - "js-data": "~2.3.0", - "js-data-angular": "~3.0.0", + "font-awesome-bower": "~4.4.0", + "js-data": "~2.8.1", + "js-data-angular": "~3.1.0", "ng-file-upload": "~9.1.2" } } diff --git a/openslides/core/static/css/app.css b/openslides/core/static/css/app.css index e1e677347..879f6147e 100644 --- a/openslides/core/static/css/app.css +++ b/openslides/core/static/css/app.css @@ -49,92 +49,49 @@ body { color: red; font-weight: bold; } -.spacer { +.spacer, .spacer-top { margin-top: 7px; } +.spacer-right { + margin-right: 5px; +} .hoverActions { font-size: 85%; } .hiddenDiv { visibility: hidden; } +/* override bootstraps's progress bar for poll results*/ +.progress { + height: 12px; + margin-bottom: 0; +} +/* voting results */ +.result_label { + margin-top: 5px; +} -/* TODO: used by ng-fab-forms */ -.validation-success { - opacity: 1; - display: block; - position: absolute; - right: -7.2px; - bottom: -7.2px; - font-size: 28.8px; - width: 36px; - height: 36px; - line-height: 36px; - text-align: center; - border-radius: 36px; - color: #62b14c; - transition: all ease-out 0.32s; } - .validation-success:after { - display: block; - content: '\e013'; - font-family: 'Glyphicons Halflings'; } - .validation-success.ng-hide { - transition-delay: 0s; - transition: all ease-out 0.12s; - opacity: 0; - transform: rotate(360deg); } -.ng-hide-remove li { - opacity: 0; } +/* ngAnimate classes */ +.animate-item.ng-enter { + -webkit-animation: fade-in 1s linear; + animation: fade-in 1.5s linear; +} +.animate-item.ng-leave { + -webkit-animation: fade-out 1s linear; + animation: fade-out 1.5s linear; +} -.validation { - color: #fff; - margin: 0; - position: relative; - font-size: 14px; - overflow: visible; - background: #c00640; } - .validation ul { - display: block; - overflow: hidden; } - .validation li { - display: block; - line-height: 1; - background: #c00640; - position: absolute; - right: -4px; - top: -10px; - text-align: center; - font-weight: bold; - padding: 2px 10px; - color: #fff; - transform: rotate(0deg); - transition: all ease-in 0.2s; - opacity: 1; - transition-delay: 0s; } - .validation li.ng-enter { - opacity: 0; - top: 0; } - .validation li.ng-leave { - transition: all ease-in 0s; - opacity: 0; } - *:focus + .validation li { - background-color: #63bff8 !important; } - -input.ng-touched.ng-invalid:not(.ng-valid), textarea.ng-touched.ng-invalid:not(.ng-valid), select.ng-touched.ng-invalid:not(.ng-valid) { - border-color: #c00640; } -input:focus, input:focus.ng-touched.ng-invalid:not(.ng-valid), textarea:focus, textarea:focus.ng-touched.ng-invalid:not(.ng-valid), select:focus, select:focus.ng-touched.ng-invalid:not(.ng-valid) { - border-color: #63bff8; } -input.ng-valid-required.ng-valid:not(.ng-invalid), textarea.ng-valid-required.ng-valid:not(.ng-invalid), select.ng-valid-required.ng-valid:not(.ng-invalid) { - border-color: #62b14c; } - -form[class*="ng-invalid"] button.btn[type=submit] { - background: #63bff8; - transition: none; } - -form button.btn[type=submit] { - transition: all ease-in 0.5s; - background: #62b14c; } +@keyframes fade-out { + 0% { opacity: 1; background: none; } + 25% { opacity: 1; background: #f8efc0; } + 100% { opacity: 0; background: none; } +} +@keyframes fade-in { + 0% { opacity: 0; background: none; } + 25% { opacity: 1; background: #dff0d8; } + 100% { opacity: 1; background: none; } +} @@ -296,9 +253,9 @@ tr.selected td { padding-left: 20px; } .smallhr { - margin-top: 2px; - margin-bottom: 2px; - border-color: #333333; + margin-top: 5px; + margin-bottom: 5px; + border-color: #cccccc; } .resultcolumn { font-weight: bold; diff --git a/openslides/core/static/js/core/site.js b/openslides/core/static/js/core/site.js index 014351ca8..30a5f6ebb 100644 --- a/openslides/core/static/js/core/site.js +++ b/openslides/core/static/js/core/site.js @@ -6,8 +6,9 @@ angular.module('OpenSlidesApp.core.site', [ 'OpenSlidesApp.core', 'ui.router', + 'formly', + 'formlyBootstrap', 'ngBootbox', - 'ngFabForm', 'ngMessages', 'ngCsvImport', 'ngSanitize', // TODO: only use this in functions that need it. @@ -260,13 +261,6 @@ angular.module('OpenSlidesApp.core.site', [ $locationProvider.html5Mode(true); }) -// config for ng-fab-form -.config(function(ngFabFormProvider) { - ngFabFormProvider.extendConfig({ - setAsteriskForRequiredLabel: true - }); -}) - // Helper to add ui.router states at runtime. // Needed for the django url_patterns. .provider('runtimeStates', function($stateProvider) { @@ -299,6 +293,26 @@ angular.module('OpenSlidesApp.core.site', [ editableOptions.theme = 'bs3'; }) +// angular formly config options +.run([ + 'formlyConfig', + function (formlyConfig) { + // NOTE: This next line is highly recommended. Otherwise Chrome's autocomplete will appear over your options! + formlyConfig.extras.removeChromeAutoComplete = true; + + // Configure custom types + formlyConfig.setType({ + name: 'ui-select-single', + extends: 'select', + templateUrl: 'static/templates/core/ui-select-single.html' + }); + formlyConfig.setType({ + name: 'ui-select-multiple', + extends: 'select', + templateUrl: 'static/templates/core/ui-select-multiple.html' + }); + } +]) // html-tag os-form-field to generate generic from fields // TODO: make it possible to use other fields then config fields diff --git a/openslides/core/static/templates/core/ui-select-multiple.html b/openslides/core/static/templates/core/ui-select-multiple.html new file mode 100644 index 000000000..32cef10de --- /dev/null +++ b/openslides/core/static/templates/core/ui-select-multiple.html @@ -0,0 +1,10 @@ + + + + {{$item[to.labelProp]}} + + +
+
+
diff --git a/openslides/core/static/templates/core/ui-select-single.html b/openslides/core/static/templates/core/ui-select-single.html new file mode 100644 index 000000000..efa0e4771 --- /dev/null +++ b/openslides/core/static/templates/core/ui-select-single.html @@ -0,0 +1,10 @@ + + + + {{$select.selected[to.labelProp]}} + + +
+
+
diff --git a/openslides/motions/migrations/0004_auto_20151105_2312.py b/openslides/motions/migrations/0004_auto_20151105_2312.py new file mode 100644 index 000000000..412d44ad6 --- /dev/null +++ b/openslides/motions/migrations/0004_auto_20151105_2312.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('motions', '0003_auto_20150904_2029'), + ] + + operations = [ + migrations.RenameField( + model_name='state', + old_name='icon', + new_name='css_class', + ), + migrations.AlterField( + model_name='state', + name='css_class', + field=models.CharField(max_length=255, default='primary'), + ), + migrations.AlterField( + model_name='state', + name='workflow', + field=models.ForeignKey(to='motions.Workflow', related_name='states'), + ), + ] diff --git a/openslides/motions/models.py b/openslides/motions/models.py index 9067f43b8..bac06ac91 100644 --- a/openslides/motions/models.py +++ b/openslides/motions/models.py @@ -736,8 +736,12 @@ class State(RESTModelMixin, models.Model): next_states = models.ManyToManyField('self', symmetrical=False) """A many-to-many relation to all states, that can be choosen from this state.""" - icon = models.CharField(max_length=255) - """A string representing the url to the icon-image.""" + css_class = models.CharField(max_length=255, default='primary') + """ + A css class string for showing the state name in a coloured label based on bootstrap, + e.g. 'danger' (red), 'success' (green), 'warning' (yellow), 'default' (grey). + Default value is 'primary' (blue). + """ required_permission_to_see = models.CharField(max_length=255, blank=True) """ @@ -764,7 +768,6 @@ class State(RESTModelMixin, models.Model): This behavior can be changed by the form and view, e. g. via the MotionDisableVersioningMixin. """ - # TODO: preferred_for = ChoiceField leave_old_version_active = models.BooleanField(default=False) """If true, new versions are not automaticly set active.""" diff --git a/openslides/motions/serializers.py b/openslides/motions/serializers.py index f9732fdac..d713288e6 100644 --- a/openslides/motions/serializers.py +++ b/openslides/motions/serializers.py @@ -49,7 +49,7 @@ class StateSerializer(ModelSerializer): 'id', 'name', 'action_word', - 'icon', + 'css_class', 'required_permission_to_see', 'allow_support', 'allow_create_poll', @@ -77,9 +77,17 @@ class MotionLogSerializer(ModelSerializer): """ Serializer for motion.models.MotionLog objects. """ + message = SerializerMethodField() + class Meta: model = MotionLog - fields = ('message_list', 'person', 'time',) + fields = ('message_list', 'person', 'time', 'message',) + + def get_message(self, obj): + """ + Concats the message parts to one string. Useful for smart template code. + """ + return str(obj) class MotionPollSerializer(ModelSerializer): diff --git a/openslides/motions/signals.py b/openslides/motions/signals.py index 5a61a2c4b..113113406 100644 --- a/openslides/motions/signals.py +++ b/openslides/motions/signals.py @@ -189,13 +189,16 @@ def create_builtin_workflows(sender, **kwargs): allow_submitter_edit=True) state_1_2 = State.objects.create(name=ugettext_noop('accepted'), workflow=workflow_1, - action_word=ugettext_noop('Accept')) + action_word=ugettext_noop('Accept'), + css_class='success') state_1_3 = State.objects.create(name=ugettext_noop('rejected'), workflow=workflow_1, - action_word=ugettext_noop('Reject')) + action_word=ugettext_noop('Reject'), + css_class='danger') state_1_4 = State.objects.create(name=ugettext_noop('not decided'), workflow=workflow_1, - action_word=ugettext_noop('Do not decide')) + action_word=ugettext_noop('Do not decide'), + css_class='default') state_1_1.next_states.add(state_1_2, state_1_3, state_1_4) workflow_1.first_state = state_1_1 workflow_1.save() @@ -216,35 +219,43 @@ def create_builtin_workflows(sender, **kwargs): state_2_3 = State.objects.create(name=ugettext_noop('accepted'), workflow=workflow_2, action_word=ugettext_noop('Accept'), - versioning=True) + versioning=True, + css_class='success') state_2_4 = State.objects.create(name=ugettext_noop('rejected'), workflow=workflow_2, action_word=ugettext_noop('Reject'), - versioning=True) + versioning=True, + css_class='danger') state_2_5 = State.objects.create(name=ugettext_noop('withdrawed'), workflow=workflow_2, action_word=ugettext_noop('Withdraw'), - versioning=True) + versioning=True, + css_class='default') state_2_6 = State.objects.create(name=ugettext_noop('adjourned'), workflow=workflow_2, action_word=ugettext_noop('Adjourn'), - versioning=True) + versioning=True, + css_class='default') state_2_7 = State.objects.create(name=ugettext_noop('not concerned'), workflow=workflow_2, action_word=ugettext_noop('Do not concern'), - versioning=True) + versioning=True, + css_class='default') state_2_8 = State.objects.create(name=ugettext_noop('commited a bill'), workflow=workflow_2, action_word=ugettext_noop('Commit a bill'), - versioning=True) + versioning=True, + css_class='default') state_2_9 = State.objects.create(name=ugettext_noop('needs review'), workflow=workflow_2, action_word=ugettext_noop('Needs review'), - versioning=True) + versioning=True, + css_class='default') state_2_10 = State.objects.create(name=ugettext_noop('rejected (not authorized)'), workflow=workflow_2, action_word=ugettext_noop('Reject (not authorized)'), - versioning=True) + versioning=True, + css_class='default') state_2_1.next_states.add(state_2_2, state_2_5, state_2_10) state_2_2.next_states.add(state_2_3, state_2_4, state_2_5, state_2_6, state_2_7, state_2_8, state_2_9) workflow_2.first_state = state_2_1 diff --git a/openslides/motions/static/js/motions/motions.js b/openslides/motions/static/js/motions/motions.js index 41d83f393..af837ec38 100644 --- a/openslides/motions/static/js/motions/motions.js +++ b/openslides/motions/static/js/motions/motions.js @@ -40,7 +40,7 @@ angular.module('OpenSlidesApp.motions', []) } ]) -// Load all MotionWorkflows at stateup +// Load all MotionWorkflows at startup .run([ 'Workflow', function (Workflow) { @@ -53,7 +53,7 @@ angular.module('OpenSlidesApp.motions', []) 'Config', function (DS, Config) { return DS.defineResource({ - name: 'motions/poll', + name: 'motions/motionpoll', relations: { belongsTo: { 'motions/motion': { @@ -63,61 +63,91 @@ angular.module('OpenSlidesApp.motions', []) } }, methods: { - getYesPercent: function () { + getYesPercent: function (valueOnly) { var config = Config.get('motions_poll_100_percent_base').value; - if (config == "WITHOUT_INVALID" && this.votesvalid > 0) { - return "(" + Math.round(this.yes * 100 / this.votesvalid * 10) / 10 + " %)"; - } else if (config == "WITH_INVALID" && this.votescast > 0) { - return "(" + Math.round(this.yes * 100 / (this.votescast) * 10) / 10 + " %)"; + var returnvalue; + if (config == "WITHOUT_INVALID" && this.votesvalid > 0 && this.yes >= 0) { + returnvalue = Math.round(this.yes * 100 / this.votesvalid * 10) / 10; + } else if (config == "WITH_INVALID" && this.votescast > 0 && this.yes >= 0) { + returnvalue = Math.round(this.yes * 100 / (this.votescast) * 10) / 10; } else { - return null; + returnvalue = null; } + if (!valueOnly && returnvalue != null) { + returnvalue = "(" + returnvalue + "%)"; + } + return returnvalue; }, - getNoPercent: function () { + getNoPercent: function (valueOnly) { var config = Config.get('motions_poll_100_percent_base').value; - if (config == "WITHOUT_INVALID" && this.votesvalid > 0) { - return "(" + Math.round(this.no * 100 / this.votesvalid * 10) / 10 + " %)"; - } else if (config == "WITH_INVALID" && this.votescast > 0) { - return "(" + Math.round(this.no * 100 / (this.votescast) * 10) / 10 + " %)"; + var returnvalue; + if (config == "WITHOUT_INVALID" && this.votesvalid > 0 && this.no >= 0) { + returnvalue = Math.round(this.no * 100 / this.votesvalid * 10) / 10; + } else if (config == "WITH_INVALID" && this.votescast > 0 && this.no >= 0) { + returnvalue = Math.round(this.no * 100 / (this.votescast) * 10) / 10; } else { - return null; + returnvalue = null; } + if (!valueOnly && returnvalue != null) { + returnvalue = "(" + returnvalue + "%)"; + } + return returnvalue; }, - getAbstainPercent: function () { + getAbstainPercent: function (valueOnly) { var config = Config.get('motions_poll_100_percent_base').value; - if (config == "WITHOUT_INVALID" && this.votesvalid > 0) { - return "(" + Math.round(this.abstain * 100 / this.votesvalid * 10) / 10 + " %)"; - } else if (config == "WITH_INVALID" && this.votescast > 0) { - return "(" + Math.round(this.abstain * 100 / (this.votescast) * 10) / 10 + " %)"; + var returnvalue; + if (config == "WITHOUT_INVALID" && this.votesvalid > 0 && this.abstain >= 0) { + returnvalue = Math.round(this.abstain * 100 / this.votesvalid * 10) / 10; + } else if (config == "WITH_INVALID" && this.votescast > 0 && this.abstain >= 0) { + returnvalue = Math.round(this.abstain * 100 / (this.votescast) * 10) / 10; } else { - return null; + returnvalue = null; } + if (!valueOnly && returnvalue != null) { + returnvalue = "(" + returnvalue + "%)"; + } + return returnvalue; }, - getVotesValidPercent: function () { + getVotesValidPercent: function (valueOnly) { var config = Config.get('motions_poll_100_percent_base').value; - if (config == "WITHOUT_INVALID") { - return "(100 %)"; - } else if (config == "WITH_INVALID") { - return "(" + Math.round(this.votesvalid * 100 / (this.votescast) * 10) / 10 + " %)"; + var returnvalue; + if (config == "WITHOUT_INVALID" && this.votevalid >= 0) { + returnvalue = 100; + } else if (config == "WITH_INVALID" && this.votevalid >= 0) { + returnvalue = Math.round(this.votesvalid * 100 / (this.votescast) * 10) / 10; } else { - return null; + returnvalue = null; } + if (!valueOnly && returnvalue != null) { + returnvalue = "(" + returnvalue + "%)"; + } + return returnvalue; }, - getVotesInvalidPercent: function () { + getVotesInvalidPercent: function (valueOnly) { var config = Config.get('motions_poll_100_percent_base').value; - if (config == "WITH_INVALID") { - return "(" + Math.round(this.votesinvalid * 100 / (this.votescast) * 10) / 10 + " %)"; + var returnvalue; + if (config == "WITH_INVALID" && this.voteinvalid >= 0) { + returnvalue = Math.round(this.votesinvalid * 100 / (this.votescast) * 10) / 10; } else { - return null; + returnvalue = null; } + if (!valueOnly && returnvalue != null) { + returnvalue = "(" + returnvalue + "%)"; + } + return returnvalue; }, - getVotesCastPercent: function () { + getVotesCastPercent: function (valueOnly) { var config = Config.get('motions_poll_100_percent_base').value; - if (config == "WITH_INVALID") { - return "(100 %)"; + var returnvalue; + if (config == "WITH_INVALID" && this.votecast >= 0) { + returnvalue = 100; } else { - return null; + returnvalue = null; } + if (!valueOnly && returnvalue != null) { + returnvalue = "(" + returnvalue + "%)"; + } + return returnvalue; } } }); @@ -183,6 +213,10 @@ angular.module('OpenSlidesApp.motions', []) localField: 'tags', localKeys: 'tags_id', }, + 'mediafiles/mediafile': { + localField: 'attachments', + localKeys: 'attachments_id', + }, 'users/user': [ { localField: 'submitters', @@ -193,7 +227,7 @@ angular.module('OpenSlidesApp.motions', []) localKeys: 'supporters_id', } ], - 'motions/poll': { + 'motions/motionpoll': { localField: 'polls', foreignKey: 'motion_id', } @@ -209,6 +243,144 @@ angular.module('OpenSlidesApp.motions', []) } ]) +// Provide generic motion form fields for create and update view +.factory('MotionFormFieldFactory', [ + 'gettext', + 'Category', + 'Config', + 'Mediafile', + 'Tag', + 'User', + 'Workflow', + function (gettext, Category, Config, Mediafile, Tag, User, Workflow) { + return { + getFormFields: function () { + return [ + { + key: 'identifier', + type: 'input', + templateOptions: { + label: gettext('Identifier') + }, + hide: true + }, + { + key: 'submitters_id', + type: 'ui-select-multiple', + templateOptions: { + label: gettext('Submitters'), + optionsAttr: 'bs-options', + options: User.getAll(), + ngOptions: 'option[to.valueProp] as option in to.options | filter: $select.search', + valueProp: 'id', + labelProp: 'full_name', + placeholder: gettext('Select or search a submitter...') + } + }, + { + key: 'title', + type: 'input', + templateOptions: { + label: gettext('Title'), + required: true + } + }, + { + key: 'text', + type: 'textarea', + templateOptions: { + label: gettext('Text'), + required: true + } + }, + { + key: 'reason', + type: 'textarea', + templateOptions: { + label: gettext('Reason') + } + }, + { + key: 'more', + type: 'checkbox', + templateOptions: { + label: gettext('Show extended fields') + } + }, + { + key: 'attachments_id', + type: 'ui-select-multiple', + templateOptions: { + label: gettext('Attachment'), + optionsAttr: 'bs-options', + options: Mediafile.getAll(), + ngOptions: 'option[to.valueProp] as option in to.options | filter: $select.search', + valueProp: 'id', + labelProp: 'title_or_filename', + placeholder: gettext('Select or search an attachment...') + }, + hideExpression: '!model.more' + }, + { + key: 'category_id', + type: 'ui-select-single', + templateOptions: { + label: gettext('Category'), + optionsAttr: 'bs-options', + options: Category.getAll(), + ngOptions: 'option[to.valueProp] as option in to.options | filter: $select.search', + valueProp: 'id', + labelProp: 'name', + placeholder: gettext('Select or search a category...') + }, + hideExpression: '!model.more' + }, + { + key: 'tags_id', + type: 'ui-select-multiple', + templateOptions: { + label: gettext('Tags'), + optionsAttr: 'bs-options', + options: Tag.getAll(), + ngOptions: 'option[to.valueProp] as option in to.options | filter: $select.search', + valueProp: 'id', + labelProp: 'name', + placeholder: gettext('Select or search a tag...') + }, + hideExpression: '!model.more' + }, + { + key: 'supporters_id', + type: 'ui-select-multiple', + templateOptions: { + label: gettext('Supporters'), + optionsAttr: 'bs-options', + options: User.getAll(), + ngOptions: 'option[to.valueProp] as option in to.options | filter: $select.search', + valueProp: 'id', + labelProp: 'full_name', + placeholder: gettext('Select or search a supporter...') + }, + hideExpression: '!model.more' + }, + { + key: 'workflow_id', + type: 'ui-select-single', + templateOptions: { + label: gettext('Workflow'), + optionsAttr: 'bs-options', + options: Workflow.getAll(), + ngOptions: 'option[to.valueProp] as option in to.options | filter: $select.search', + valueProp: 'id', + labelProp: 'name', + placeholder: gettext('Select or search a workflow...') + }, + hideExpression: '!model.more', + }]; + } + } + } +]) .factory('Category', ['DS', function(DS) { return DS.defineResource({ name: 'motions/category', @@ -252,6 +424,9 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions']) categories: function(Category) { return Category.findAll(); }, + tags: function(Tag) { + return Tag.findAll(); + }, users: function(User) { return User.findAll(); } @@ -259,9 +434,6 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions']) }) .state('motions.motion.create', { resolve: { - items: function(Agenda) { - return Agenda.findAll(); - }, categories: function(Category) { return Category.findAll(); }, @@ -273,6 +445,9 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions']) }, mediafiles: function(Mediafile) { return Mediafile.findAll(); + }, + workflows: function(Workflow) { + return Workflow.findAll(); } } }) @@ -287,9 +462,12 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions']) users: function(User) { return User.findAll(); }, + mediafiles: function(Mediafile) { + return Mediafile.findAll(); + }, tags: function(Tag) { return Tag.findAll(); - }, + } } }) .state('motions.motion.detail.update', { @@ -297,9 +475,6 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions']) '@motions.motion': {} }, resolve: { - items: function(Agenda) { - return Agenda.findAll(); - }, categories: function(Category) { return Category.findAll(); }, @@ -311,6 +486,9 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions']) }, mediafiles: function(Mediafile) { return Mediafile.findAll(); + }, + workflows: function(Workflow) { + return Workflow.findAll(); } } }) @@ -351,11 +529,16 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions']) '$state', 'Motion', 'Category', + 'Tag', + 'Workflow', 'User', - function($scope, $state, Motion, Category, User) { + function($scope, $state, Motion, Category, Tag, Workflow, User) { Motion.bindAll({}, $scope, 'motions'); Category.bindAll({}, $scope, 'categories'); + Tag.bindAll({}, $scope, 'tags'); + Workflow.bindAll({}, $scope, 'workflows'); User.bindAll({}, $scope, 'users'); + $scope.alert = {}; // setup table sorting $scope.sortColumn = 'identifier'; @@ -369,6 +552,21 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions']) $scope.sortColumn = column; }; + // collect all states of all workflows + // TODO: regard workflows only which are used by motions + $scope.states = []; + var workflows = Workflow.getAll(); + angular.forEach(workflows, function (workflow) { + if (workflows.length > 1) { + var wf = {} + wf.name = "# "+workflow.name; + $scope.states.push(wf); + } + angular.forEach(workflow.states, function (state) { + $scope.states.push(state); + }); + }); + // hover edit actions $scope.hoverIn = function () { $scope.showEditActions = true; @@ -432,24 +630,37 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions']) .controller('MotionDetailCtrl', [ '$scope', + '$http', 'Motion', 'Category', - 'Workflow', + 'Mediafile', + 'Tag', 'User', + 'Workflow', 'motion', - '$http', - function($scope, Motion, Category, Workflow, User, motion, $http) { + function($scope, $http, Motion, Category, Mediafile, Tag, User, Workflow, motion) { Motion.bindOne(motion.id, $scope, 'motion'); Category.bindAll({}, $scope, 'categories'); - Workflow.bindAll({}, $scope, 'workflows'); + Mediafile.bindAll({}, $scope, 'mediafiles'); + Tag.bindAll({}, $scope, 'tags'); User.bindAll({}, $scope, 'users'); + Workflow.bindAll({}, $scope, 'workflows'); Motion.loadRelations(motion, 'agenda_item'); - var state = motion.state; - state.getNextStates() - $scope.alert = {}; // TODO: show alert in template + // TODO: make 'motion.attachments' useable and itteratable in template + // Motion.loadRelations(motion, 'attachments'); - $scope.update_state = function (state_id) { - $http.put('/rest/motions/motion/' + motion.id + '/set_state/', {'state': state_id}); + $scope.alert = {}; + $scope.isCollapsed = true; + + $scope.support = function () { + $http.post('/rest/motions/motion/' + motion.id + '/support/'); + } + $scope.unsupport = function () { + $http.delete('/rest/motions/motion/' + motion.id + '/support/'); + console.log(motion); + } + $scope.update_state = function (state) { + $http.put('/rest/motions/motion/' + motion.id + '/set_state/', {'state': state.id}); } $scope.reset_state = function (state_id) { $http.put('/rest/motions/motion/' + motion.id + '/set_state/', {}); @@ -473,64 +684,123 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions']) votesvalid: poll.votesvalid, votesinvalid: poll.votesinvalid, votescast: poll.votescast + }) + .then(function(success) { + $scope.alert.show = false; + poll.isEditMode = false; + }) + .catch(function(error) { + var message = ''; + for (var e in error.data) { + message += e + ': ' + error.data[e] + ' '; + } + $scope.alert = { type: 'danger', msg: message, show: true }; }); - poll.isEditMode = false; } } ]) -.controller('MotionCreateCtrl', - function($scope, $state, $http, Motion, Agenda, User, Category, Workflow, Tag, Mediafile) { - Agenda.bindAll({}, $scope, 'items'); - User.bindAll({}, $scope, 'users'); - Category.bindAll({}, $scope, 'categories'); - Workflow.bindAll({}, $scope, 'workflows'); - Tag.bindAll({}, $scope, 'tags'); - Mediafile.bindAll({}, $scope, 'mediafiles'); - - $scope.motion = {}; - $scope.save = function (motion) { - Motion.create(motion).then( - function(success) { - $state.go('motions.motion.list'); - } - ); - }; -}) - -.controller('MotionUpdateCtrl', [ +.controller('MotionCreateCtrl', [ '$scope', '$state', - '$http', + 'gettext', 'Motion', - 'Agenda', - 'User', + 'MotionFormFieldFactory', 'Category', - 'Workflow', - 'Tag', + 'Config', 'Mediafile', - 'motion', - function ($scope, $state, $http, Motion, Agenda, User, Category, Workflow, Tag, Mediafile, motion) { - Agenda.bindAll({}, $scope, 'items'); - User.bindAll({}, $scope, 'users'); + 'Tag', + 'User', + 'Workflow', + function($scope, $state, gettext, Motion, MotionFormFieldFactory, Category, Config, Mediafile, Tag, User, Workflow) { Category.bindAll({}, $scope, 'categories'); - Workflow.bindAll({}, $scope, 'workflows'); - Tag.bindAll({}, $scope, 'tags'); Mediafile.bindAll({}, $scope, 'mediafiles'); + Tag.bindAll({}, $scope, 'tags'); + User.bindAll({}, $scope, 'users'); + Workflow.bindAll({}, $scope, 'workflows'); - $scope.motion = motion; - // get latest version for edit - $scope.motion.title = $scope.motion.getTitle(-1); - $scope.motion.text = $scope.motion.getText(-1); - $scope.motion.reason = $scope.motion.getReason(-1); - + // get all form fields + $scope.formFields = MotionFormFieldFactory.getFormFields(); + // override default values for create form + for (var i = 0; i < $scope.formFields.length; i++) { + if ($scope.formFields[i].key == "identifier") { + $scope.formFields[i].hide = true; + } + if ($scope.formFields[i].key == "workflow_id") { + // preselect default workflow + $scope.formFields[i].defaultValue = Config.get('motions_workflow').value; + } + } $scope.save = function (motion) { - Motion.save(motion).then( + Motion.create(motion).then( function(success) { $state.go('motions.motion.list'); } ); }; + + } +]) + +.controller('MotionUpdateCtrl', [ + '$scope', + '$state', + 'gettext', + 'Motion', + 'Category', + 'Config', + 'Mediafile', + 'MotionFormFieldFactory', + 'Tag', + 'User', + 'Workflow', + 'motion', + function($scope, $state, gettext, Motion, Category, Config, Mediafile, MotionFormFieldFactory, Tag, User, Workflow, motion) { + Category.bindAll({}, $scope, 'categories'); + Mediafile.bindAll({}, $scope, 'mediafiles'); + Tag.bindAll({}, $scope, 'tags'); + User.bindAll({}, $scope, 'users'); + Workflow.bindAll({}, $scope, 'workflows'); + + // set initial values for form model + $scope.model = motion; + $scope.model.more = false; + // get all form fields + $scope.formFields = MotionFormFieldFactory.getFormFields(); + // override default values for update form + for (var i = 0; i < $scope.formFields.length; i++) { + if ($scope.formFields[i].key == "identifier") { + // show identifier field + $scope.formFields[i].hide = false; + } + if ($scope.formFields[i].key == "title") { + // get title of latest version + $scope.formFields[i].defaultValue = motion.getTitle(-1); + } + if ($scope.formFields[i].key == "text") { + // get text of latest version + $scope.formFields[i].defaultValue = motion.getText(-1); + } + if ($scope.formFields[i].key == "reason") { + // get reason of latest version + $scope.formFields[i].defaultValue = motion.getReason(-1); + } + if ($scope.formFields[i].key == "workflow_id") { + // get saved workflow id from state + $scope.formFields[i].defaultValue = motion.state.workflow_id; + } + } + + // save form + $scope.save = function (model) { + Motion.save(model) + .then(function(success) { + $state.go('motions.motion.detail', {id: motion.id}); + }) + .catch(function(fallback) { + console.log(fallback); + }); + }; } ]) diff --git a/openslides/motions/static/templates/motions/motion-detail.html b/openslides/motions/static/templates/motions/motion-detail.html index 9cc830743..026610707 100644 --- a/openslides/motions/static/templates/motions/motion-detail.html +++ b/openslides/motions/static/templates/motions/motion-detail.html @@ -6,11 +6,7 @@ -agenda_id: {{ motion.agenda_item }} - -

state: {{ motion.state }} - -

next states: {{ motion.state.getNextStates() }} +
+

Submitters

{{ submitter.get_full_name() }}
+

Supporters

  1. - {{ supporters.get_full_name() }} + {{ supporters.get_full_name() }}
+ + + +

State

- {{ motion.state.name | translate }} - -
+ + {{ motion.state.name | translate }} + +
- + -
-
-
@@ -93,67 +120,85 @@ agenda_id: {{ motion.agenda_item }} class="btn btn-default btn-xs"> -
-
- -
-
- -
- -
-
- -
- -
-
- -
- -
-
- -
- -
-
- -
- -
-
- -
- -
- - -
-
-
+
+
+

+ Special values:
+ -1 = majority
+ -2 = undocumented +

+ + {{alert.msg}} + + +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+ + +
+
+
+
Yes:
-
{{ poll.yes }} {{poll.getYesPercent()}}
+
+ +
+
{{ poll.yes }} {{ poll.getYesPercent() }}
No:
-
{{ poll.no }} {{poll.getNoPercent()}}
+
+ +
+
{{ poll.no }} {{ poll.getNoPercent() }}
-
+
Abstain:
-
{{ poll.abstain }} {{poll.getAbstainPercent()}}
+
+ +
+
{{ poll.abstain }} {{ poll.getAbstainPercent() }}

@@ -161,7 +206,7 @@ agenda_id: {{ motion.agenda_item }} Valid votes:
-
{{ poll.votesvalid }} {{poll.getVotesValidPercent()}}
+
{{ poll.votesvalid }} {{ poll.getVotesValidPercent() }}
@@ -169,20 +214,20 @@ agenda_id: {{ motion.agenda_item }} Invalid votes:
-
{{ poll.votesinvalid }} {{poll.getVotesInvalidPercent()}}
+
{{ poll.votesinvalid }} {{ poll.getVotesInvalidPercent() }}

-
+
Votes cast:
-
{{ poll.votescast }} {{poll.getVotesCastPercent()}}
+
{{ poll.votescast }} {{ poll.getVotesCastPercent() }}
- @@ -191,12 +236,11 @@ agenda_id: {{ motion.agenda_item }} {{ motion.category.name }}

Tags

- - +

+ {{ tag.name }} -   - +

diff --git a/openslides/motions/static/templates/motions/motion-form.html b/openslides/motions/static/templates/motions/motion-form.html index 07b64f0d2..5fb3f33ed 100644 --- a/openslides/motions/static/templates/motions/motion-form.html +++ b/openslides/motions/static/templates/motions/motion-form.html @@ -8,94 +8,14 @@
-
-
- - -
-
- - -
-
- - - - {{ $item.get_full_name() }} - - -
-
-
-
-
- - -
-
- -