Merge pull request #1447 from ostcar/ui-router

use ui router for more generic url routing
This commit is contained in:
Oskar Hahn 2015-02-12 10:09:38 +01:00
commit 85dfbb185f
20 changed files with 338 additions and 246 deletions

View File

@ -8,8 +8,7 @@
"bootstrap": "~3.3.1", "bootstrap": "~3.3.1",
"datatables-bootstrap3-plugin": "~0.2.0", "datatables-bootstrap3-plugin": "~0.2.0",
"angular": "~1.3.11", "angular": "~1.3.11",
"angular-cookies": "~1.3.11", "angular-ui-router": "~0.2.13",
"angular-route": "~1.3.11",
"angular-data": "~1.5.3", "angular-data": "~1.5.3",
"sockjs": "~0.3.4", "sockjs": "~0.3.4",
"jed": "~1.1.0" "jed": "~1.1.0"

View File

@ -1,39 +1,37 @@
angular.module('OpenSlidesApp.agenda', []) angular.module('OpenSlidesApp.agenda', [])
.config(['$routeProvider', function($routeProvider) { .config(function($stateProvider) {
$routeProvider $stateProvider
.when('/agenda', { .state('agenda', {
templateUrl: 'static/templates/agenda/item-list.html', url: '/agenda',
controller: 'ItemListCtrl', abstract: true,
template: "<ui-view/>",
})
.state('agenda.item', {
abstract: true,
template: "<ui-view/>",
})
.state('agenda.item.list', {
resolve: { resolve: {
items: function(Agenda) { items: function(Agenda) {
return Agenda.findAll(); return Agenda.findAll();
} }
} }
}) })
.when('/agenda/new', { .state('agenda.item.create', {})
templateUrl: 'static/templates/agenda/item-form.html', .state('agenda.item.detail', {
controller: 'ItemCreateCtrl'
})
.when('/agenda/:id', {
templateUrl: 'static/templates/agenda/item-detail.html',
controller: 'ItemDetailCtrl',
resolve: { resolve: {
item: function(Agenda, $route) { item: function(Agenda, $stateParams) {
return Agenda.find($route.current.params.id); return Agenda.find($stateParams.id);
} }
} }
}) })
.when('/agenda/:id/edit', { .state('agenda.item.detail.update', {
templateUrl: 'static/templates/agenda/item-form.html', views: {
controller: 'ItemUpdateCtrl', '@agenda.item': {}
resolve: {
item: function(Agenda, $route) {
return Agenda.find($route.current.params.id);
}
} }
}); });
}]) })
.factory('Agenda', function(DS) { .factory('Agenda', function(DS) {
return DS.defineResource({ return DS.defineResource({
@ -48,20 +46,21 @@ angular.module('OpenSlidesApp.agenda', [])
$scope.test_singular = i18n.ngettext('test', 'tests', 1); $scope.test_singular = i18n.ngettext('test', 'tests', 1);
}) })
.controller('ItemDetailCtrl', function($scope, $routeParams, Agenda) { .controller('ItemDetailCtrl', function($scope, Agenda, item) {
Agenda.bindOne($scope, 'item', $routeParams.id); Agenda.bindOne($scope, 'item', item.id);
}) })
.controller('ItemCreateCtrl', function($scope, Agenda) { .controller('ItemCreateCtrl', function($scope, Agenda) {
$scope.item = {}; $scope.item = {};
$scope.save = function (item) { $scope.save = function (item) {
item.weight = 0; // TODO: the rest_api should do this item.weight = 0; // TODO: the rest_api should do this
item.tags = []; // TODO: the rest_api should do this
Agenda.create(item); Agenda.create(item);
// TODO: redirect to list-view // TODO: redirect to list-view
}; };
}) })
.controller('ItemUpdateCtrl', function($scope, $routeParams, Agenda, item) { .controller('ItemUpdateCtrl', function($scope, Agenda, item) {
$scope.item = item; // do not use Agenda.binOne(...) so autoupdate is not activated $scope.item = item; // do not use Agenda.binOne(...) so autoupdate is not activated
$scope.save = function (item) { $scope.save = function (item) {
Agenda.save(item); Agenda.save(item);

View File

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

View File

@ -1,68 +0,0 @@
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,69 @@
angular.module('OpenSlidesApp.assignments', [])
.config(function($stateProvider) {
$stateProvider
.state('assignments', {
url: '/assignment',
abstract: true,
template: "<ui-view/>",
})
.state('assignments.assignment', {
abstract: true,
template: "<ui-view/>",
})
.state('assignments.assignment.list', {
resolve: {
assignments: function(Assignment) {
return Assignment.findAll();
}
}
})
.state('assignments.assignment.create', {})
.state('assignments.assignment.detail', {
controller: 'AssignmentDetailCtrl',
resolve: {
assignment: function(Assignment, $stateParams) {
return Assignment.find($stateParams.id);
}
}
})
.state('assignments.assignment.detail.update', {
views: {
'@assignments.assignment': {}
}
});
})
.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, Assignment, assignment) {
Assignment.bindOne($scope, 'assignment', assignment.id)
})
.controller('AssignmentCreateCtrl', function($scope, Assignment) {
$scope.assignment = {};
$scope.save = function(assignment) {
assignment.posts = 1;
assignment.tags = []; // TODO: the rest_api should do this
Assignment.create(assignment);
// TODO: redirect to list-view
};
})
.controller('AssignmentUpdateCtrl', function($scope, 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

@ -1,7 +0,0 @@
<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,7 @@
<ul>
<li ng-repeat="assignment in assignments">
<a ui-sref="assignments.assignment.detail({id: assignment.id })">{{ assignment.name }}</a>
<a ui-sref="assignments.assignment.detail.update({id: assignment.id })">Bearbeiten</a>
</li>
</ul>
<a ui-sref="assignments.assignment.create">Neu</a>

View File

@ -1,105 +1,20 @@
angular.module('OpenSlidesApp', [ angular.module('OpenSlidesApp', [
'ngRoute', 'ui.router',
'angular-data.DS', 'angular-data.DS',
'ngCookies', 'OpenSlidesApp.core',
'OpenSlidesApp.agenda', 'OpenSlidesApp.agenda',
'OpenSlidesApp.assignment', 'OpenSlidesApp.assignments',
'OpenSlidesApp.user', 'OpenSlidesApp.users',
]) ])
.config(function($routeProvider, $locationProvider) { .config(function($urlRouterProvider, $locationProvider) {
$routeProvider // define fallback url and html5Mode
.when('/', { $urlRouterProvider.otherwise('/');
templateUrl: 'static/templates/dashboard.html'
})
.otherwise({redirectTo: '/'});
$locationProvider.html5Mode(true); $locationProvider.html5Mode(true);
}) })
.run(function($http, $cookies) { .config(function($httpProvider) {
$http.defaults.headers.post['X-CSRFToken'] = $cookies.csrftoken; // Combine the django csrf system with the angular csrf system
$http.defaults.headers.put['X-CSRFToken'] = $cookies.csrftoken; $httpProvider.defaults.xsrfCookieName = 'csrftoken';
}) $httpProvider.defaults.xsrfHeaderName = 'X-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,176 @@
angular.module('OpenSlidesApp.core', [])
.config(function($stateProvider) {
// Use stateProvider.decorator to give default values to our states
$stateProvider.decorator('views', function(state, parent) {
var result = {},
views = parent(state);
if (state.abstract) {
return views;
}
angular.forEach(views, function(config, name) {
// Sets default values for templateUrl
var patterns = state.name.split('.'),
templateUrl,
controller,
defaultControllers = {
create: 'CreateCtrl',
update: 'UpdateCtrl',
list: 'ListCtrl',
detail: 'DetailCtrl',
};
// templateUrl
if (_.last(patterns).match(/(create|update)/)) {
// When state_patterns is in the form "app.module.create" or
// "app.module.update", use the form template.
templateUrl = 'static/templates/' + patterns[0] + '/' + patterns[1] + '-form.html'
} else {
// Replaces the first point through a slash (the app name)
var appName = state.name.replace('.', '/');
// Replaces any folowing points though a -
templateUrl = 'static/templates/' + appName.replace(/\./g, '-') + '.html';
}
config.templateUrl = config.templateUrl || templateUrl;
// controller
if (patterns.length >= 3) {
controller = _.capitalize(patterns[1]) + defaultControllers[_.last(patterns)];
config.controller = config.controller || controller;
}
result[name] = config;
});
return result;
})
.decorator('url', function(state, parent) {
var defaultUrl;
if (state.abstract) {
defaultUrl = '';
} else {
var patterns = state.name.split('.'),
defaultUrls = {
create: '/new',
update: '/edit',
list: '',
detail: '/:id',
};
defaultUrl = defaultUrls[_.last(patterns)];
}
state.url = state.url || defaultUrl;
return parent(state)
});
})
.config(function($stateProvider, $urlRouterProvider, $locationProvider) {
// Core urls
$urlRouterProvider.otherwise('/');
$stateProvider
.state('dashboard', {
url: '/',
templateUrl: 'static/templates/dashboard.html'
});
$locationProvider.html5Mode(true);
})
.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, 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

@ -1,4 +1,4 @@
<h1>Dashboard</h1> <h1>Dashboard</h1>
<a ng-href="assignment">Assignments</a> <a ui-sref="assignments.assignment.list">Assignments</a>
<a ng-href="agenda">Agenda</a> <a ui-sref="agenda.item.list">Agenda</a>
<a ng-href="user">User</a> <a ui-sref="users.user.list">User</a>

View File

@ -78,7 +78,7 @@
<div id="content" class="col-md-10"> <div id="content" class="col-md-10">
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
<div ng-view></div> <div ui-view></div>
</div> </div>
</div> </div>
<hr /> <hr />
@ -94,8 +94,9 @@
<script src="static/js/app.js"></script> <script src="static/js/app.js"></script>
<script src="static/js/core.js"></script>
<script src="static/js/agenda/agenda.js"></script> <script src="static/js/agenda/agenda.js"></script>
<script src="static/js/assignment/assignment.js"></script> <script src="static/js/assignments/assignments.js"></script>
<script src="static/js/user/user.js"></script> <script src="static/js/users/users.js"></script>
</body> </body>
</html> </html>

View File

@ -14,6 +14,11 @@ urlpatterns = patterns(
# TODO: add "special" urls, for example pdf views etc. # TODO: add "special" urls, for example pdf views etc.
url(r'^user.*', IndexView.as_view()), url(r'^user.*', IndexView.as_view()),
# activate next line go get more angular views
# url(r'^$', IndexView.as_view()),
# url(r'^assignment.*', IndexView.as_view()),
# url(r'^agenda.*', IndexView.as_view()),
) )

View File

@ -1,39 +1,37 @@
angular.module('OpenSlidesApp.user', []) angular.module('OpenSlidesApp.users', [])
.config(['$routeProvider', function($routeProvider) { .config(function($stateProvider) {
$routeProvider $stateProvider
.when('/user', { .state('users', {
templateUrl: 'static/templates/user/user-list.html', url: '/user',
controller: 'UserListCtrl', abstract: true,
template: "<ui-view/>",
})
.state('users.user', {
abstract: true,
template: "<ui-view/>",
})
.state('users.user.list', {
resolve: { resolve: {
users: function(User) { users: function(User) {
return User.findAll(); return User.findAll();
} }
} }
}) })
.when('/user/new', { .state('users.user.create', {})
templateUrl: 'static/templates/user/user-form.html', .state('users.user.detail', {
controller: 'UserCreateCtrl'
})
.when('/user/:id', {
templateUrl: 'static/templates/user/user-detail.html',
controller: 'UserDetailCtrl',
resolve: { resolve: {
user: function(User, $route) { user: function(User, $stateParams) {
return User.find($route.current.params.id); return User.find($stateParams.id);
} }
} }
}) })
.when('/user/:id/edit', { .state('users.user.detail.update', {
templateUrl: 'static/templates/user/user-form.html', views: {
controller: 'UserUpdateCtrl', '@users.user': {}
resolve: {
user: function(User, $route) {
return User.find($route.current.params.id);
}
} }
}); });
}]) })
.factory('User', function(DS) { .factory('User', function(DS) {
return DS.defineResource({ return DS.defineResource({
@ -59,7 +57,6 @@ angular.module('OpenSlidesApp.user', [])
}) })
.factory('Group', function(DS) { .factory('Group', function(DS) {
// TODO: the rest api for group does not exist at the moment
return DS.defineResource({ return DS.defineResource({
name: 'users/group', name: 'users/group',
endpoint: '/rest/users/group/' endpoint: '/rest/users/group/'
@ -70,8 +67,8 @@ angular.module('OpenSlidesApp.user', [])
User.bindAll($scope, 'users'); User.bindAll($scope, 'users');
}) })
.controller('UserDetailCtrl', function($scope, $routeParams, User) { .controller('UserDetailCtrl', function($scope, User, user) {
User.bindOne($scope, 'user', $routeParams.id); User.bindOne($scope, 'user', user.id);
}) })
.controller('UserCreateCtrl', function($scope, User) { .controller('UserCreateCtrl', function($scope, User) {
@ -82,7 +79,7 @@ angular.module('OpenSlidesApp.user', [])
}; };
}) })
.controller('UserUpdateCtrl', function($scope, $routeParams, User, user) { .controller('UserUpdateCtrl', function($scope, User, user) {
$scope.user = user; // do not use Agenda.binOne(...) so autoupdate is not activated $scope.user = user; // do not use Agenda.binOne(...) so autoupdate is not activated
$scope.save = function(user) { $scope.save = function(user) {
User.save(user); User.save(user);

View File

@ -1,7 +0,0 @@
<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

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

View File

@ -0,0 +1,7 @@
<ul>
<li ng-repeat="user in users">
<a ui-sref="users.user.detail({id: user.id })">{{ user.get_short_name() }}</a>
<a ui-sref="users.user.detail.update({id: user.id })">{{ gettext('Edit') }}</a>
</li>
</ul>
<a ui-sref="users.user.create">{{ gettext('New') }}</a>

View File

@ -5,7 +5,7 @@ bleach>=1.4,<1.5
django-ckeditor-updated>=4.2.3,<4.4 django-ckeditor-updated>=4.2.3,<4.4
django-haystack>=2.1,<2.4 django-haystack>=2.1,<2.4
django-mptt>=0.6,<0.7 django-mptt>=0.6,<0.7
djangorestframework>=3.0.1,<3.1 djangorestframework>=3.0.1,<3.0.5
jsonfield>=0.9.19,<1.1 jsonfield>=0.9.19,<1.1
natsort>=3.2,<3.6 natsort>=3.2,<3.6
reportlab>=3.0,<3.2 reportlab>=3.0,<3.2