diff --git a/bower.json b/bower.json index d0082fb0b..1c33da2da 100644 --- a/bower.json +++ b/bower.json @@ -1,10 +1,22 @@ { - "name": "OpenSlides", - "private": true, - "dependencies": { - "bootstrap": "3.3.1", - "datatables-bootstrap3-plugin": "0.2.0", - "jquery": "1.11.2", - "jquery.cookie" : "1.4.1" + "name": "OpenSlides", + "private": true, + "dependencies": { + "lodash": "~3.0.1", + "jquery": "~1.11.2", + "jquery.cookie": "~1.4.1", + "bootstrap": "~3.3.1", + "datatables-bootstrap3-plugin": "~0.2.0", + "angular": "~1.3.11", + "angular-cookies": "~1.3.11", + "angular-route": "~1.3.11", + "angular-data": "~1.5.3", + "sockjs": "~0.3.4", + "jed": "~1.1.0" + }, + "overrides": { + "jed": { + "main": "jed.js" } + } } diff --git a/make/commands.py b/make/commands.py index 7fcd5da7f..e21db86da 100644 --- a/make/commands.py +++ b/make/commands.py @@ -102,3 +102,21 @@ def clear(args=None): call('find -name "*.pyc" -delete') call('find -name "*.orig" -delete') call('find -type d -empty -delete') + +@command('po', + help="Generates the po-file for javascript") +def po(args=None): + # TODO: in the value "" there has to be the entry: + # "plural_forms: nplurals=2; plural=(n != 1);" + call('find openslides/ -iname "*.js" -or -iname "*.html" | ' + 'xargs xgettext --from-code=UTF-8 --language=JavaScript ' + '--output=openslides/locale/en/javascript.po') + + +@argument('-l', '--language') +@command('po2json', + help="Generates json for a translated po file") +def po2json(args=None): + lang = args.language + call('node_modules/.bin/po2json openslides/locale/%s/javascript.po openslides/static/i18n/%s.json' % + (lang, lang)) diff --git a/openslides/agenda/static/js/agenda/agenda.js b/openslides/agenda/static/js/agenda/agenda.js new file mode 100644 index 000000000..4827224e6 --- /dev/null +++ b/openslides/agenda/static/js/agenda/agenda.js @@ -0,0 +1,70 @@ +angular.module('OpenSlidesApp.agenda', []) + +.config(['$routeProvider', function($routeProvider) { + $routeProvider + .when('/agenda', { + templateUrl: 'static/templates/agenda/item-list.html', + controller: 'ItemListCtrl', + resolve: { + items: function(Agenda) { + return Agenda.findAll(); + } + } + }) + .when('/agenda/new', { + templateUrl: 'static/templates/agenda/item-form.html', + controller: 'ItemCreateCtrl' + }) + .when('/agenda/:id', { + templateUrl: 'static/templates/agenda/item-detail.html', + controller: 'ItemDetailCtrl', + resolve: { + item: function(Agenda, $route) { + return Agenda.find($route.current.params.id); + } + } + }) + .when('/agenda/:id/edit', { + templateUrl: 'static/templates/agenda/item-form.html', + controller: 'ItemUpdateCtrl', + resolve: { + item: function(Agenda, $route) { + return Agenda.find($route.current.params.id); + } + } + }); +}]) + +.factory('Agenda', function(DS) { + return DS.defineResource({ + name: 'agenda/item', + endpoint: '/rest/agenda/item/' + }); +}) + +.controller('ItemListCtrl', function($scope, Agenda, i18n) { + Agenda.bindAll($scope, 'items'); + $scope.test_plural = i18n.ngettext('test', 'tests', 2); + $scope.test_singular = i18n.ngettext('test', 'tests', 1); +}) + +.controller('ItemDetailCtrl', function($scope, $routeParams, Agenda) { + Agenda.bindOne($scope, 'item', $routeParams.id); +}) + +.controller('ItemCreateCtrl', function($scope, Agenda) { + $scope.item = {}; + $scope.save = function (item) { + item.weight = 0; // TODO: the rest_api should do this + Agenda.create(item); + // TODO: redirect to list-view + }; +}) + +.controller('ItemUpdateCtrl', function($scope, $routeParams, Agenda, item) { + $scope.item = item; // do not use Agenda.binOne(...) so autoupdate is not activated + $scope.save = function (item) { + Agenda.save(item); + // TODO: redirect to list-view + }; +}); diff --git a/openslides/agenda/static/templates/agenda/item-detail.html b/openslides/agenda/static/templates/agenda/item-detail.html new file mode 100644 index 000000000..09811fd77 --- /dev/null +++ b/openslides/agenda/static/templates/agenda/item-detail.html @@ -0,0 +1,2 @@ +

{{ item.get_title }}

+{{ item.text }} diff --git a/openslides/agenda/static/templates/agenda/item-form.html b/openslides/agenda/static/templates/agenda/item-form.html new file mode 100644 index 000000000..f2330908c --- /dev/null +++ b/openslides/agenda/static/templates/agenda/item-form.html @@ -0,0 +1,8 @@ +

{{ item.title }}

+

Neuer Eintrag

+ +
+ Titel:
+ Text:

+ +
diff --git a/openslides/agenda/static/templates/agenda/item-list.html b/openslides/agenda/static/templates/agenda/item-list.html new file mode 100644 index 000000000..bb2485b15 --- /dev/null +++ b/openslides/agenda/static/templates/agenda/item-list.html @@ -0,0 +1,9 @@ + +{{ gettext('New') }} +{{ test_singular }} +{{ test_plural }} diff --git a/openslides/assignment/static/js/assignment/assignment.js b/openslides/assignment/static/js/assignment/assignment.js new file mode 100644 index 000000000..9e4f2a219 --- /dev/null +++ b/openslides/assignment/static/js/assignment/assignment.js @@ -0,0 +1,68 @@ +angular.module('OpenSlidesApp.assignment', []) + +.config(['$routeProvider', function($routeProvider) { + $routeProvider + .when('/assignment', { + templateUrl: 'static/templates/assignment/assignment-list.html', + controller: 'AssignmentListCtrl', + resolve: { + assignments: function(Assignment) { + return Assignment.findAll(); + } + } + }) + .when('/assignment/new', { + templateUrl: 'static/templates/assignment/assignment-form.html', + controller: 'AssignmentCreateCtrl' + }) + .when('/assignment/:id', { + templateUrl: 'static/templates/assignment/assignment-detail.html', + controller: 'AssignmentDetailCtrl', + resolve: { + assignment: function(Assignment, $route) { + return Assignment.find($route.current.params.id); + } + } + }) + .when('/assignment/:id/edit', { + templateUrl: 'static/templates/assignment/assignment-form.html', + controller: 'AssignmentUpdateCtrl', + resolve: { + assignment: function(Assignment, $route) { + return Assignment.find($route.current.params.id); + } + } + }); +}]) + +.factory('Assignment', function(DS) { + return DS.defineResource({ + name: 'assignment/assignment', + endpoint: '/rest/assignment/assignment/' + }); +}) + +.controller('AssignmentListCtrl', function($scope, Assignment) { + Assignment.bindAll($scope, 'assignments'); +}) + +.controller('AssignmentDetailCtrl', function($scope, $routeParams, Assignment) { + Assignment.bindOne($scope, 'assignment', $routeParams.id) +}) + +.controller('AssignmentCreateCtrl', function($scope, Assignment) { + $scope.assignment = {}; + $scope.save = function(assignment) { + Assignment.create(assignment); + // TODO: redirect to list-view + }; +}) + +.controller('AssignmentUpdateCtrl', function($scope, $routeParams, Assignment, assignment) { + $scope.assignment = assignment; // do not use .binOne(...) so autoupdate is not activated + $scope.save = function (assignment) { + Assignment.save(assignment); + // TODO: redirect to list-view + }; +}); + diff --git a/openslides/assignment/static/templates/assignment/assignment-detail.html b/openslides/assignment/static/templates/assignment/assignment-detail.html new file mode 100644 index 000000000..3b2050169 --- /dev/null +++ b/openslides/assignment/static/templates/assignment/assignment-detail.html @@ -0,0 +1,2 @@ +

{{ assignment.name }}

+{{ assignment.description }} diff --git a/openslides/assignment/static/templates/assignment/assignment-form.html b/openslides/assignment/static/templates/assignment/assignment-form.html new file mode 100644 index 000000000..9e8e15f5f --- /dev/null +++ b/openslides/assignment/static/templates/assignment/assignment-form.html @@ -0,0 +1,8 @@ +

{{ assignment.name }}

+

Neue Assignment

+ +
+ Titel:
+ Beschreibung:

+ +
diff --git a/openslides/assignment/static/templates/assignment/assignment-list.html b/openslides/assignment/static/templates/assignment/assignment-list.html new file mode 100644 index 000000000..404e8344a --- /dev/null +++ b/openslides/assignment/static/templates/assignment/assignment-list.html @@ -0,0 +1,7 @@ + +Neu diff --git a/openslides/core/static/css/app.css b/openslides/core/static/css/app.css new file mode 100644 index 000000000..ce7c324b3 --- /dev/null +++ b/openslides/core/static/css/app.css @@ -0,0 +1,376 @@ +/* + * OpenSlides default template styles of the web interface + */ + +body { + background-color: #FBFBFB; +} + +/* Header */ +#header { + background-color: #333333; + background-image: -moz-linear-gradient(top, #444444, #222222); + background-image: -ms-linear-gradient(top, #444444, #222222); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#444444), to(#222222)); + background-image: -webkit-linear-gradient(top, #444444, #222222); + background-image: -o-linear-gradient(top, #444444, #222222); + background-image: linear-gradient(top, #444444, #222222); + box-shadow: 0 0 7px rgba(0,0,0,0.6); + border-radius: 0; + height: 35px; + margin-bottom: 20px; + padding: 7px 20px 0; + position: relative; +} +#header .logo img { + height: 30px; + padding-left: 3px; +} +#header .title { + font-size: 16px; + color: #999999; + position: absolute; + margin: 8px 0 0 50px; +} +#searchform { + margin-top: 0px; +} + +footer { + margin-bottom: 20px; +} + +/* Headings and Links */ +h1 { + border-bottom: 1px solid #EEEEEE; + margin: 0px 0 30px; + padding-bottom: 9px; +} +a:hover { + text-decoration: none; +} + +/* Login page */ +#login-page.container { + width: 320px; + margin-top: 20px; +} +#login-page h2 { + margin-left: 30px; + margin-bottom: 20px; +} +#login-page h2 img { + width: 250px; +} +#login-page .well { + background-color: white; + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 6px 6px 6px 6px; + box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); + margin-top: 20px; + padding-bottom: 20px; + line-height: 45px; +} + +/* Log */ +#log { + padding-left: 14px; +} + +/** Utils **/ +tr.offline td, li.offline { + background-color: #EAEAEA !important; +} +tr.activeline td, li.activeline { + background-color: #bed4de !important; +} +.nopadding { + padding: 0; +} +.alert form { + margin-bottom: 0; +} +tr.total td { + border-top: 1px solid #333333; +} +.nobr { + white-space: nowrap; +} +.right { + float: right; +} +.indentation { + margin-left: 12px; +} +.mini_width { + width: 1px; +} +/* show optional column */ +.optional { + display: auto; +} +.user_details fieldset { + margin-bottom: 10px; +} +.user_details legend { + margin-bottom: 5px; +} +.user_details label { + font-weight: bold; + margin: 10px 0 0 0; +} +.user_details label:after { + content: ":"; +} + +/** Colors **/ +.grey { + color: grey; +} + +/** Forms **/ +input, textarea { + width: 320px; +} +.small-form input { + width: 55px; +} +.normal-form input { + width: 320px; +} +textarea { + height: 100px; +} +.help-inline { + font-size: 11px; +} +.errorlist{ + margin: 0; +} +.errorlist li { + list-style: none outside none; +} +form .required label:after { + content: " *"; +} +legend + .control-group { + margin-top: 0px !important; +} +#id_permissions { + height: 310px; + width: auto; +} +#id_users { + height: 110px; + width: auto; +} +#dataTable_filter input { + width: auto; +} +#dataTable { + clear: none; +} +#dataTable_wrapper .row-fluid:after { + clear: none; +} +.searchresults li { + margin-bottom: 15px; +} +.searchresults li .app { + color: #999999; +} +.highlighted { + font-weight: bold; +} + +/* ckeditor plugin insertpre: textarea style */ +table.cke_dialog_contents textarea { + font-family: monospace !important; +} + + +/** Left sidebar navigation **/ +.leftmenu ul { + margin: 0; + padding: 0; + list-style: none; +} +.leftmenu ul ul { + display: none; + margin-left: 35px; + margin-top: -1px; + box-shadow: 0 1px 2px rgba(0,0,0,0.1); +} +.leftmenu ul li { + display: block; + width: 100%; + line-height: 30px; +} +.leftmenu ul li a { + border-style: none solid solid; + border-width: 0 1px 1px; + border-color: #dddddd; + color: #666666; + display: block; + font-weight: bold; + background-color: #ffffff; + padding: 0; +} +.leftmenu ul li:first-child a { + border-top: 1px solid #DDDDDD; +} +.leftmenu ul li a .glyphicon { + display: inline-block; + background: #f9f9f9; + padding: 8px 10px 6px; + margin: 0 5px 0 0; + border-right: 1px solid #dddddd; +} +.leftmenu ul li a, .leftmenu ul li a .glyphicon { + -webkit-transition: background 0.2s ease-in-out; + -moz-transition: background 0.2s ease-in-out; + -ms-transition: background 0.2s ease-in-out; + -o-transition: background 0.2s ease-in-out; + transition: background 0.2s ease-in-out; +} +.leftmenu ul li a:hover { + background-color: #f5f5f5; + color: #000000; +} +.leftmenu ul li a:hover .glyphicon { + background-color: #efefef; +} +.leftmenu ul li.active a { + background-color: #333333; + color: #ffffff; +} +.leftmenu ul li.active a .glyphicon { + background-color: #333333; + border-right: 1px solid #444444; +} +.leftmenu ul li.hider a { + margin-top: 5px; + height: 20px; +} +.leftmenu.lefticon > ul { + width: 37px !important; +} +.leftmenu.lefticon ul ul { + position: absolute; + z-index: 20; + margin-top: -34px; +} +.leftmenu.lefticon > ul > li > a > span.text { + display: none; +} +.leftmenu.lefticon ul ul > li > a { + min-width: 200px !important; +} +.leftmenu.lefticon span.text { + padding-right: 15px; +} + +/** Icons **/ +/* TODO: Move some of them to the respective apps. */ +.icon-assignment { + background: url("../img/glyphicons_041_charts.png") no-repeat !important; + width: 25px; + margin-left: 10px !important; +} +.leftmenu ul li.active a .glyphicon.icon-assignment { + background-image: url("../img/glyphicons_041_charts_white.png") !important; +} +.status_link .icon-on, .icon-checked-new { + background-image: url("../img/glyphicons_152_check.png"); + background-position: 0; +} +.icon-checked-new_white { + background-image: url("../img/glyphicons_152_check_white.png"); + background-position: 0; +} +.status_link .icon-off, .icon-unchecked-new { + background-image: url("../img/glyphicons_153_unchecked.png"); + background-position: 0; +} +.icon-summary { + background-image: url("../img/glyphicons_154_more_windows.png"); + background-position: 0; +} +.icon-summary.icon-white { + background-image: url("../img/glyphicons_154_more_windows_white.png"); + background-position: 0; +} +.icon-login { + background-image: url("../img/glyphicons_044_keys.png"); + background-position: 0; +} +.icon-group { + background-image: url("../img/glyphicons_043_group.png"); + background-position: 0; +} +.icon-import { + background-image: url("../img/glyphicons_358_file_import.png"); + background-position: 0; +} +.icon-delete { + background-image: url("../img/glyphicons_256_delete.png"); + background-position: 0; +} +.icon-add-user { + background-image: url("../img/glyphicons_006_user_add.png"); + background-position: 0; +} +.icon-clock { + background-image: url("../img/glyphicons_054_clock.png"); + background-position: 0; +} +.icon-speaker { + background-image: url("../img/glyphicons_300_microphone.png"); + background-position: 0; +} + +/** Responsive **/ +@media (max-width: 767px) { + body { + padding: 0; + } + .row-fluid .leftmenu { + float: left; + width: auto; + } + #content { + margin: 0 5px 0 45px; + width: auto; + } + /* hide optional column */ + .optional, #searchform #id_q { + display: none; + } + #searchform button { + border-radius: 4px; + } +} + +@media (max-width: 480px) { + body { + padding: 0; + } + .row-fluid .leftmenu { + float: left; + width: auto; + } + #header { + padding: 7px 5px 0; + } + #content { + margin: 0 5px 0 45px; + width: auto; + } + /* hide optional column */ + .optional, .optional-small{ + display: none; + } + #menu-overview .manage, .agenda_list .manage { + width: 50px !important; + } +} diff --git a/openslides/core/static/js/app.js b/openslides/core/static/js/app.js new file mode 100644 index 000000000..ad815ab02 --- /dev/null +++ b/openslides/core/static/js/app.js @@ -0,0 +1,105 @@ +angular.module('OpenSlidesApp', [ + 'ngRoute', + 'angular-data.DS', + 'ngCookies', + 'OpenSlidesApp.agenda', + 'OpenSlidesApp.assignment', + 'OpenSlidesApp.user', +]) + +.config(function($routeProvider, $locationProvider) { + $routeProvider + .when('/', { + templateUrl: 'static/templates/dashboard.html' + }) + .otherwise({redirectTo: '/'}); + $locationProvider.html5Mode(true); +}) + +.run(function($http, $cookies) { + $http.defaults.headers.post['X-CSRFToken'] = $cookies.csrftoken; + $http.defaults.headers.put['X-CSRFToken'] = $cookies.csrftoken; +}) + +.run(function(DS, autoupdate) { + autoupdate.on_message(function(data) { + // TODO: when MODEL.find() is called after this + // a new request is fired. This could be a bug in DS + if (data.status_code == 200) { + DS.inject(data.collection, data.data) + } + // TODO: handle other statuscodes + }); +}) + +.run(function($rootScope, i18n) { + // Puts the gettext methods into each scope. + // Uses the methods that are known by xgettext by default. + methods = ['gettext', 'dgettext', 'dcgettext', 'ngettext', 'dngettext', + 'pgettext', 'dpgettext']; + _.forEach(methods, function(method) { + $rootScope[method] = _.bind(i18n[method], i18n); + }); +}) + +.run(function($rootScope, Config) { + // Puts the config object into each scope. + // TODO: maybe rootscope.config has to set before findAll() is finished + Config.findAll().then(function() {; + $rootScope.config = function(key) { + return Config.get(key).value; + } + }); +}) + +.factory('autoupdate', function() { + //TODO: use config here + var url = "http://" + location.host + "/sockjs"; + + var Autoupdate = { + socket: null, + message_receivers: [], + connect: function() { + var autoupdate = this; + this.socket = new SockJS(url); + + this.socket.onmessage = function(event) { + _.forEach(autoupdate.message_receivers, function(receiver) { + receiver(event.data); + }); + } + + this.socket.onclose = function() { + setTimeout(autoupdate.connect, 5000); + } + }, + on_message: function(receiver) { + this.message_receivers.push(receiver); + } + }; + Autoupdate.connect(); + return Autoupdate; +}) + +.factory('i18n', function($http) { + // TODO: there is a bug(?) in jed. I had to call val_idx++; in line 285 + // TODO: make the language variable and changeable at runtime + var i18n = new Jed({ + 'domain': 'de', + 'locale_data': {'de': {"": {}}}, + }); // TODO: use promise here + $http.get('/static/i18n/de.json') + .success(function(data) { + // TODO: check data. + i18n.options.locale_data['de'] = data; + }); + return i18n; +}) + +.factory('Config', function(DS) { + return DS.defineResource({ + name: 'config/config', + idAttribute: 'key', + endpoint: '/rest/config/config/' + }); +}); diff --git a/openslides/core/static/templates/dashboard.html b/openslides/core/static/templates/dashboard.html new file mode 100644 index 000000000..8ea14ece0 --- /dev/null +++ b/openslides/core/static/templates/dashboard.html @@ -0,0 +1,4 @@ +

Dashboard

+Assignments +Agenda +User diff --git a/openslides/core/static/templates/index.html b/openslides/core/static/templates/index.html new file mode 100644 index 000000000..e1872c894 --- /dev/null +++ b/openslides/core/static/templates/index.html @@ -0,0 +1,101 @@ + + + + + + + + + + OpenSlides + + + + + + + + + + +
+
+ + +
+ +
+ + +
+
+
+
+
+
+
+ +
+ +
+
+ + + + + + + + diff --git a/openslides/core/views.py b/openslides/core/views.py index 90a6150a1..45fd3f86b 100644 --- a/openslides/core/views.py +++ b/openslides/core/views.py @@ -1,5 +1,6 @@ from django.conf import settings from django.contrib import messages +from django.contrib.staticfiles import finders from django.core.exceptions import PermissionDenied from django.core.urlresolvers import reverse from django.db import IntegrityError @@ -8,6 +9,7 @@ from django.template import RequestContext from django.utils.importlib import import_module from django.utils.translation import ugettext as _ from haystack.views import SearchView as _SearchView +from django.http import HttpResponse from openslides import get_version as get_openslides_version from openslides import get_git_commit_id, RELEASE @@ -24,6 +26,21 @@ from .exceptions import TagException from .serializers import CustomSlideSerializer, TagSerializer +class IndexView(utils_views.View): + """ + The primary view for OpenSlides using AngularJS. + + The default base template is 'openslides/core/static/templates/index.html'. + You can override it by simply adding a custom 'templates/index.html' file + to the custom staticfiles directory. See STATICFILES_DIRS in settings.py. + """ + + def get(self, *args, **kwargs): + with open(finders.find('templates/index.html')) as f: + content = f.read() + return HttpResponse(content) + + class DashboardView(utils_views.AjaxMixin, utils_views.TemplateView): """ Overview over all possible slides, the overlays and a live view: the diff --git a/openslides/locale/de/javascript.po b/openslides/locale/de/javascript.po new file mode 100644 index 000000000..e517fab8c --- /dev/null +++ b/openslides/locale/de/javascript.po @@ -0,0 +1,220 @@ +# Language file of OpenSlides used by transifex: +# https://www.transifex.com/projects/p/openslides/ + +msgid "" +msgstr "" +"Project-Id-Version: 2.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2015-02-01 11:55+0100\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: 8bit\n" +"plural_forms: nplurals=2; plural=(n != 1);" + +#: openslides/agenda/static/js/agenda/agenda.js:51 +msgid "test" +msgid_plural "tests" +msgstr[0] "test-de" +msgstr[1] "tests-de" + +#: openslides/agenda/static/js/agenda.js:14 +#, javascript-format +msgid ", of which %s are hidden." +msgstr "" + +#: openslides/agenda/static/templates/agenda/item-list.html:7 +msgid "New" +msgstr "Neu" + +#: openslides/core/static/js/jquery/datepicker-config.js:2 +#: openslides/core/static/js/jquery/datepicker-config.js:32 +msgid "en" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:3 +msgid "previous month" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:4 +msgid "next month" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:6 +msgid "January" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:6 +msgid "February" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:6 +msgid "March" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:7 +msgid "April" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:7 +#: openslides/core/static/js/jquery/datepicker-config.js:13 +msgid "May" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:7 +msgid "June" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:8 +msgid "July" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:8 +msgid "August" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:8 +msgid "September" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:9 +msgid "October" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:9 +msgid "November" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:9 +msgid "December" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:12 +msgid "Jan" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:12 +msgid "Feb" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:12 +msgid "Mar" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:13 +msgid "Apr" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:13 +msgid "Jun" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:14 +msgid "Jul" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:14 +msgid "Aug" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:14 +msgid "Sep" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:15 +msgid "Oct" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:15 +msgid "Nov" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:15 +msgid "Dec" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:18 +msgid "Sunday" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:18 +msgid "Monday" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:18 +msgid "Tuesday" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:18 +msgid "Wednesday" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:19 +msgid "Thursday" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:19 +msgid "Friday" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:19 +msgid "Saturday" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:22 +#: openslides/core/static/js/jquery/datepicker-config.js:26 +msgid "Su" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:22 +#: openslides/core/static/js/jquery/datepicker-config.js:26 +msgid "Mo" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:22 +#: openslides/core/static/js/jquery/datepicker-config.js:26 +msgid "Tu" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:22 +#: openslides/core/static/js/jquery/datepicker-config.js:26 +msgid "We" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:23 +#: openslides/core/static/js/jquery/datepicker-config.js:27 +msgid "Th" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:23 +#: openslides/core/static/js/jquery/datepicker-config.js:27 +msgid "Fr" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:23 +#: openslides/core/static/js/jquery/datepicker-config.js:27 +msgid "Sa" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:38 +msgid "Time" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:39 +msgid "Hour" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:40 +msgid "Minute" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:41 +msgid "Current time" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:42 +msgid "Close" +msgstr "" diff --git a/openslides/locale/en/javascript.po b/openslides/locale/en/javascript.po new file mode 100644 index 000000000..18c00c475 --- /dev/null +++ b/openslides/locale/en/javascript.po @@ -0,0 +1,220 @@ +# Language file of OpenSlides used by transifex: +# https://www.transifex.com/projects/p/openslides/ + +msgid "" +msgstr "" +"Project-Id-Version: 2.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2015-02-01 11:55+0100\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: 8bit\n" +"plural_forms: nplurals=2; plural=(n != 1);" + +#: openslides/agenda/static/js/agenda/agenda.js:51 +msgid "test" +msgid_plural "tests" +msgstr[0] "" +msgstr[1] "" + +#: openslides/agenda/static/js/agenda.js:14 +#, javascript-format +msgid ", of which %s are hidden." +msgstr "" + +#: openslides/agenda/static/templates/agenda/item-list.html:7 +msgid "New" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:2 +#: openslides/core/static/js/jquery/datepicker-config.js:32 +msgid "en" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:3 +msgid "previous month" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:4 +msgid "next month" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:6 +msgid "January" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:6 +msgid "February" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:6 +msgid "March" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:7 +msgid "April" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:7 +#: openslides/core/static/js/jquery/datepicker-config.js:13 +msgid "May" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:7 +msgid "June" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:8 +msgid "July" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:8 +msgid "August" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:8 +msgid "September" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:9 +msgid "October" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:9 +msgid "November" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:9 +msgid "December" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:12 +msgid "Jan" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:12 +msgid "Feb" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:12 +msgid "Mar" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:13 +msgid "Apr" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:13 +msgid "Jun" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:14 +msgid "Jul" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:14 +msgid "Aug" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:14 +msgid "Sep" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:15 +msgid "Oct" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:15 +msgid "Nov" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:15 +msgid "Dec" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:18 +msgid "Sunday" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:18 +msgid "Monday" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:18 +msgid "Tuesday" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:18 +msgid "Wednesday" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:19 +msgid "Thursday" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:19 +msgid "Friday" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:19 +msgid "Saturday" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:22 +#: openslides/core/static/js/jquery/datepicker-config.js:26 +msgid "Su" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:22 +#: openslides/core/static/js/jquery/datepicker-config.js:26 +msgid "Mo" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:22 +#: openslides/core/static/js/jquery/datepicker-config.js:26 +msgid "Tu" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:22 +#: openslides/core/static/js/jquery/datepicker-config.js:26 +msgid "We" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:23 +#: openslides/core/static/js/jquery/datepicker-config.js:27 +msgid "Th" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:23 +#: openslides/core/static/js/jquery/datepicker-config.js:27 +msgid "Fr" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:23 +#: openslides/core/static/js/jquery/datepicker-config.js:27 +msgid "Sa" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:38 +msgid "Time" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:39 +msgid "Hour" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:40 +msgid "Minute" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:41 +msgid "Current time" +msgstr "" + +#: openslides/core/static/js/jquery/datepicker-config.js:42 +msgid "Close" +msgstr "" diff --git a/openslides/urls.py b/openslides/urls.py index 31191e2ae..7867b62f4 100644 --- a/openslides/urls.py +++ b/openslides/urls.py @@ -1,23 +1,24 @@ -from django.conf import settings from django.conf.urls import include, patterns, url -from openslides.core.views import ErrorView -from openslides.utils.plugins import get_urlpatterns +from openslides.core.views import IndexView, ErrorView from openslides.utils.rest_api import router handler403 = ErrorView.as_view(status_code=403) handler404 = ErrorView.as_view(status_code=404) handler500 = ErrorView.as_view(status_code=500) -urlpatterns = [] +urlpatterns = patterns( + '', + url(r'^rest/', include(router.urls)), + # TODO: add "special" urls, for example pdf views etc. + url(r'^user.*', IndexView.as_view()), +) + + +# Deprecated. js_info_dict = {'packages': []} -for plugin in settings.INSTALLED_PLUGINS: - plugin_urlpatterns = get_urlpatterns(plugin) - if plugin_urlpatterns: - urlpatterns += plugin_urlpatterns - js_info_dict['packages'].append(plugin) urlpatterns += patterns( '', @@ -32,13 +33,6 @@ urlpatterns += patterns( (r'^ckeditor/', include('ckeditor.urls')), ) -urlpatterns += patterns( - '', - url(r'^api/', include(router.urls)), - # TODO: Remove the next line if you are sure. - # url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')) -) - # TODO: move this patterns into core or the participant app from openslides.users.views import UserSettingsView, UserPasswordSettingsView urlpatterns += patterns( @@ -53,11 +47,11 @@ urlpatterns += patterns( 'django.contrib.auth.views.logout_then_login', name='user_logout'), - url(r'^usersettings/$', + url(r'^myusersettings/$', UserSettingsView.as_view(), name='user_settings'), - url(r'^usersettings/changepassword/$', + url(r'^myusersettings/changepassword/$', UserPasswordSettingsView.as_view(), name='password_change'), ) diff --git a/openslides/users/static/js/user/user.js b/openslides/users/static/js/user/user.js new file mode 100644 index 000000000..eba4e3a08 --- /dev/null +++ b/openslides/users/static/js/user/user.js @@ -0,0 +1,91 @@ +angular.module('OpenSlidesApp.user', []) + +.config(['$routeProvider', function($routeProvider) { + $routeProvider + .when('/user', { + templateUrl: 'static/templates/user/user-list.html', + controller: 'UserListCtrl', + resolve: { + users: function(User) { + return User.findAll(); + } + } + }) + .when('/user/new', { + templateUrl: 'static/templates/user/user-form.html', + controller: 'UserCreateCtrl' + }) + .when('/user/:id', { + templateUrl: 'static/templates/user/user-detail.html', + controller: 'UserDetailCtrl', + resolve: { + user: function(User, $route) { + return User.find($route.current.params.id); + } + } + }) + .when('/user/:id/edit', { + templateUrl: 'static/templates/user/user-form.html', + controller: 'UserUpdateCtrl', + resolve: { + user: function(User, $route) { + return User.find($route.current.params.id); + } + } + }); +}]) + +.factory('User', function(DS) { + return DS.defineResource({ + name: 'users/user', + endpoint: '/rest/users/user/', + methods: { + get_short_name: function() { + // should be the same as in the python user model. + var firstName = _.trim(this.first_name), + lastName = _.trim(this.last_name), + name; + + if (firstName && lastName) { + // TODO: check config + name = [firstName, lastName].join(' '); + } else { + name = firstName || lastName || this.username; + } + return name; + }, + } + }); +}) + +.factory('Group', function(DS) { + // TODO: the rest api for group does not exist at the moment + return DS.defineResource({ + name: 'users/group', + endpoint: '/rest/users/group/' + }); +}) + +.controller('UserListCtrl', function($scope, User, i18n) { + User.bindAll($scope, 'users'); +}) + +.controller('UserDetailCtrl', function($scope, $routeParams, User) { + User.bindOne($scope, 'user', $routeParams.id); +}) + +.controller('UserCreateCtrl', function($scope, User) { + $scope.user = {}; + $scope.save = function (user) { + User.create(user); + // TODO: redirect to list-view + }; +}) + +.controller('UserUpdateCtrl', function($scope, $routeParams, User, user) { + $scope.user = user; // do not use Agenda.binOne(...) so autoupdate is not activated + $scope.save = function (user) { + User.save(user); + // TODO: redirect to list-view + }; +}); diff --git a/openslides/users/static/templates/user/user-detail.html b/openslides/users/static/templates/user/user-detail.html new file mode 100644 index 000000000..9ed8dee2d --- /dev/null +++ b/openslides/users/static/templates/user/user-detail.html @@ -0,0 +1,3 @@ +

Persönliche Daten

+{{ user.get_short_name() }} + diff --git a/openslides/users/static/templates/user/user-form.html b/openslides/users/static/templates/user/user-form.html new file mode 100644 index 000000000..dc6afa301 --- /dev/null +++ b/openslides/users/static/templates/user/user-form.html @@ -0,0 +1,8 @@ +

{{ user.get_short_name() }}

+

{{ gettext("New User") }}

+ +
+ firstname:
+ lastname:
+ +
diff --git a/openslides/users/static/templates/user/user-list.html b/openslides/users/static/templates/user/user-list.html new file mode 100644 index 000000000..c3cf7075f --- /dev/null +++ b/openslides/users/static/templates/user/user-list.html @@ -0,0 +1,7 @@ + +{{ gettext('New') }} diff --git a/openslides/utils/autoupdate.py b/openslides/utils/autoupdate.py index 5b4a854d8..841bce4a0 100644 --- a/openslides/utils/autoupdate.py +++ b/openslides/utils/autoupdate.py @@ -101,9 +101,14 @@ class OpenSlidesSockJSConnection(SockJSConnection): # Send out internal HTTP request to get data from the REST api. for waiter in cls.waiters: # Read waiter's former cookies and parse session cookie to new header object. - session_cookie = waiter.connection_info.cookies[settings.SESSION_COOKIE_NAME] headers = HTTPHeaders() - headers.add('Cookie', '%s=%s' % (settings.SESSION_COOKIE_NAME, session_cookie.value)) + try: + session_cookie = waiter.connection_info.cookies[settings.SESSION_COOKIE_NAME] + except KeyError: + # There is no session cookie + pass + else: + headers.add('Cookie', '%s=%s' % (settings.SESSION_COOKIE_NAME, session_cookie.value)) # Setup uncompressed request. request = HTTPRequest( url=url, diff --git a/openslides/utils/rest_api.py b/openslides/utils/rest_api.py index a57c7f7cc..262774042 100644 --- a/openslides/utils/rest_api.py +++ b/openslides/utils/rest_api.py @@ -45,7 +45,7 @@ def get_collection_and_id_from_url(url): Raises OpenSlidesError if the URL is invalid. """ path = urlparse(url).path - match = re.match(r'^/api/(?P[-\w]+/[-\w]+)/(?P[-\w]+)/$', path) + match = re.match(r'^/rest/(?P[-\w]+/[-\w]+)/(?P[-\w]+)/$', path) if not match: raise OpenSlidesError('Invalid REST api URL: %s' % url) - return match.group('name'), match.group('id') + return match.group('collection'), match.group('id') diff --git a/package.json b/package.json index a196eb79a..3923e7480 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,15 @@ { - "name": "OpenSlides", - "private": true, - "devDependencies": { - "bower": "~1.3.12", - "gulp": "~3.8.10", - "gulp-concat": "~2.4.3", - "gulp-if": "~1.2.5", - "gulp-minify-css": "~0.3.11", - "gulp-uglify": "~1.0.2", - "main-bower-files": "~2.4.1", - "yargs": "~1.3.3" - } + "name": "OpenSlides", + "private": true, + "devDependencies": { + "bower": "~1.3.12", + "gulp": "~3.8.10", + "gulp-concat": "~2.4.3", + "gulp-if": "~1.2.5", + "gulp-minify-css": "~0.3.11", + "gulp-uglify": "~1.0.2", + "main-bower-files": "~2.4.1", + "yargs": "~1.3.3", + "po2json": "~0.3.2" + } } diff --git a/tests/integration/users/__init__.py b/tests/integration/users/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/integration/users/test_views.py b/tests/integration/users/test_views.py deleted file mode 100644 index f978768d5..000000000 --- a/tests/integration/users/test_views.py +++ /dev/null @@ -1,14 +0,0 @@ -from openslides.utils.test import TestCase -from django.test.client import Client - - -class UserViews(TestCase): - def setUp(self): - self.client = Client() - self.client.login(username='admin', password='admin') - - def test_user_list(self): - response = self.client.get('/user/') - - self.assertTemplateUsed(response, 'users/user_list.html') - self.assertEqual(response.status_code, 200) diff --git a/tests/old/plugin_api/test_plugin_api.py b/tests/old/plugin_api/test_plugin_api.py index df4951ec2..3e0d58ee6 100644 --- a/tests/old/plugin_api/test_plugin_api.py +++ b/tests/old/plugin_api/test_plugin_api.py @@ -1,8 +1,5 @@ -from imp import reload - from django.test.client import Client from django.test.utils import override_settings -from django.core.urlresolvers import clear_url_caches from openslides.utils.test import TestCase @@ -19,13 +16,6 @@ class TestPluginOne(TestCase): self.assertContains(response, '(Short description of test plugin Sah9aiQuae5hoocai7ai)') self.assertContains(response, '– Version test_version_string_MoHonepahfofiree6Iej') - def test_url_patterns(self): - from openslides import urls - reload(urls) - clear_url_caches() - response = self.admin_client.get('/test_plugin_one_url_Eexea4nie1fexaax3oX7/') - self.assertRedirects(response, '/version/') - @override_settings(INSTALLED_PLUGINS=('tests.old.plugin_api.test_plugin_two',)) class TestPluginTwo(TestCase): diff --git a/tests/old/users/test_umlaut_user.py b/tests/old/users/test_umlaut_user.py deleted file mode 100644 index c245c434b..000000000 --- a/tests/old/users/test_umlaut_user.py +++ /dev/null @@ -1,40 +0,0 @@ -from django.test.client import Client -from django.test.utils import override_settings - -from openslides.users.models import Group, User -from openslides.utils.test import TestCase - - -@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.PBKDF2PasswordHasher',)) -class TestUmlautUser(TestCase): - """ - Tests persons with umlauts in there name. - """ - - def setUp(self): - self.user = User.objects.create_user('äöü', 'äöü') - self.client = Client() - self.client.login(username='äöü', password='äöü') - - def test_login(self): - client = Client() - response = client.post('/login/', {'username': 'äöü', - 'password': 'äöüß'}) - self.assertEqual(response.status_code, 200) - - response = client.post('/login/', {'username': 'äöü', - 'password': 'äöü'}) - self.assertEqual(response.status_code, 302) - - def test_logout(self): - response = self.client.get('/logout/') - self.assertEqual(response.status_code, 302) - - def test_permission(self): - response = self.client.get('/user/1/edit/') - self.assertEqual(response.status_code, 403) - - self.user.groups.add(Group.objects.get(pk=4)) - - response = self.client.get('/user/1/edit/') - self.assertEqual(response.status_code, 200) diff --git a/tests/old/users/test_views.py b/tests/old/users/test_views.py deleted file mode 100644 index c1f439423..000000000 --- a/tests/old/users/test_views.py +++ /dev/null @@ -1,223 +0,0 @@ -import re - -from django.contrib.auth.models import Permission -from django.contrib.contenttypes.models import ContentType -from django.test.client import Client - -from openslides.config.api import config -from openslides.users.api import get_registered_group -from openslides.users.models import Group, User -from openslides.utils.test import TestCase - - -class UserViews(TestCase): - """ - Tests some views for users. - """ - def setUp(self): - self.admin = User.objects.get(pk=1) - self.client = Client() - self.client.login(username='admin', password='admin') - - def test_create(self): - response = self.client.get('/user/new/') - - self.assertTemplateUsed(response, 'users/user_form.html') - self.assertContains(response, 'New user') - response = self.client.post('/user/new/', {'first_name': 'test_name_ho8hui2niz4nohSupahb'}) - self.assertRedirects(response, '/user/') - - def test_create_multiple(self): - response = self.client.get('/user/new_multiple/') - self.assertTemplateUsed(response, 'users/user_form_multiple.html') - self.assertContains(response, 'New multiple users') - self.assertEqual(User.objects.count(), 1) - block = ('first_name_ksdjfhkjsdhf75utgeitrten last_name_khonizt958zh8fh\n' - 'first_name_1_bmgnf7z8ru first_name_2_kjc98vivt last_name_dfg76kjkjuibv') - response = self.client.post('/user/new_multiple/', - {'users_block': block}) - self.assertEqual(User.objects.count(), 3) - - def test_update(self): - response = self.client.get('/user/1/edit/') - - self.assertTemplateUsed(response, 'users/user_form.html') - self.assertContains(response, 'Edit user') - - response = self.client.post( - '/user/1/edit/', - {'username': 'test_name_unaewae5Ir0saijeac2I', - 'first_name': 'test_name_aJi5jaizaVingaeF3Ohj', - 'groups': '4', - 'is_active': 'yes'}) - - self.assertRedirects(response, '/user/') - - def test_activate(self): - response = self.client.get('/user/1/status/activate/') - self.assertEqual(response.status_code, 302) - - def test_reset_password(self): - self.admin.default_password = new_password = 'password_ohweleeh1Shee5wibo1I' - self.admin.save() - self.client.post('/user/1/reset_password/', {'yes': 'yes'}) - self.assertTrue(self.client.login(username='admin', password=new_password)) - - -class GroupViews(TestCase): - """ - Tests the detail view for groups and later also the other views. - """ - def setUp(self): - self.user_1 = User.objects.get(pk=1) - self.user_1.first_name = 'admins_first_name' - self.user_1.save() - - self.user_2 = User.objects.create(last_name='uquahx3Wohtieph9baer', - first_name='aWei4ien6Se0vie0xeiv', - username='aWei4ien6Se0vie0xeiv uquahx3Wohtieph9baer') - self.delegate = Group.objects.get(pk=3) - self.user_1.groups.add(self.delegate) - self.user_2.groups.add(self.delegate) - - self.client = Client() - self.client.login(username='admin', password='admin') - - def test_detail(self): - response = self.client.get('/user/group/3/') - pattern = r'Administrator, admins_first_name|uquahx3Wohtieph9baer, aWei4ien6Se0vie0xeiv' - match = re.findall(pattern, response.content.decode('utf8')) - self.assertEqual(match[0], 'Administrator, admins_first_name') - self.assertEqual(match[1], 'uquahx3Wohtieph9baer, aWei4ien6Se0vie0xeiv') - - config['users_sort_users_by_first_name'] = True - self.assertTrue(config['users_sort_users_by_first_name']) - response = self.client.get('/user/group/3/') - pattern = r'admins_first_name Administrator|aWei4ien6Se0vie0xeiv uquahx3Wohtieph9baer' - match = re.findall(pattern, response.content.decode('utf8')) - self.assertEqual(match[1], 'admins_first_name Administrator') - self.assertEqual(match[0], 'aWei4ien6Se0vie0xeiv uquahx3Wohtieph9baer') - - def test_create(self): - response = self.client.get('/user/group/new/') - - self.assertTemplateUsed(response, 'users/group_form.html') - self.assertContains(response, 'New group') - - response = self.client.post('/user/group/new/', {'name': 'test_group_name_Oeli1aeXoobohv8eikai'}) - - self.assertRedirects(response, '/user/group/') - - def test_update(self): - response = self.client.get('/user/group/1/edit/') - - self.assertTemplateUsed(response, 'users/group_form.html') - self.assertContains(response, 'Edit group') - - response = self.client.post('/user/group/1/edit/', {'name': 'test_group_name_ahFeicoz5jedie4Fop0U'}) - - self.assertRedirects(response, '/user/group/') - - -class LockoutProtection(TestCase): - """ - Tests that a manager user can not lockout himself by doing - something that removes his last permission to manage users. Tests - also that he can see the user app (although there is no absolute - protection). - """ - def setUp(self): - self.user = User.objects.get(pk=1) - self.user.groups.add(Group.objects.get(pk=4)) - self.client = Client() - self.client.login(username='admin', password='admin') - self.assertEqual(User.objects.count(), 1) - self.assertEqual(Group.objects.count(), 4) - self.assertFalse(self.user.is_superuser) - - def test_delete_yourself(self): - response = self.client.get('/user/1/del/') - self.assertRedirects(response, '/user/1/') - self.assertTrue('You can not delete yourself.' in response.cookies['messages'].value) - response = self.client.post('/user/1/del/', - {'yes': 'yes'}) - self.assertTrue('You can not delete yourself.' in response.cookies['messages'].value) - self.assertRedirects(response, '/user/') - self.assertEqual(User.objects.count(), 1) - - def test_delete_last_manager_group(self): - response = self.client.get('/user/group/4/del/') - self.assertRedirects(response, '/user/group/4/') - self.assertTrue('You can not delete the last group containing the permission ' - 'to manage users you are in.' in response.cookies['messages'].value) - response = self.client.post('/user/group/4/del/', - {'yes': 'yes'}) - self.assertTrue('You can not delete the last group containing the permission ' - 'to manage users you are in.' in response.cookies['messages'].value) - self.assertRedirects(response, '/user/group/') - self.assertEqual(Group.objects.count(), 4) - - def test_remove_user_from_last_manager_group_via_UserUpdateView(self): - response = self.client.post('/user/1/edit/', - {'username': 'arae0eQu8eeghoogeik0', - 'groups': '3'}) - self.assertFormError( - response=response, - form='form', - field=None, - errors='You can not remove the last group containing the permission to manage users.') - - def test_remove_user_from_last_manager_group_via_GroupUpdateView(self): - User.objects.get_or_create(username='foo', pk=2) - response = self.client.post('/user/group/4/edit/', - {'name': 'ChaeFaev4leephaiChae', - 'users': '2'}) - self.assertFormError( - response=response, - form='form', - field=None, - errors='You can not remove yourself from the last group containing the permission to manage users.') - - def test_remove_perm_from_last_manager_group(self): - response = self.client.post('/user/group/4/edit/', - {'name': 'ChaeFaev4leephaiChae', - 'users': '1', - 'permissions': []}) - self.assertFormError( - response=response, - form='form', - field=None, - errors='You can not remove the permission to manage users from the last group you are in.') - - def test_remove_permission_user_can_see_name_from_registered(self): - self.assertTrue(self.user.has_perm('users.can_see_name')) - # Remove perm from registered group - can_see_perm = Permission.objects.get( - content_type=ContentType.objects.get(app_label='users', model='user'), - codename='can_see_name') - get_registered_group().permissions.remove(can_see_perm) - # Reload user - self.user = User.objects.get(pk=1) - self.assertTrue(self.user.has_perm('users.can_see_name')) - - -class TestUserSettings(TestCase): - def setUp(self): - self.admin = User.objects.get(pk=1) - self.admin_client = Client() - self.admin_client.login(username='admin', password='admin') - - def test_get(self): - response = self.admin_client.get('/usersettings/') - self.assertEqual(response.status_code, 200) - - def test_post(self): - response = self.admin_client.post('/usersettings/', { - 'username': 'new_name', - 'language': 'de'}) - - self.assertRedirects(response, '/usersettings/') - - admin = User.objects.get(pk=1) - - self.assertEqual(admin.username, 'new_name')