angular single page application

This commit is contained in:
Oskar Hahn 2015-01-30 11:58:36 +01:00
parent afc295b2ec
commit 7171a71919
30 changed files with 1397 additions and 328 deletions

View File

@ -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"
}
}
}

View File

@ -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))

View File

@ -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
};
});

View File

@ -0,0 +1,2 @@
<h1>{{ item.get_title }}</h1>
{{ item.text }}

View File

@ -0,0 +1,8 @@
<h1 ng-if="item.id">{{ item.title }}</h1>
<h1 ng-if="!item.id">Neuer Eintrag</h1>
<form>
Titel: <input type="text" ng-model="item.title"><br>
Text:<br> <textarea ng-model="item.text"></textarea><br>
<input type="submit" ng-click="save(item)" value="Save" />
</form>

View File

@ -0,0 +1,9 @@
<ul>
<li ng-repeat="item in items">
<a ng-href="agenda/{{ item.id }}/">{{ item.get_title }}</a>
<a ng-href="agenda/{{ item.id }}/edit/">Bearbeiten</a>
</li>
</ul>
<a ng-href="agenda/new/">{{ gettext('New') }}</a>
{{ test_singular }}
{{ test_plural }}

View File

@ -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
};
});

View File

@ -0,0 +1,2 @@
<h1>{{ assignment.name }}</h1>
{{ assignment.description }}

View File

@ -0,0 +1,8 @@
<h1 ng-if="assignment.id">{{ assignment.name }}</h1>
<h1 ng-if="!assignment.id">Neue Assignment</h1>
<form>
Titel: <input type="text" ng-model="assignment.name"><br>
Beschreibung:<br> <textarea ng-model="assignment.description"></textarea><br>
<input type="submit" ng-click="save(assignment)" value="Save" />
</form>

View File

@ -0,0 +1,7 @@
<ul>
<li ng-repeat="assignment in assignments">
<a ng-href="assignment/{{ assignment.id }}/">{{ assignment.name }}</a>
<a ng-href="assignment/{{ assignment.id }}/edit/">Bearbeiten</a>
</li>
</ul>
<a ng-href="assignment/new/">Neu</a>

View File

@ -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;
}
}

View File

@ -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/'
});
});

View File

@ -0,0 +1,4 @@
<h1>Dashboard</h1>
<a ng-href="assignment">Assignments</a>
<a ng-href="agenda">Agenda</a>
<a ng-href="user">User</a>

View File

@ -0,0 +1,101 @@
<!DOCTYPE html>
<!--[if lt IE 7]> <html lang="en" ng-app="myApp" class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]> <html lang="en" ng-app="myApp" class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]> <html lang="en" ng-app="myApp" class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!--> <html lang="en" ng-app="OpenSlidesApp" class="no-js"> <!--<![endif]-->
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<base href="/">
<title>OpenSlides</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="static/css/openslides-libs.css">
<link rel="stylesheet" href="static/css/app.css">
<script src="static/js/openslides-libs.js"></script>
</head>
<body>
<!-- Navbar -->
<nav id="header" class="navbar">
<div class="container-fluid">
<div class="navbar-header">
<a href="/" class="logo"><img src="/static/img/logo.png" alt="OpenSlides" /></a>
<span class="title optional">{{ config('event_name') }}</span>
</div>
{% block loginbutton %}
<div class="navbar-right">
<!-- login/logout button -->
<div class="btn-group">
{% if user.is_authenticated %}
<a href="#" data-toggle="dropdown" class="btn btn-default dropdown-toggle">
<span class="glyphicon glyphicon-user" aria-hidden="true"></span>
<span class="optional-small">{{ user.username }}</span>
<span class="caret"></span>
</a>
<ul class="dropdown-menu pull-right">
<li><a href="{% url 'user_settings' %}">
<span class="glyphicon glyphicon-cog" aria-hidden="true"></span>
{{ gettext("Edit profile") }}</a></li>
<li><a href="{% url 'password_change' %}">
<span class="glyphicon glyphicon-lock" aria-hidden="true"></span>
{% trans "Change password" %}</a></li>
<li class="divider"></li>
<li><a href="{% url 'user_logout' %}">
<span class="glyphicon glyphicon-log-out" aria-hidden="true"></span>
{% trans "Logout" %}</a></li>
</ul>
{% else %}
<a href="{% url 'user_login' %}" class="btn btn-default">
<span class="glyphicon glyphicon-log-in" aria-hidden="true"></span>
{{ gettext("Login") }}
</a>
{% endif %}
</div>
</div>
{% endblock %}
</div>
</nav>
<!-- Container -->
<div class="container-fluid" id="container">
<div class="row">
<!-- Sidebar navigation (main menu) -->
<div class="col-md-2 leftmenu lefticon">
<ul>
{% for entry in main_menu_entries %}
<li{% if entry.is_active %} class="active"{% endif %}>
<a href="{{ entry.get_url }}" class="tooltip-right">
<!--TODO-->
<span class="glyphicon {{ entry.get_icon_css_class }}" aria-hidden="true"></span>
<span class="text">{{ entry }}</span>
</a>
</li>
{% endfor %}
</ul>
</div>
<!-- Content -->
<div id="content" class="col-md-10">
<div class="row">
<div class="col-md-12">
<div ng-view></div>
</div>
</div>
<hr />
<footer>
<small>
&copy; Copyright 20112015 | Powered by <a href="http://openslides.org" target="_blank">OpenSlides</a> | <a href="{% url 'core_version' %}">Version</a>
</small>
</footer>
</div><!--/#content-->
</div><!--/.row-->
</div><!--/#container-->
<script src="static/js/app.js"></script>
<script src="static/js/agenda/agenda.js"></script>
<script src="static/js/assignment/assignment.js"></script>
<script src="static/js/user/user.js"></script>
</body>
</html>

View File

@ -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

View File

@ -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 <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\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 ""

View File

@ -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 <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\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 ""

View File

@ -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'),
)

View File

@ -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
};
});

View File

@ -0,0 +1,3 @@
<h1>Persönliche Daten</h1>
{{ user.get_short_name() }}

View File

@ -0,0 +1,8 @@
<h1 ng-if="user.id">{{ user.get_short_name() }}</h1>
<h1 ng-if="!user.id">{{ gettext("New User") }}</h1>
<form>
firstname: <input type="text" ng-model="user.first_name"><br>
lastname: <input type="text" ng-model="user.last_name"><br>
<input type="submit" ng-click="save(user)" value="Save" />
</form>

View File

@ -0,0 +1,7 @@
<ul>
<li ng-repeat="user in users">
<a ng-href="user/{{ user.id }}/">{{ user.get_short_name() }}</a>
<a ng-href="user/{{ user.id }}/edit/">{{ gettext('Edit') }}</a>
</li>
</ul>
<a ng-href="user/new/">{{ gettext('New') }}</a>

View File

@ -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,

View File

@ -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<name>[-\w]+/[-\w]+)/(?P<id>[-\w]+)/$', path)
match = re.match(r'^/rest/(?P<collection>[-\w]+/[-\w]+)/(?P<id>[-\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')

View File

@ -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"
}
}

View File

@ -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)

View File

@ -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):

View File

@ -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)

View File

@ -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')