Small changes for multiple projector feature.

This commit is contained in:
Norman Jäckel 2016-09-29 15:32:58 +02:00
parent e6b9b21d41
commit 8427ffd816
19 changed files with 112 additions and 133 deletions

View File

@ -20,7 +20,7 @@ Core:
- Added support for big assemblies with lots of users.
- Added HTML support for messages on the projector.
- Moved custom slides to own app "topics". Renamed it to "Topic".
- Added support for multiple projectors
- Added support for multiple projectors.
Motions:
- Added origin field.

View File

@ -251,7 +251,7 @@ angular.module('OpenSlidesApp.agenda.site', ['OpenSlidesApp.agenda'])
var isAgendaProjectedId = $scope.isAgendaProjected($scope.mainListTree);
if (isAgendaProjectedId > 0) {
// Deactivate
$http.post('/rest/core/projector/' + isAgendaProjectedId + '/prune_elements/', []);
$http.post('/rest/core/projector/' + isAgendaProjectedId + '/clear_elements/');
}
if (isAgendaProjectedId != projectorId) {
$http.post('/rest/core/projector/' + projectorId + '/prune_elements/',
@ -332,7 +332,7 @@ angular.module('OpenSlidesApp.agenda.site', ['OpenSlidesApp.agenda'])
if (projectiondefaultItem) {
$scope.defaultProjectorItemId = projectiondefaultItem.projector_id;
}
var projectiondefaultListOfSpeakers = ProjectionDefault.filter({name: 'list_of_speakers'})[0];
var projectiondefaultListOfSpeakers = ProjectionDefault.filter({name: 'agenda_list_of_speakers'})[0];
if (projectiondefaultListOfSpeakers) {
$scope.defaultProjectorListOfSpeakersId = projectiondefaultListOfSpeakers.projector_id;
}
@ -515,16 +515,11 @@ angular.module('OpenSlidesApp.agenda.site', ['OpenSlidesApp.agenda'])
}, function() {
$scope.projectors = Projector.getAll();
$scope.updateCurrentListOfSpeakers();
});
$scope.$watch(function () {
return Projector.lastModified();
}, function () {
var projectiondefault = ProjectionDefault.filter({name: 'current_list_of_speakers'})[0];
var projectiondefault = ProjectionDefault.filter({name: 'agenda_current_list_of_speakers'})[0];
if (projectiondefault) {
$scope.defaultProjectorId = projectiondefault.projector_id;
}
});
$scope.updateCurrentListOfSpeakers = function () {
var itemPromise = CurrentListOfSpeakersItem.getItem($scope.currentListOfSpeakersReference);
if (itemPromise) {
@ -539,7 +534,7 @@ angular.module('OpenSlidesApp.agenda.site', ['OpenSlidesApp.agenda'])
var isCurrentLoSProjectedId = $scope.isCurrentLoSProjected($scope.mainListTree);
if (isCurrentLoSProjectedId > 0) {
// Deactivate
$http.post('/rest/core/projector/' + isCurrentLoSProjectedId + '/prune_elements/', []);
$http.post('/rest/core/projector/' + isCurrentLoSProjectedId + '/clear_elements/');
}
if (isCurrentLoSProjectedId != projectorId) {
$http.post('/rest/core/projector/' + projectorId + '/prune_elements/',

View File

@ -13,7 +13,7 @@ def name_default_projector(apps, schema_editor):
Set the name of the default projector to 'Defaultprojector'
"""
Projector = apps.get_model('core', 'Projector')
Projector.objects.filter(pk=1).update(name='Defaultprojector')
Projector.objects.filter(pk=1).update(name='Default projector')
class Migration(migrations.Migration):

View File

@ -179,18 +179,19 @@ class Projector(RESTModelMixin, models.Model):
class ProjectionDefault(RESTModelMixin, models.Model):
"""
Model for the ProjectionDefaults like Motion, Agenda, List of speakers,...
The name is the technical name like 'topics', 'motions'. For apps the name should
be the app name to get keep the ProjectionDefault for apps generic. But it is
possible to give some special name like 'list_of_speakers'.
The display_name is the shown name on the front end for the user.
Model for the projection defaults like motions, agenda, list of
speakers and thelike. The name is the technical name like 'topics' or
'motions'. For apps the name should be the app name to get keep the
ProjectionDefault for apps generic. But it is possible to give some
special name like 'list_of_speakers'. The display_name is the shown
name on the front end for the user.
"""
name = models.CharField(max_length=256)
display_name = models.CharField(max_length=256)
projector = models.ForeignKey(
'Projector',
Projector,
on_delete=models.CASCADE,
related_name='projectiondefaults')

View File

@ -40,8 +40,8 @@ class ProjectorSerializer(ModelSerializer):
class Meta:
model = Projector
fields = ('id', 'config', 'elements', 'scale', 'scroll', 'name', 'blank', 'width', 'height', 'projectiondefaults')
read_only_fields = ('scale', 'scroll', 'blank', 'width', 'height', 'projectiondefaults')
fields = ('id', 'config', 'elements', 'scale', 'scroll', 'name', 'blank', 'width', 'height', 'projectiondefaults', )
read_only_fields = ('scale', 'scroll', 'blank', 'width', 'height', )
class TagSerializer(ModelSerializer):

View File

@ -27,18 +27,20 @@ def delete_django_app_permissions(sender, **kwargs):
def create_builtin_projection_defaults(**kwargs):
"""
Creates the builtin defaults:
- agenda_all_items, agenda_item
- agenda_all_items, agenda_list_of_speakers, agenda_current_list_of_speakers
- topics
- assignments
- mediafiles
- motion
- users
- list_of_speakers
- current_list_of_speakers
"""
# Check whether ProjectionDefaults exists
These strings have to be used in the controllers where you want to
define a projector button. Use the string to get the id of the
responsible projector and pass this id to the projector button directive.
"""
# Check whether ProjectionDefault objects exist.
if ProjectionDefault.objects.all().exists():
# Do completely nothing if the defaults are already in the database.
# Do completely nothing if some defaults are already in the database.
return
default_projector = Projector.objects.get(pk=1)
@ -52,11 +54,11 @@ def create_builtin_projection_defaults(**kwargs):
display_name='Topics',
projector=default_projector)
ProjectionDefault.objects.create(
name='list_of_speakers',
name='agenda_list_of_speakers',
display_name='List of speakers',
projector=default_projector)
ProjectionDefault.objects.create(
name='current_list_of_speakers',
name='agenda_current_list_of_speakers',
display_name='Current list of speakers',
projector=default_projector)
ProjectionDefault.objects.create(

View File

@ -192,10 +192,12 @@ angular.module('OpenSlidesApp.core', [
autoupdate.onMessage(function(json) {
// TODO: when MODEL.find() is called after this
// a new request is fired. This could be a bug in DS
// TODO: If you don't have the permission to see a projector, the
// variable json is a string with an error message. Therefor
// the next line fails.
var dataList = JSON.parse(json);
var dataList = [];
try {
dataList = JSON.parse(json);
} catch(err) {
console.error(json);
}
_.forEach(dataList, function(data) {
console.log("Received object: " + data.collection + ", " + data.id);
var instance = DS.get(data.collection, data.id);
@ -313,15 +315,17 @@ angular.module('OpenSlidesApp.core', [
}
])
// This places a Projectorbutton in the document. Example:
// <projector-button model="motion" default-projector.id="defPrId" additional-id="2"
// content="{{ 'project' | translate }}"></projector-button>
//
// This button references to model (in this case 'motion'). Also a defaultProjectionId has to
// be given. In the Exable its a scope variable. The next two parameters are additional:
// - additional-id: Then the model.project and model.isProjected will be called whith this
// argument (ex.: model.project(2))
// - content: A not trusted text placed behind the projector symbol.
/*
* This places a projector button in the document.
*
* Example: <projector-button model="motion" default-projector.id="defPrId"
* additional-id="2" content="{{ 'project' | translate }}"></projector-button>
* This button references to model (in this example 'motion'). Also a defaultProjectionId
* has to be given. In the example it's a scope variable. The next two parameters are additional:
* - additional-id: Then the model.project and model.isProjected will be called with
* this argument (e. g.: model.project(2))
* - content: A text placed behind the projector symbol.
*/
.directive('projectorButton', [
'Projector',
function (Projector) {
@ -370,7 +374,7 @@ angular.module('OpenSlidesApp.core', [
// if this object is already projected on projectorId, delete this element from this projector
var isProjectedId = this.isProjected();
if (isProjectedId > 0) {
$http.post('/rest/core/projector/' + isProjectedId + '/prune_elements/');
$http.post('/rest/core/projector/' + isProjectedId + '/clear_elements/');
}
// if it was the same projector before, just delete it but not show again
if (isProjectedId != projectorId) {
@ -483,7 +487,7 @@ angular.module('OpenSlidesApp.core', [
},
getStateForCurrentSlide: function () {
var return_dict;
$.each(this.elements, function(key, value) {
angular.forEach(this.elements, function(key, value) {
if (value.name == 'agenda/list-of-speakers') {
return_dict = {
'state': 'agenda.item.detail',
@ -592,11 +596,13 @@ angular.module('OpenSlidesApp.core', [
}
])
// This filter filters all items in array. If the filterArray is empty, the array is passed.
// The filterArray contains numbers of the multiselect: [1, 3, 4].
// Then, all items in array are passed, if the item_id (get with id_function) matches one of the
// ids in filterArray. id_function could also return a list of ids. Example:
// Item 1 has two tags with ids [1, 4]. filterArray = [3, 4] --> match
/*
* This filter filters all items in an array. If the filterArray is empty, the
* array is passed. The filterArray contains numbers of the multiselect, e. g. [1, 3, 4].
* Then, all items in the array are passed, if the item_id (get with id_function) matches
* one of the ids in filterArray. id_function could also return a list of ids. Example:
* Item 1 has two tags with ids [1, 4]. filterArray == [3, 4] --> match
*/
.filter('SelectMultipleFilter', [
function () {
return function (array, filterArray, idFunction) {

View File

@ -60,10 +60,11 @@ angular.module('OpenSlidesApp.core.projector', ['OpenSlidesApp.core'])
.controller('ProjectorContainerCtrl', [
'$scope',
'$location',
'gettext',
'loadGlobalData',
'Projector',
'ProjectorID',
function($scope, $location, loadGlobalData, Projector, ProjectorID) {
function($scope, $location, gettext, loadGlobalData, Projector, ProjectorID) {
loadGlobalData();
$scope.projector_id = ProjectorID();
@ -73,17 +74,15 @@ angular.module('OpenSlidesApp.core.projector', ['OpenSlidesApp.core'])
$scope.$watch(function () {
return Projector.lastModified($scope.projector_id);
}, function () {
Projector.find($scope.projector_id).then(function (projector) {
var projector = Projector.get($scope.projector_id)
if (projector) {
$scope.error = '';
$scope.projectorWidth = projector.width;
$scope.projectorHeight = projector.height;
$scope.recalculateIframe();
}, function (error) {
if (error.status == 404) {
$scope.error = 'Projector not found.';
} else if (error.status == 403) {
$scope.error = 'You have to login to see the projector.';
}
});
} else {
$scope.error = gettext('Can not open the projector.');
}
});
// recalculate the actual Iframesize and scale
@ -164,34 +163,31 @@ angular.module('OpenSlidesApp.core.projector', ['OpenSlidesApp.core'])
$scope.$watch(function () {
return Config.lastModified('projector_broadcast');
}, function () {
Config.findAll().then(function () {
var bc = Config.get('projector_broadcast').value;
if ($scope.broadcast != bc) {
$scope.broadcast = bc;
if ($scope.broadcastDeregister) {
// revert to original $scope.projector
$scope.broadcastDeregister();
$scope.broadcastDeregister = null;
setElements($scope.projector);
$scope.blank = $scope.projector.blank;
var bc = Config.get('projector_broadcast').value;
if ($scope.broadcast != bc) {
$scope.broadcast = bc;
if ($scope.broadcastDeregister) {
// revert to original $scope.projector
$scope.broadcastDeregister();
$scope.broadcastDeregister = null;
setElements($scope.projector);
$scope.blank = $scope.projector.blank;
}
}
if ($scope.broadcast > 0) {
// get elements and blank from broadcast projector
$scope.broadcastDeregister = $scope.$watch(function () {
return Projector.lastModified($scope.broadcast);
}, function () {
if ($scope.broadcast > 0) {
// var broadcast_projector = Projector.get($scope.broadcast);
Projector.find($scope.broadcast).then(function (broadcast_projector) {
setElements(broadcast_projector);
$scope.blank = broadcast_projector.blank;
});
}
}
if ($scope.broadcast > 0) {
// get elements and blank from broadcast projector
$scope.broadcastDeregister = $scope.$watch(function () {
return Projector.lastModified($scope.broadcast);
}, function () {
if ($scope.broadcast > 0) {
// var broadcast_projector = Projector.get($scope.broadcast);
Projector.find($scope.broadcast).then(function (broadcast_projector) {
setElements(broadcast_projector);
$scope.blank = broadcast_projector.blank;
});
}
});
}
});
});
}
});
$scope.$on('$destroy', function() {

View File

@ -1616,7 +1616,7 @@ angular.module('OpenSlidesApp.core.site', [
};
$scope.removeIdentifierMessages = function () {
Projector.getAll().forEach(function (projector) {
$.each(projector.elements, function (uuid, value) {
angular.forEach(projector.elements, function (uuid, value) {
if (value.name == 'core/message' && value.type == 'identify') {
$http.post('/rest/core/projector/' + projector.id + '/deactivate_elements/', [uuid]);
}

View File

@ -56,7 +56,7 @@
<li ng-repeat="projectiondefault in projectiondefaults | orderBy:'id'"
ng-click="setProjectionDefault(projector, projectiondefault)">
<i class="fa fa-check" ng-if="projectiondefault.projector_id === projector.id"></i>
<translate>{{ projectiondefault.display_name }}</translate>
{{ projectiondefault.display_name | translate }}
</li>
</ul>
</div>

View File

@ -20,7 +20,7 @@
ng-class="{ 'projected': (model.isProjected(additionalId) == projector.id) }">
<i class="fa fa-video-camera" ng-show="model.isProjected(additionalId) == projector.id"></i>
{{ projector.name }}
<span ng-if="defaultProjectorId == projector.id">(<translate>Standard</translate>)</span>
<span ng-if="defaultProjectorId == projector.id">(<translate>Default</translate>)</span>
</a>
</li>
</ul>

View File

@ -18,7 +18,6 @@ from django.utils.timezone import now
from openslides import __version__ as version
from openslides.utils import views as utils_views
from openslides.utils.autoupdate import inform_changed_data
from openslides.utils.plugins import (
get_plugin_description,
get_plugin_verbose_name,
@ -190,23 +189,25 @@ class ProjectorViewSet(ModelViewSet):
result = self.get_access_permissions().check_permissions(self.request.user)
elif self.action == 'metadata':
result = self.request.user.has_perm('core.can_see_projector')
elif self.action in ('activate_elements', 'prune_elements', 'update_elements',
'deactivate_elements', 'clear_elements', 'control_view',
'set_resolution', 'set_scroll', 'control_blank', 'destroy',
'create', 'update', 'broadcast', 'set_projectiondefault'):
elif self.action in (
'create', 'update', 'partial_update', 'destroy',
'activate_elements', 'prune_elements', 'update_elements', 'deactivate_elements', 'clear_elements',
'control_view', 'set_resolution', 'set_scroll', 'control_blank', 'broadcast',
'set_projectiondefault',
):
result = (self.request.user.has_perm('core.can_see_projector') and
self.request.user.has_perm('core.can_manage_projector'))
else:
result = False
return result
# Assign all projectionDefaults from this projector to the default projector (pk=1)
# Assign all ProjectionDefault objects from this projector to the default projector (pk=1).
def destroy(self, *args, **kwargs):
projector_instance = self.get_object()
for a in ProjectionDefault.objects.all():
if a.projector.id == projector_instance.id:
a.projector = Projector.objects.get(pk=1)
a.save()
for projection_default in ProjectionDefault.objects.all():
if projection_default.projector.id == projector_instance.id:
projection_default.projector_id = 1
projection_default.save()
return super(ProjectorViewSet, self).destroy(*args, **kwargs)
@detail_route(methods=['post'])
@ -486,8 +487,6 @@ class ProjectorViewSet(ModelViewSet):
"""
if config['projector_broadcast'] == 0:
config['projector_broadcast'] = pk
projector_instance = self.get_object()
inform_changed_data(projector_instance)
message = "Setting projector {id} as broadcast projector was successful.".format(
id=pk)
else:

View File

@ -158,7 +158,7 @@ angular.module('OpenSlidesApp.mediafiles.site', ['ngFileUpload', 'OpenSlidesApp.
$scope.showMediafile = function (projectorId, mediafile) {
var isProjectedId = mediafile.isProjected();
if (isProjectedId > 0) {
$http.post('/rest/core/projector/' + isProjectedId + '/prune_elements/', []);
$http.post('/rest/core/projector/' + isProjectedId + '/clear_elements/');
}
if (isProjectedId != projectorId) {
var postUrl = '/rest/core/projector/' + projectorId + '/prune_elements/';
@ -177,9 +177,6 @@ angular.module('OpenSlidesApp.mediafiles.site', ['ngFileUpload', 'OpenSlidesApp.
}
};
// To avoid some kind of 60,000000000001% in template
$scope.round = function (val) {return Math.round(val);};
var sendMediafileCommand = function (mediafile, data) {
var updateData = _.extend({}, mediafile);
_.extend(updateData, data);

View File

@ -196,7 +196,7 @@
<td ng-show="!isDeleteMode"
os-perms="core.can_manage_projector">
<div class="btn-group" style="min-width:54px;" uib-dropdown
ng-if="mediafile.mediafile.is_presentable"
ng-if="mediafile.is_presentable"
uib-tooltip="{{ 'Projektor' | translate }} {{ mediafile.isProjected() }}"
tooltip-enable="mediafile.isProjected() > 0">
<button type="button" class="btn btn-default btn-sm"

View File

@ -79,9 +79,7 @@ angular.module('OpenSlidesApp.motions.projector', ['OpenSlidesApp.motions'])
}
};
Motion.find(id).then(function (motion) {
$scope.motion = motion;
});
Motion.bindOne(id, $scope, 'motion');
User.bindAll({}, $scope, 'users');
}

View File

@ -1217,7 +1217,6 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions', 'OpenSlid
$scope.$watch(function () {
return Projector.lastModified();
}, function () {
console.log(ProjectionDefault.getAll());
$scope.defaultProjectorId = ProjectionDefault.filter({name: 'motions'})[0].projector_id;
});
$scope.version = motion.active_version;

View File

@ -64,7 +64,7 @@ def ws_add_projector(message, projector_id):
"""
user = message.user
# user is the django anonymous user. We have our own.
if user.is_anonymous:
if user.is_anonymous and config['general_systen_enable_anonymous']:
user = AnonymousUser()
if not user.has_perm('core.can_see_projector'):
@ -140,12 +140,9 @@ def send_data(message):
broadcast_id = config['projector_broadcast']
if broadcast_id > 0:
projectors = Projector.objects.all() # also the broadcasted projector should get data
broadcast_projector = Projector.objects.get(pk=broadcast_id)
projectors = Projector.objects.all() # Also the broadcasted projector should get his data
send_all = True
# The data from the broadcasted projector
broadcast_projector_data = get_projector_element_data(broadcast_projector)
broadcast_projector_data = get_projector_element_data(Projector.objects.get(pk=broadcast_id))
else:
broadcast_projector_data = None

View File

@ -14,7 +14,6 @@ from rest_framework.routers import DefaultRouter
from rest_framework.serializers import ModelSerializer as _ModelSerializer
from rest_framework.serializers import ( # noqa
MANY_RELATION_KWARGS,
BooleanField,
CharField,
DictField,
Field,

View File

@ -26,22 +26,12 @@ class ProjectorAPI(TestCase):
response = self.client.get(reverse('projector-detail', args=['1']))
content = json.loads(response.content.decode())
del content['projectiondefaults']
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(content, {
'id': 1,
'elements': {
'aae4a07b26534cfb9af4232f361dce73':
{'id': topic.id,
'uuid': 'aae4a07b26534cfb9af4232f361dce73',
'name': 'topics/topic'}},
'scale': 0,
'scroll': 0,
'name': 'Defaultprojector',
'blank': False,
'width': 1024,
'height': 768})
self.assertEqual(content['elements'], {
'aae4a07b26534cfb9af4232f361dce73':
{'id': topic.id,
'uuid': 'aae4a07b26534cfb9af4232f361dce73',
'name': 'topics/topic'}})
def test_invalid_slide_on_default_projector(self):
self.client.login(username='admin', password='admin')