Moved custom slides to own app topics for better app structure.
Renamed model to Topic. Added migrations file. Fixed #2402.
This commit is contained in:
parent
53c4932171
commit
cab53f0434
@ -18,6 +18,7 @@ Assignments:
|
||||
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".
|
||||
|
||||
Motions:
|
||||
- Added origin field.
|
||||
|
@ -68,10 +68,6 @@ angular.module('OpenSlidesApp.agenda.site', ['OpenSlidesApp.agenda'])
|
||||
url: '/sort',
|
||||
controller: 'AgendaSortCtrl',
|
||||
})
|
||||
.state('agenda.item.import', {
|
||||
url: '/import',
|
||||
controller: 'AgendaImportCtrl',
|
||||
})
|
||||
.state('agenda.current-list-of-speakers', {
|
||||
url: '/speakers',
|
||||
controller: 'ListOfSpeakersViewCtrl',
|
||||
@ -100,10 +96,10 @@ angular.module('OpenSlidesApp.agenda.site', ['OpenSlidesApp.agenda'])
|
||||
'operator',
|
||||
'ngDialog',
|
||||
'Agenda',
|
||||
'CustomslideForm',
|
||||
'TopicForm', // TODO: Remove this dependency. Use template hook for "New" and "Import" buttons.
|
||||
'AgendaTree',
|
||||
'Projector',
|
||||
function($scope, $filter, $http, $state, DS, operator, ngDialog, Agenda, CustomslideForm, AgendaTree, Projector) {
|
||||
function($scope, $filter, $http, $state, DS, operator, ngDialog, Agenda, TopicForm, AgendaTree, Projector) {
|
||||
// Bind agenda tree to the scope
|
||||
$scope.$watch(function () {
|
||||
return Agenda.lastModified();
|
||||
@ -125,11 +121,12 @@ angular.module('OpenSlidesApp.agenda.site', ['OpenSlidesApp.agenda'])
|
||||
};
|
||||
|
||||
// check open permission
|
||||
// TODO: Use generic solution here.
|
||||
$scope.isAllowedToSeeOpenLink = function (item) {
|
||||
var collection = item.content_object.collection;
|
||||
switch (collection) {
|
||||
case 'core/customslide':
|
||||
return operator.hasPerms('core.can_manage_projector');
|
||||
case 'topics/topic':
|
||||
return operator.hasPerms('agenda.can_see');
|
||||
case 'motions/motion':
|
||||
return operator.hasPerms('motions.can_see');
|
||||
case 'assignments/assignment':
|
||||
@ -138,9 +135,9 @@ angular.module('OpenSlidesApp.agenda.site', ['OpenSlidesApp.agenda'])
|
||||
return false;
|
||||
}
|
||||
};
|
||||
// open new dialog
|
||||
// open dialog for new topics // TODO Remove this. Don't forget import button in template.
|
||||
$scope.newDialog = function () {
|
||||
ngDialog.open(CustomslideForm.getDialog());
|
||||
ngDialog.open(TopicForm.getDialog());
|
||||
};
|
||||
// cancel QuickEdit mode
|
||||
$scope.cancelQuickEdit = function (item) {
|
||||
@ -185,7 +182,7 @@ angular.module('OpenSlidesApp.agenda.site', ['OpenSlidesApp.agenda'])
|
||||
});
|
||||
}
|
||||
};
|
||||
// delete selected items only if items are customslides
|
||||
// delete selected items
|
||||
$scope.deleteMultiple = function () {
|
||||
angular.forEach($scope.items, function (item) {
|
||||
if (item.selected) {
|
||||
@ -401,161 +398,16 @@ angular.module('OpenSlidesApp.agenda.site', ['OpenSlidesApp.agenda'])
|
||||
}
|
||||
])
|
||||
|
||||
.controller('AgendaImportCtrl', [
|
||||
'$scope',
|
||||
'gettext',
|
||||
'Agenda',
|
||||
'Customslide',
|
||||
function($scope, gettext, Agenda, Customslide) {
|
||||
// import from textarea
|
||||
$scope.importByLine = function () {
|
||||
if ($scope.itemlist) {
|
||||
$scope.titleItems = $scope.itemlist[0].split("\n");
|
||||
$scope.importcounter = 0;
|
||||
$scope.titleItems.forEach(function(title, index) {
|
||||
var item = {title: title};
|
||||
// TODO: create all items in bulk mode
|
||||
Customslide.create(item).then(
|
||||
function(success) {
|
||||
// find related agenda item
|
||||
Agenda.find(success.agenda_item_id).then(function(item) {
|
||||
// import all items as type AGENDA_ITEM = 1
|
||||
item.type = 1;
|
||||
item.weight = 1000 + index;
|
||||
Agenda.save(item);
|
||||
});
|
||||
$scope.importcounter++;
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// *** CSV import ***
|
||||
// set initial data for csv import
|
||||
$scope.items = [];
|
||||
$scope.separator = ',';
|
||||
$scope.encoding = 'UTF-8';
|
||||
$scope.encodingOptions = ['UTF-8', 'ISO-8859-1'];
|
||||
$scope.accept = '.csv, .txt';
|
||||
$scope.csv = {
|
||||
content: null,
|
||||
header: true,
|
||||
headerVisible: false,
|
||||
separator: $scope.separator,
|
||||
separatorVisible: false,
|
||||
encoding: $scope.encoding,
|
||||
encodingVisible: false,
|
||||
accept: $scope.accept,
|
||||
result: null
|
||||
};
|
||||
// set csv file encoding
|
||||
$scope.setEncoding = function () {
|
||||
$scope.csv.encoding = $scope.encoding;
|
||||
};
|
||||
// set csv file encoding
|
||||
$scope.setSeparator = function () {
|
||||
$scope.csv.separator = $scope.separator;
|
||||
};
|
||||
// detect if csv file is loaded
|
||||
$scope.$watch('csv.result', function () {
|
||||
$scope.items = [];
|
||||
var quotionRe = /^"(.*)"$/;
|
||||
angular.forEach($scope.csv.result, function (item, index) {
|
||||
// title
|
||||
if (item.title) {
|
||||
item.title = item.title.replace(quotionRe, '$1');
|
||||
}
|
||||
if (!item.title) {
|
||||
item.importerror = true;
|
||||
item.title_error = gettext('Error: Title is required.');
|
||||
}
|
||||
// text
|
||||
if (item.text) {
|
||||
item.text = item.text.replace(quotionRe, '$1');
|
||||
}
|
||||
// duration
|
||||
if (item.duration) {
|
||||
item.duration = item.duration.replace(quotionRe, '$1');
|
||||
}
|
||||
// comment
|
||||
if (item.comment) {
|
||||
item.comment = item.comment.replace(quotionRe, '$1');
|
||||
}
|
||||
// is_hidden
|
||||
if (item.is_hidden) {
|
||||
item.is_hidden = item.is_hidden.replace(quotionRe, '$1');
|
||||
if (item.is_hidden == '1') {
|
||||
item.type = 2;
|
||||
} else {
|
||||
item.type = 1;
|
||||
}
|
||||
} else {
|
||||
item.type = 1;
|
||||
}
|
||||
// set weight for right csv row order
|
||||
// (Use 1000+ to protect existing items and prevent collision
|
||||
// with new items which use weight 10000 as default.)
|
||||
item.weight = 1000 + index;
|
||||
$scope.items.push(item);
|
||||
});
|
||||
});
|
||||
|
||||
// import from csv file
|
||||
$scope.import = function () {
|
||||
$scope.csvImporting = true;
|
||||
angular.forEach($scope.items, function (item) {
|
||||
if (!item.importerror) {
|
||||
Customslide.create(item).then(
|
||||
function(success) {
|
||||
item.imported = true;
|
||||
// find related agenda item
|
||||
Agenda.find(success.agenda_item_id).then(function(agendaItem) {
|
||||
agendaItem.duration = item.duration;
|
||||
agendaItem.comment = item.comment;
|
||||
agendaItem.type = item.type;
|
||||
agendaItem.weight = item.weight;
|
||||
Agenda.save(agendaItem);
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
$scope.csvimported = true;
|
||||
};
|
||||
$scope.clear = function () {
|
||||
$scope.csv.result = null;
|
||||
};
|
||||
// download CSV example file
|
||||
$scope.downloadCSVExample = function () {
|
||||
var element = document.getElementById('downloadLink');
|
||||
var csvRows = [
|
||||
// column header line
|
||||
['title', 'text', 'duration', 'comment', 'is_hidden'],
|
||||
// example entries
|
||||
['Demo 1', 'Demo text 1', '1:00', 'test comment', ''],
|
||||
['Break', '', '0:10', '', '1'],
|
||||
['Demo 2', 'Demo text 2', '1:30', '', '']
|
||||
|
||||
];
|
||||
var csvString = csvRows.join("%0A");
|
||||
element.href = 'data:text/csv;charset=utf-8,' + csvString;
|
||||
element.download = 'agenda-example.csv';
|
||||
element.target = '_blank';
|
||||
};
|
||||
}
|
||||
])
|
||||
|
||||
.controller('ListOfSpeakersViewCtrl', [
|
||||
'$scope',
|
||||
'$state',
|
||||
'$http',
|
||||
'Projector',
|
||||
'Assignment',
|
||||
'Customslide',
|
||||
'Motion',
|
||||
'Assignment', // TODO: Remove this after refactoring of data loading on start.
|
||||
'Topic', // TODO: Remove this after refactoring of data loading on start.
|
||||
'Motion', // TODO: Remove this after refactoring of data loading on start.
|
||||
'Agenda',
|
||||
function($scope, $state, $http, Projector, Assignment, Customslide, Motion, Agenda) {
|
||||
function($scope, $state, $http, Projector, Assignment, Topic, Motion, Agenda) {
|
||||
$scope.$watch(
|
||||
function() {
|
||||
return Projector.lastModified(1);
|
||||
@ -572,10 +424,10 @@ angular.module('OpenSlidesApp.agenda.site', ['OpenSlidesApp.agenda'])
|
||||
});
|
||||
});
|
||||
break;
|
||||
case 'core/customslide':
|
||||
Customslide.find(element.id).then(function(customslide) {
|
||||
Customslide.loadRelations(customslide, 'agenda_item').then(function() {
|
||||
$scope.AgendaItem = customslide.agenda_item;
|
||||
case 'topics/topic':
|
||||
Topic.find(element.id).then(function(topic) {
|
||||
Topic.loadRelations(topic, 'agenda_item').then(function() {
|
||||
$scope.AgendaItem = topic.agenda_item;
|
||||
});
|
||||
});
|
||||
break;
|
||||
|
@ -9,7 +9,7 @@
|
||||
<i class="fa fa-sitemap fa-lg"></i>
|
||||
<translate>Sort agenda</translate>
|
||||
</a>
|
||||
<a ui-sref="agenda.item.import" os-perms="agenda.can_manage" class="btn btn-default btn-sm">
|
||||
<a ui-sref="topics.topic.import" os-perms="agenda.can_manage" class="btn btn-default btn-sm">
|
||||
<i class="fa fa-download fa-lg"></i>
|
||||
<translate>Import</translate>
|
||||
</a>
|
||||
@ -202,7 +202,7 @@
|
||||
<span os-perms="!agenda.can_manage">
|
||||
<i ng-if="item.closed" class="fa fa-check-square-o"></i>
|
||||
</span>
|
||||
<input os-perms="agenda.can_manage" type="checkbox" ng-model="item.closed" ng-change="save(item.id);">
|
||||
<input os-perms="agenda.can_manage" type="checkbox" ng-model="item.closed" ng-change="save(item);">
|
||||
<!-- quickEdit columns -->
|
||||
<td ng-show="item.quickEdit" os-perms="agenda.can_manage" colspan="3">
|
||||
<h4>{{ item.getTitle() }} <span class="text-muted">– QuickEdit</span></h4>
|
||||
|
@ -82,8 +82,8 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments'])
|
||||
// (from assignment controller use AssignmentForm factory instead to open dialog in front
|
||||
// of current view without redirect)
|
||||
.state('assignments.assignment.detail.update', {
|
||||
onEnter: ['$stateParams', '$state', 'ngDialog', 'Assignment', 'Agenda',
|
||||
function($stateParams, $state, ngDialog, Assignment, Agenda) {
|
||||
onEnter: ['$stateParams', '$state', 'ngDialog', 'Assignment',
|
||||
function($stateParams, $state, ngDialog, Assignment) {
|
||||
ngDialog.open({
|
||||
template: 'static/templates/assignments/assignment-form.html',
|
||||
controller: 'AssignmentUpdateCtrl',
|
||||
|
@ -20,25 +20,6 @@ class ProjectorAccessPermissions(BaseAccessPermissions):
|
||||
return ProjectorSerializer
|
||||
|
||||
|
||||
class CustomSlideAccessPermissions(BaseAccessPermissions):
|
||||
"""
|
||||
Access permissions container for CustomSlide and CustomSlideViewSet.
|
||||
"""
|
||||
def check_permissions(self, user):
|
||||
"""
|
||||
Returns True if the user has read access model instances.
|
||||
"""
|
||||
return user.has_perm('core.can_manage_projector')
|
||||
|
||||
def get_serializer_class(self, user=None):
|
||||
"""
|
||||
Returns serializer class.
|
||||
"""
|
||||
from .serializers import CustomSlideSerializer
|
||||
|
||||
return CustomSlideSerializer
|
||||
|
||||
|
||||
class TagAccessPermissions(BaseAccessPermissions):
|
||||
"""
|
||||
Access permissions container for Tag and TagViewSet.
|
||||
|
@ -24,7 +24,6 @@ class CoreAppConfig(AppConfig):
|
||||
from .views import (
|
||||
ChatMessageViewSet,
|
||||
ConfigViewSet,
|
||||
CustomSlideViewSet,
|
||||
ProjectorViewSet,
|
||||
TagViewSet,
|
||||
)
|
||||
@ -40,7 +39,6 @@ class CoreAppConfig(AppConfig):
|
||||
# Register viewsets.
|
||||
router.register(self.get_model('Projector').get_collection_string(), ProjectorViewSet)
|
||||
router.register(self.get_model('ChatMessage').get_collection_string(), ChatMessageViewSet)
|
||||
router.register(self.get_model('CustomSlide').get_collection_string(), CustomSlideViewSet)
|
||||
router.register(self.get_model('Tag').get_collection_string(), TagViewSet)
|
||||
router.register(self.get_model('ConfigStore').get_collection_string(), ConfigViewSet, 'config')
|
||||
|
||||
|
69
openslides/core/migrations/0005_auto_20160918_2104.py
Normal file
69
openslides/core/migrations/0005_auto_20160918_2104.py
Normal file
@ -0,0 +1,69 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.1 on 2016-09-18 19:04
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
from openslides.utils.autoupdate import (
|
||||
inform_changed_data_receiver,
|
||||
inform_deleted_data_receiver,
|
||||
)
|
||||
|
||||
|
||||
def move_custom_slides_to_topics(apps, schema_editor):
|
||||
# Disconnect autoupdate. We do not want to trigger it here.
|
||||
models.signals.post_save.disconnect(dispatch_uid='inform_changed_data_receiver')
|
||||
models.signals.post_save.disconnect(dispatch_uid='inform_deleted_data_receiver')
|
||||
|
||||
# We get the model from the versioned app registry;
|
||||
# if we directly import it, it will be the wrong version.
|
||||
ContentType = apps.get_model('contenttypes', 'ContentType')
|
||||
CustomSlide = apps.get_model('core', 'CustomSlide')
|
||||
Item = apps.get_model('agenda', 'Item')
|
||||
Topic = apps.get_model('topics', 'Topic')
|
||||
|
||||
# Copy data.
|
||||
content_type_custom_slide = ContentType.objects.get_for_model(CustomSlide)
|
||||
content_type_topic = ContentType.objects.get_for_model(Topic)
|
||||
for custom_slide in CustomSlide.objects.all():
|
||||
# This line does not create a new Item because this migration model has
|
||||
# no method 'get_agenda_title()'. See agenda/signals.py.
|
||||
topic = Topic.objects.create(title=custom_slide.title, text=custom_slide.text)
|
||||
topic.attachments.add(*custom_slide.attachments.all())
|
||||
item = Item.objects.get(object_id=custom_slide.pk, content_type=content_type_custom_slide)
|
||||
item.object_id = topic.pk
|
||||
item.content_type = content_type_topic
|
||||
item.save()
|
||||
|
||||
# Delete old data.
|
||||
CustomSlide.objects.all().delete()
|
||||
content_type_custom_slide.delete()
|
||||
|
||||
# Reconnect autoupdate.
|
||||
models.signals.post_save.connect(
|
||||
inform_changed_data_receiver,
|
||||
dispatch_uid='inform_changed_data_receiver')
|
||||
models.signals.post_delete.connect(
|
||||
inform_deleted_data_receiver,
|
||||
dispatch_uid='inform_deleted_data_receiver')
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('core', '0004_projector_resolution'),
|
||||
('topics', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(
|
||||
move_custom_slides_to_topics
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='customslide',
|
||||
name='attachments',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='CustomSlide',
|
||||
),
|
||||
]
|
@ -1,17 +1,14 @@
|
||||
from django.conf import settings
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.contrib.sessions.models import Session as DjangoSession
|
||||
from django.db import models
|
||||
from jsonfield import JSONField
|
||||
|
||||
from openslides.mediafiles.models import Mediafile
|
||||
from openslides.utils.models import RESTModelMixin
|
||||
from openslides.utils.projector import ProjectorElement
|
||||
|
||||
from .access_permissions import (
|
||||
ChatMessageAccessPermissions,
|
||||
ConfigAccessPermissions,
|
||||
CustomSlideAccessPermissions,
|
||||
ProjectorAccessPermissions,
|
||||
TagAccessPermissions,
|
||||
)
|
||||
@ -32,7 +29,7 @@ class Projector(RESTModelMixin, models.Model):
|
||||
|
||||
{
|
||||
"881d875cf01741718ca926279ac9c99c": {
|
||||
"name": "core/customslide",
|
||||
"name": "topics/topic",
|
||||
"id": 1
|
||||
},
|
||||
"191c0878cdc04abfbd64f3177a21891a": {
|
||||
@ -155,61 +152,6 @@ class Projector(RESTModelMixin, models.Model):
|
||||
return True
|
||||
|
||||
|
||||
class CustomSlide(RESTModelMixin, models.Model):
|
||||
"""
|
||||
Model for slides with custom content.
|
||||
"""
|
||||
access_permissions = CustomSlideAccessPermissions()
|
||||
|
||||
title = models.CharField(
|
||||
max_length=256)
|
||||
text = models.TextField(
|
||||
blank=True)
|
||||
weight = models.IntegerField(
|
||||
default=0)
|
||||
attachments = models.ManyToManyField(
|
||||
Mediafile,
|
||||
blank=True)
|
||||
|
||||
class Meta:
|
||||
default_permissions = ()
|
||||
ordering = ('weight', 'title', )
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
@property
|
||||
def agenda_item(self):
|
||||
"""
|
||||
Returns the related agenda item.
|
||||
"""
|
||||
# TODO: Move the agenda app in the core app to fix circular dependencies
|
||||
from openslides.agenda.models import Item
|
||||
content_type = ContentType.objects.get_for_model(self)
|
||||
return Item.objects.get(object_id=self.pk, content_type=content_type)
|
||||
|
||||
@property
|
||||
def agenda_item_id(self):
|
||||
"""
|
||||
Returns the id of the agenda item object related to this object.
|
||||
"""
|
||||
return self.agenda_item.pk
|
||||
|
||||
def get_agenda_title(self):
|
||||
return self.title
|
||||
|
||||
def get_agenda_list_view_title(self):
|
||||
return self.title
|
||||
|
||||
def get_search_index_string(self):
|
||||
"""
|
||||
Returns a string that can be indexed for the search.
|
||||
"""
|
||||
return " ".join((
|
||||
self.title,
|
||||
self.text))
|
||||
|
||||
|
||||
class Tag(RESTModelMixin, models.Model):
|
||||
"""
|
||||
Model for tags. This tags can be used for other models like agenda items,
|
||||
|
@ -3,28 +3,7 @@ from django.utils.timezone import now
|
||||
from ..utils.projector import ProjectorElement
|
||||
from .config import config
|
||||
from .exceptions import ProjectorException
|
||||
from .models import CustomSlide, Projector
|
||||
|
||||
|
||||
class CustomSlideSlide(ProjectorElement):
|
||||
"""
|
||||
Slide definitions for custom slide model.
|
||||
"""
|
||||
name = 'core/customslide'
|
||||
|
||||
def check_data(self):
|
||||
if not CustomSlide.objects.filter(pk=self.config_entry.get('id')).exists():
|
||||
raise ProjectorException('Custom slide does not exist.')
|
||||
|
||||
def get_requirements(self, config_entry):
|
||||
try:
|
||||
custom_slide = CustomSlide.objects.get(pk=config_entry.get('id'))
|
||||
except CustomSlide.DoesNotExist:
|
||||
# Custom slide does not exist. Just do nothing.
|
||||
pass
|
||||
else:
|
||||
yield custom_slide
|
||||
yield custom_slide.agenda_item
|
||||
from .models import Projector
|
||||
|
||||
|
||||
class Clock(ProjectorElement):
|
||||
|
@ -1,6 +1,6 @@
|
||||
from openslides.utils.rest_api import Field, ModelSerializer, ValidationError
|
||||
|
||||
from .models import ChatMessage, CustomSlide, Projector, Tag
|
||||
from .models import ChatMessage, Projector, Tag
|
||||
|
||||
|
||||
class JSONSerializerField(Field):
|
||||
@ -33,15 +33,6 @@ class ProjectorSerializer(ModelSerializer):
|
||||
fields = ('id', 'config', 'elements', 'scale', 'scroll', 'width', 'height',)
|
||||
|
||||
|
||||
class CustomSlideSerializer(ModelSerializer):
|
||||
"""
|
||||
Serializer for core.models.CustomSlide objects.
|
||||
"""
|
||||
class Meta:
|
||||
model = CustomSlide
|
||||
fields = ('id', 'title', 'text', 'weight', 'attachments', 'agenda_item_id')
|
||||
|
||||
|
||||
class TagSerializer(ModelSerializer):
|
||||
"""
|
||||
Serializer for core.models.Tag objects.
|
||||
|
@ -338,50 +338,6 @@ angular.module('OpenSlidesApp.core', [
|
||||
}
|
||||
])
|
||||
|
||||
.factory('Customslide', [
|
||||
'DS',
|
||||
'jsDataModel',
|
||||
'gettext',
|
||||
function(DS, jsDataModel, gettext) {
|
||||
var name = 'core/customslide';
|
||||
return DS.defineResource({
|
||||
name: name,
|
||||
useClass: jsDataModel,
|
||||
verboseName: gettext('Agenda item'),
|
||||
methods: {
|
||||
getResourceName: function () {
|
||||
return name;
|
||||
},
|
||||
getAgendaTitle: function () {
|
||||
return this.title;
|
||||
},
|
||||
// link name which is shown in search result
|
||||
getSearchResultName: function () {
|
||||
return this.getAgendaTitle();
|
||||
},
|
||||
// subtitle of search result
|
||||
getSearchResultSubtitle: function () {
|
||||
return "Agenda item";
|
||||
},
|
||||
},
|
||||
relations: {
|
||||
belongsTo: {
|
||||
'agenda/item': {
|
||||
localKey: 'agenda_item_id',
|
||||
localField: 'agenda_item',
|
||||
}
|
||||
},
|
||||
hasMany: {
|
||||
'mediafiles/mediafile': {
|
||||
localField: 'attachments',
|
||||
localKeys: 'attachments_id',
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
])
|
||||
|
||||
.factory('Tag', [
|
||||
'DS',
|
||||
function(DS) {
|
||||
@ -544,10 +500,9 @@ angular.module('OpenSlidesApp.core', [
|
||||
.run([
|
||||
'ChatMessage',
|
||||
'Config',
|
||||
'Customslide',
|
||||
'Projector',
|
||||
'Tag',
|
||||
function (ChatMessage, Config, Customslide, Projector, Tag) {}
|
||||
function (ChatMessage, Config, Projector, Tag) {}
|
||||
]);
|
||||
|
||||
}());
|
||||
|
@ -42,10 +42,6 @@ angular.module('OpenSlidesApp.core.projector', ['OpenSlidesApp.core'])
|
||||
.config([
|
||||
'slidesProvider',
|
||||
function(slidesProvider) {
|
||||
slidesProvider.registerSlide('core/customslide', {
|
||||
template: 'static/templates/core/slide_customslide.html',
|
||||
});
|
||||
|
||||
slidesProvider.registerSlide('core/clock', {
|
||||
template: 'static/templates/core/slide_clock.html',
|
||||
});
|
||||
@ -151,18 +147,6 @@ angular.module('OpenSlidesApp.core.projector', ['OpenSlidesApp.core'])
|
||||
}
|
||||
])
|
||||
|
||||
.controller('SlideCustomSlideCtrl', [
|
||||
'$scope',
|
||||
'Customslide',
|
||||
function($scope, Customslide) {
|
||||
// Attention! Each object that is used here has to be dealt on server side.
|
||||
// Add it to the coresponding get_requirements method of the ProjectorElement
|
||||
// class.
|
||||
var id = $scope.element.id;
|
||||
Customslide.bindOne(id, $scope, 'customslide');
|
||||
}
|
||||
])
|
||||
|
||||
.controller('SlideClockCtrl', [
|
||||
'$scope',
|
||||
function($scope) {
|
||||
|
@ -713,48 +713,6 @@ angular.module('OpenSlidesApp.core.site', [
|
||||
templateUrl: 'static/templates/search.html',
|
||||
})
|
||||
|
||||
// customslide
|
||||
.state('core.customslide', {
|
||||
url: '/customslide',
|
||||
abstract: true,
|
||||
template: "<ui-view/>",
|
||||
})
|
||||
.state('core.customslide.detail', {
|
||||
resolve: {
|
||||
customslide: function(Customslide, $stateParams) {
|
||||
return Customslide.find($stateParams.id);
|
||||
},
|
||||
items: function(Agenda) {
|
||||
return Agenda.findAll();
|
||||
}
|
||||
}
|
||||
})
|
||||
// redirects to customslide detail and opens customslide edit form dialog, uses edit url,
|
||||
// used by ui-sref links from agenda only
|
||||
// (from customslide controller use CustomSlideForm factory instead to open dialog in front
|
||||
// of current view without redirect)
|
||||
.state('core.customslide.detail.update', {
|
||||
onEnter: ['$stateParams', '$state', 'ngDialog', 'Customslide', 'Agenda',
|
||||
function($stateParams, $state, ngDialog, Customslide, Agenda) {
|
||||
ngDialog.open({
|
||||
template: 'static/templates/core/customslide-form.html',
|
||||
controller: 'CustomslideUpdateCtrl',
|
||||
className: 'ngdialog-theme-default wide-form',
|
||||
resolve: {
|
||||
customslide: function() {
|
||||
return Customslide.find($stateParams.id);
|
||||
},
|
||||
items: function() {
|
||||
return Agenda.findAll();
|
||||
}
|
||||
},
|
||||
preCloseCallback: function() {
|
||||
$state.go('core.customslide.detail', {customslide: $stateParams.id});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}],
|
||||
})
|
||||
// tag
|
||||
.state('core.tag', {
|
||||
url: '/tag',
|
||||
@ -1144,7 +1102,7 @@ angular.module('OpenSlidesApp.core.site', [
|
||||
if ($scope.filterMotion && result.urlState == 'motions.motion.detail') {
|
||||
return result;
|
||||
}
|
||||
if ($scope.filterAgenda && result.urlState == 'core.customslide.detail') {
|
||||
if ($scope.filterAgenda && result.urlState == 'topics.topic.detail') {
|
||||
return result;
|
||||
}
|
||||
if ($scope.filterAssignment && result.urlState == 'assignments.assignment.detail') {
|
||||
@ -1159,88 +1117,6 @@ angular.module('OpenSlidesApp.core.site', [
|
||||
}
|
||||
])
|
||||
|
||||
|
||||
// Provide generic customslide form fields for create and update view
|
||||
.factory('CustomslideForm', [
|
||||
'gettextCatalog',
|
||||
'Editor',
|
||||
'Mediafile',
|
||||
'Agenda',
|
||||
'AgendaTree',
|
||||
function (gettextCatalog, Editor, Mediafile, Agenda, AgendaTree) {
|
||||
return {
|
||||
// ngDialog for customslide form
|
||||
getDialog: function (customslide) {
|
||||
var resolve = {};
|
||||
if (customslide) {
|
||||
resolve = {
|
||||
customslide: function(Customslide) {return Customslide.find(customslide.id);}
|
||||
};
|
||||
}
|
||||
resolve.mediafiles = function(Mediafile) {return Mediafile.findAll();};
|
||||
return {
|
||||
template: 'static/templates/core/customslide-form.html',
|
||||
controller: (customslide) ? 'CustomslideUpdateCtrl' : 'CustomslideCreateCtrl',
|
||||
className: 'ngdialog-theme-default wide-form',
|
||||
closeByEscape: false,
|
||||
closeByDocument: false,
|
||||
resolve: (resolve) ? resolve : null
|
||||
};
|
||||
},
|
||||
getFormFields: function () {
|
||||
var images = Mediafile.getAllImages();
|
||||
return [
|
||||
{
|
||||
key: 'title',
|
||||
type: 'input',
|
||||
templateOptions: {
|
||||
label: gettextCatalog.getString('Title'),
|
||||
required: true
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'text',
|
||||
type: 'editor',
|
||||
templateOptions: {
|
||||
label: gettextCatalog.getString('Text')
|
||||
},
|
||||
data: {
|
||||
tinymceOption: Editor.getOptions(images)
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'attachments_id',
|
||||
type: 'select-multiple',
|
||||
templateOptions: {
|
||||
label: gettextCatalog.getString('Attachment'),
|
||||
options: Mediafile.getAll(),
|
||||
ngOptions: 'option.id as option.title_or_filename for option in to.options',
|
||||
placeholder: gettextCatalog.getString('Select or search an attachment ...')
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'showAsAgendaItem',
|
||||
type: 'checkbox',
|
||||
templateOptions: {
|
||||
label: gettextCatalog.getString('Show as agenda item'),
|
||||
description: gettextCatalog.getString('If deactivated it appears as internal item on agenda.')
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'agenda_parent_item_id',
|
||||
type: 'select-single',
|
||||
templateOptions: {
|
||||
label: gettextCatalog.getString('Parent item'),
|
||||
options: AgendaTree.getFlatTree(Agenda.getAll()),
|
||||
ngOptions: 'item.id as item.getListViewTitle() for item in to.options | notself : model.agenda_item_id',
|
||||
placeholder: gettextCatalog.getString('Select a parent item ...')
|
||||
}
|
||||
}];
|
||||
}
|
||||
};
|
||||
}
|
||||
])
|
||||
|
||||
// Projector Control Controller
|
||||
.controller('ProjectorControlCtrl', [
|
||||
'$scope',
|
||||
@ -1454,101 +1330,6 @@ angular.module('OpenSlidesApp.core.site', [
|
||||
}
|
||||
])
|
||||
|
||||
// Customslide Controllers
|
||||
.controller('CustomslideDetailCtrl', [
|
||||
'$scope',
|
||||
'ngDialog',
|
||||
'CustomslideForm',
|
||||
'Customslide',
|
||||
'customslide',
|
||||
function($scope, ngDialog, CustomslideForm, Customslide, customslide) {
|
||||
Customslide.bindOne(customslide.id, $scope, 'customslide');
|
||||
Customslide.loadRelations(customslide, 'agenda_item');
|
||||
// open edit dialog
|
||||
$scope.openDialog = function (customslide) {
|
||||
ngDialog.open(CustomslideForm.getDialog(customslide));
|
||||
};
|
||||
}
|
||||
])
|
||||
|
||||
.controller('CustomslideCreateCtrl', [
|
||||
'$scope',
|
||||
'$state',
|
||||
'Customslide',
|
||||
'CustomslideForm',
|
||||
'Agenda',
|
||||
'AgendaUpdate',
|
||||
function($scope, $state, Customslide, CustomslideForm, Agenda, AgendaUpdate) {
|
||||
$scope.customslide = {};
|
||||
$scope.model = {};
|
||||
$scope.model.showAsAgendaItem = true;
|
||||
// get all form fields
|
||||
$scope.formFields = CustomslideForm.getFormFields();
|
||||
// save form
|
||||
$scope.save = function (customslide) {
|
||||
Customslide.create(customslide).then(
|
||||
function(success) {
|
||||
// type: Value 1 means a non hidden agenda item, value 2 means a hidden agenda item,
|
||||
// see openslides.agenda.models.Item.ITEM_TYPE.
|
||||
var changes = [{key: 'type', value: (customslide.showAsAgendaItem ? 1 : 2)},
|
||||
{key: 'parent_id', value: customslide.agenda_parent_item_id}];
|
||||
AgendaUpdate.saveChanges(success.agenda_item_id,changes);
|
||||
});
|
||||
$scope.closeThisDialog();
|
||||
};
|
||||
}
|
||||
])
|
||||
|
||||
.controller('CustomslideUpdateCtrl', [
|
||||
'$scope',
|
||||
'$state',
|
||||
'Customslide',
|
||||
'CustomslideForm',
|
||||
'Agenda',
|
||||
'AgendaUpdate',
|
||||
'customslide',
|
||||
function($scope, $state, Customslide, CustomslideForm, Agenda, AgendaUpdate, customslide) {
|
||||
Customslide.loadRelations(customslide, 'agenda_item');
|
||||
$scope.alert = {};
|
||||
// set initial values for form model by create deep copy of customslide object
|
||||
// so list/detail view is not updated while editing
|
||||
$scope.model = angular.copy(customslide);
|
||||
// get all form fields
|
||||
$scope.formFields = CustomslideForm.getFormFields();
|
||||
for (var i = 0; i < $scope.formFields.length; i++) {
|
||||
if ($scope.formFields[i].key == "showAsAgendaItem") {
|
||||
// get state from agenda item (hidden/internal or agenda item)
|
||||
$scope.formFields[i].defaultValue = !customslide.agenda_item.is_hidden;
|
||||
} else if($scope.formFields[i].key == "agenda_parent_item_id") {
|
||||
$scope.formFields[i].defaultValue = customslide.agenda_item.parent_id;
|
||||
}
|
||||
}
|
||||
|
||||
// save form
|
||||
$scope.save = function (customslide) {
|
||||
Customslide.create(customslide).then(
|
||||
function(success) {
|
||||
// type: Value 1 means a non hidden agenda item, value 2 means a hidden agenda item,
|
||||
// see openslides.agenda.models.Item.ITEM_TYPE.
|
||||
var changes = [{key: 'type', value: (customslide.showAsAgendaItem ? 1 : 2)},
|
||||
{key: 'parent_id', value: customslide.agenda_parent_item_id}];
|
||||
AgendaUpdate.saveChanges(success.agenda_item_id,changes);
|
||||
$scope.closeThisDialog();
|
||||
}, function (error) {
|
||||
// save error: revert all changes by restore
|
||||
// (refresh) original customslide object from server
|
||||
Customslide.refresh(customslide);
|
||||
var message = '';
|
||||
for (var e in error.data) {
|
||||
message += e + ': ' + error.data[e] + ' ';
|
||||
}
|
||||
$scope.alert = {type: 'danger', msg: message, show: true};
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
])
|
||||
|
||||
// Tag Controller
|
||||
.controller('TagListCtrl', [
|
||||
'$scope',
|
||||
|
@ -1,4 +0,0 @@
|
||||
<div ng-controller="SlideCustomSlideCtrl" class="content scrollcontent">
|
||||
<h1>{{ customslide.agenda_item.getTitle() }}</h1>
|
||||
<div ng-bind-html="customslide.text | trusted"></div>
|
||||
</div>
|
@ -37,13 +37,12 @@ from openslides.utils.search import search
|
||||
from .access_permissions import (
|
||||
ChatMessageAccessPermissions,
|
||||
ConfigAccessPermissions,
|
||||
CustomSlideAccessPermissions,
|
||||
ProjectorAccessPermissions,
|
||||
TagAccessPermissions,
|
||||
)
|
||||
from .config import config
|
||||
from .exceptions import ConfigError, ConfigNotFound
|
||||
from .models import ChatMessage, CustomSlide, Projector, Tag
|
||||
from .models import ChatMessage, Projector, Tag
|
||||
|
||||
|
||||
# Special Django views
|
||||
@ -449,27 +448,6 @@ class ProjectorViewSet(ReadOnlyModelViewSet):
|
||||
return Response({'detail': message})
|
||||
|
||||
|
||||
class CustomSlideViewSet(ModelViewSet):
|
||||
"""
|
||||
API endpoint for custom slides.
|
||||
|
||||
There are the following views: metadata, list, retrieve, create,
|
||||
partial_update, update and destroy.
|
||||
"""
|
||||
access_permissions = CustomSlideAccessPermissions()
|
||||
queryset = CustomSlide.objects.all()
|
||||
|
||||
def check_view_permissions(self):
|
||||
"""
|
||||
Returns True if the user has required permissions.
|
||||
"""
|
||||
if self.action in ('list', 'retrieve'):
|
||||
result = self.get_access_permissions().check_permissions(self.request.user)
|
||||
else:
|
||||
result = self.request.user.has_perm('core.can_manage_projector')
|
||||
return result
|
||||
|
||||
|
||||
class TagViewSet(ModelViewSet):
|
||||
"""
|
||||
API endpoint for tags.
|
||||
|
@ -17,6 +17,7 @@ INSTALLED_APPS = [
|
||||
'rest_framework',
|
||||
'channels',
|
||||
'openslides.agenda',
|
||||
'openslides.topics',
|
||||
'openslides.motions',
|
||||
'openslides.assignments',
|
||||
'openslides.mediafiles',
|
||||
|
1
openslides/topics/__init__.py
Normal file
1
openslides/topics/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
default_app_config = 'openslides.topics.apps.TopicsAppConfig'
|
20
openslides/topics/access_permissions.py
Normal file
20
openslides/topics/access_permissions.py
Normal file
@ -0,0 +1,20 @@
|
||||
from ..utils.access_permissions import BaseAccessPermissions
|
||||
|
||||
|
||||
class TopicAccessPermissions(BaseAccessPermissions):
|
||||
"""
|
||||
Access permissions container for Topic and TopicViewSet.
|
||||
"""
|
||||
def check_permissions(self, user):
|
||||
"""
|
||||
Returns True if the user has read access model instances.
|
||||
"""
|
||||
return user.has_perm('agenda.can_see')
|
||||
|
||||
def get_serializer_class(self, user=None):
|
||||
"""
|
||||
Returns serializer class.
|
||||
"""
|
||||
from .serializers import TopicSerializer
|
||||
|
||||
return TopicSerializer
|
20
openslides/topics/apps.py
Normal file
20
openslides/topics/apps.py
Normal file
@ -0,0 +1,20 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class TopicsAppConfig(AppConfig):
|
||||
name = 'openslides.topics'
|
||||
verbose_name = 'OpenSlides Topics'
|
||||
angular_site_module = True
|
||||
angular_projector_module = True
|
||||
|
||||
def ready(self):
|
||||
# Load projector elements.
|
||||
# Do this by just importing all from these files.
|
||||
from . import projector # noqa
|
||||
|
||||
# Import all required stuff.
|
||||
from ..utils.rest_api import router
|
||||
from .views import TopicViewSet
|
||||
|
||||
# Register viewsets.
|
||||
router.register(self.get_model('Topic').get_collection_string(), TopicViewSet)
|
31
openslides/topics/migrations/0001_initial.py
Normal file
31
openslides/topics/migrations/0001_initial.py
Normal file
@ -0,0 +1,31 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.1 on 2016-09-18 20:20
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import openslides.utils.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('mediafiles', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Topic',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('title', models.CharField(max_length=256)),
|
||||
('text', models.TextField(blank=True)),
|
||||
('attachments', models.ManyToManyField(blank=True, to='mediafiles.Mediafile')),
|
||||
],
|
||||
options={
|
||||
'default_permissions': (),
|
||||
},
|
||||
bases=(openslides.utils.models.RESTModelMixin, models.Model),
|
||||
),
|
||||
]
|
0
openslides/topics/migrations/__init__.py
Normal file
0
openslides/topics/migrations/__init__.py
Normal file
53
openslides/topics/models.py
Normal file
53
openslides/topics/models.py
Normal file
@ -0,0 +1,53 @@
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db import models
|
||||
|
||||
from ..agenda.models import Item
|
||||
from ..mediafiles.models import Mediafile
|
||||
from ..utils.models import RESTModelMixin
|
||||
from .access_permissions import TopicAccessPermissions
|
||||
|
||||
|
||||
class Topic(RESTModelMixin, models.Model):
|
||||
"""
|
||||
Model for slides with custom content. Used to be called custom slide.
|
||||
"""
|
||||
access_permissions = TopicAccessPermissions()
|
||||
|
||||
title = models.CharField(max_length=256)
|
||||
text = models.TextField(blank=True)
|
||||
attachments = models.ManyToManyField(Mediafile, blank=True)
|
||||
|
||||
class Meta:
|
||||
default_permissions = ()
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
@property
|
||||
def agenda_item(self):
|
||||
"""
|
||||
Returns the related agenda item.
|
||||
"""
|
||||
content_type = ContentType.objects.get_for_model(self)
|
||||
return Item.objects.get(object_id=self.pk, content_type=content_type)
|
||||
|
||||
@property
|
||||
def agenda_item_id(self):
|
||||
"""
|
||||
Returns the id of the agenda item object related to this object.
|
||||
"""
|
||||
return self.agenda_item.pk
|
||||
|
||||
def get_agenda_title(self):
|
||||
return self.title
|
||||
|
||||
def get_agenda_list_view_title(self):
|
||||
return self.title
|
||||
|
||||
def get_search_index_string(self):
|
||||
"""
|
||||
Returns a string that can be indexed for the search.
|
||||
"""
|
||||
return " ".join((
|
||||
self.title,
|
||||
self.text))
|
24
openslides/topics/projector.py
Normal file
24
openslides/topics/projector.py
Normal file
@ -0,0 +1,24 @@
|
||||
from ..core.exceptions import ProjectorException
|
||||
from ..utils.projector import ProjectorElement
|
||||
from .models import Topic
|
||||
|
||||
|
||||
class TopicSlide(ProjectorElement):
|
||||
"""
|
||||
Slide definitions for topic model.
|
||||
"""
|
||||
name = 'topics/topic'
|
||||
|
||||
def check_data(self):
|
||||
if not Topic.objects.filter(pk=self.config_entry.get('id')).exists():
|
||||
raise ProjectorException('Topic does not exist.')
|
||||
|
||||
def get_requirements(self, config_entry):
|
||||
try:
|
||||
topic = Topic.objects.get(pk=config_entry.get('id'))
|
||||
except Topic.DoesNotExist:
|
||||
# Topic does not exist. Just do nothing.
|
||||
pass
|
||||
else:
|
||||
yield topic
|
||||
yield topic.agenda_item
|
12
openslides/topics/serializers.py
Normal file
12
openslides/topics/serializers.py
Normal file
@ -0,0 +1,12 @@
|
||||
from openslides.utils.rest_api import ModelSerializer
|
||||
|
||||
from .models import Topic
|
||||
|
||||
|
||||
class TopicSerializer(ModelSerializer):
|
||||
"""
|
||||
Serializer for core.models.Topic objects.
|
||||
"""
|
||||
class Meta:
|
||||
model = Topic
|
||||
fields = ('id', 'title', 'text', 'attachments', 'agenda_item_id')
|
53
openslides/topics/static/js/topics/base.js
Normal file
53
openslides/topics/static/js/topics/base.js
Normal file
@ -0,0 +1,53 @@
|
||||
(function () {
|
||||
|
||||
'use strict';
|
||||
|
||||
angular.module('OpenSlidesApp.topics', [])
|
||||
|
||||
.factory('Topic', [
|
||||
'DS',
|
||||
'jsDataModel',
|
||||
'gettext',
|
||||
function(DS, jsDataModel, gettext) {
|
||||
var name = 'topics/topic';
|
||||
return DS.defineResource({
|
||||
name: name,
|
||||
useClass: jsDataModel,
|
||||
verboseName: gettext('Topic'),
|
||||
methods: {
|
||||
getResourceName: function () {
|
||||
return name;
|
||||
},
|
||||
getAgendaTitle: function () {
|
||||
return this.title;
|
||||
},
|
||||
// link name which is shown in search result
|
||||
getSearchResultName: function () {
|
||||
return this.getAgendaTitle();
|
||||
},
|
||||
// subtitle of search result
|
||||
getSearchResultSubtitle: function () {
|
||||
return 'Topic';
|
||||
},
|
||||
},
|
||||
relations: {
|
||||
belongsTo: {
|
||||
'agenda/item': {
|
||||
localKey: 'agenda_item_id',
|
||||
localField: 'agenda_item',
|
||||
}
|
||||
},
|
||||
hasMany: {
|
||||
'mediafiles/mediafile': {
|
||||
localField: 'attachments',
|
||||
localKeys: 'attachments_id',
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
])
|
||||
|
||||
.run(['Topic', function(Topic) {}]);
|
||||
|
||||
}());
|
28
openslides/topics/static/js/topics/projector.js
Normal file
28
openslides/topics/static/js/topics/projector.js
Normal file
@ -0,0 +1,28 @@
|
||||
(function () {
|
||||
|
||||
'use strict';
|
||||
|
||||
angular.module('OpenSlidesApp.topics.projector', ['OpenSlidesApp.topics'])
|
||||
|
||||
.config([
|
||||
'slidesProvider',
|
||||
function (slidesProvider) {
|
||||
slidesProvider.registerSlide('topics/topic', {
|
||||
template: 'static/templates/topics/slide_topic.html'
|
||||
});
|
||||
}
|
||||
])
|
||||
|
||||
.controller('SlideTopicCtrl', [
|
||||
'$scope',
|
||||
'Topic',
|
||||
function($scope, Topic) {
|
||||
// Attention! Each object that is used here has to be dealt on server side.
|
||||
// Add it to the coresponding get_requirements method of the ProjectorElement
|
||||
// class.
|
||||
var id = $scope.element.id;
|
||||
Topic.bindOne(id, $scope, 'topic');
|
||||
}
|
||||
]);
|
||||
|
||||
})();
|
391
openslides/topics/static/js/topics/site.js
Normal file
391
openslides/topics/static/js/topics/site.js
Normal file
@ -0,0 +1,391 @@
|
||||
(function () {
|
||||
|
||||
'use strict';
|
||||
|
||||
angular.module('OpenSlidesApp.topics.site', ['OpenSlidesApp.topics'])
|
||||
|
||||
.config([
|
||||
'$stateProvider',
|
||||
function($stateProvider) {
|
||||
$stateProvider
|
||||
.state('topics', {
|
||||
url: '/topics',
|
||||
abstract: true,
|
||||
template: "<ui-view/>",
|
||||
})
|
||||
|
||||
.state('topics.topic', {
|
||||
url: '/topic',
|
||||
abstract: true,
|
||||
template: "<ui-view/>",
|
||||
})
|
||||
.state('topics.topic.detail', {
|
||||
resolve: {
|
||||
topic: function(Topic, $stateParams) {
|
||||
return Topic.find($stateParams.id);
|
||||
},
|
||||
items: function(Agenda) {
|
||||
return Agenda.findAll();
|
||||
}
|
||||
}
|
||||
})
|
||||
// redirects to topic detail and opens topic edit form dialog, uses edit url,
|
||||
// used by ui-sref links from agenda only
|
||||
// (from topic controller use TopicForm factory instead to open dialog in front
|
||||
// of current view without redirect)
|
||||
.state('topics.topic.detail.update', {
|
||||
onEnter: ['$stateParams', '$state', 'ngDialog', 'Topic',
|
||||
function($stateParams, $state, ngDialog, Topic) {
|
||||
ngDialog.open({
|
||||
template: 'static/templates/topics/topic-form.html',
|
||||
controller: 'TopicUpdateCtrl',
|
||||
className: 'ngdialog-theme-default wide-form',
|
||||
closeByEscape: false,
|
||||
closeByDocument: false,
|
||||
resolve: {
|
||||
topic: function() {
|
||||
return Topic.find($stateParams.id);
|
||||
},
|
||||
items: function(Agenda) {
|
||||
return Agenda.findAll().catch(
|
||||
function() {
|
||||
return null;
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
preCloseCallback: function() {
|
||||
$state.go('topics.topic.detail', {topic: $stateParams.id});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}],
|
||||
})
|
||||
.state('topics.topic.import', {
|
||||
url: '/import',
|
||||
controller: 'TopicImportCtrl',
|
||||
});
|
||||
}
|
||||
])
|
||||
|
||||
.factory('TopicForm', [
|
||||
'gettextCatalog',
|
||||
'Editor',
|
||||
'Mediafile',
|
||||
'Agenda',
|
||||
'AgendaTree',
|
||||
function (gettextCatalog, Editor, Mediafile, Agenda, AgendaTree) {
|
||||
return {
|
||||
// ngDialog for topic form
|
||||
getDialog: function (topic) {
|
||||
var resolve = {};
|
||||
if (topic) {
|
||||
resolve = {
|
||||
topic: function (Topic) {return Topic.find(topic.id);}
|
||||
};
|
||||
}
|
||||
resolve.mediafiles = function (Mediafile) {
|
||||
return Mediafile.findAll();
|
||||
};
|
||||
return {
|
||||
template: 'static/templates/topics/topic-form.html',
|
||||
controller: (topic) ? 'TopicUpdateCtrl' : 'TopicCreateCtrl',
|
||||
className: 'ngdialog-theme-default wide-form',
|
||||
closeByEscape: false,
|
||||
closeByDocument: false,
|
||||
resolve: (resolve) ? resolve : null
|
||||
};
|
||||
},
|
||||
getFormFields: function () {
|
||||
var images = Mediafile.getAllImages();
|
||||
return [
|
||||
{
|
||||
key: 'title',
|
||||
type: 'input',
|
||||
templateOptions: {
|
||||
label: gettextCatalog.getString('Title'),
|
||||
required: true
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'text',
|
||||
type: 'editor',
|
||||
templateOptions: {
|
||||
label: gettextCatalog.getString('Text')
|
||||
},
|
||||
data: {
|
||||
tinymceOption: Editor.getOptions(images)
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'attachments_id',
|
||||
type: 'select-multiple',
|
||||
templateOptions: {
|
||||
label: gettextCatalog.getString('Attachment'),
|
||||
options: Mediafile.getAll(),
|
||||
ngOptions: 'option.id as option.title_or_filename for option in to.options',
|
||||
placeholder: gettextCatalog.getString('Select or search an attachment ...')
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'showAsAgendaItem',
|
||||
type: 'checkbox',
|
||||
templateOptions: {
|
||||
label: gettextCatalog.getString('Show as agenda item'),
|
||||
description: gettextCatalog.getString('If deactivated it appears as internal item on agenda.')
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'agenda_parent_item_id',
|
||||
type: 'select-single',
|
||||
templateOptions: {
|
||||
label: gettextCatalog.getString('Parent item'),
|
||||
options: AgendaTree.getFlatTree(Agenda.getAll()),
|
||||
ngOptions: 'item.id as item.getListViewTitle() for item in to.options | notself : model.agenda_item_id',
|
||||
placeholder: gettextCatalog.getString('Select a parent item ...')
|
||||
}
|
||||
}];
|
||||
}
|
||||
};
|
||||
}
|
||||
])
|
||||
|
||||
.controller('TopicDetailCtrl', [
|
||||
'$scope',
|
||||
'ngDialog',
|
||||
'TopicForm',
|
||||
'Topic',
|
||||
'topic',
|
||||
function($scope, ngDialog, TopicForm, Topic, topic) {
|
||||
Topic.bindOne(topic.id, $scope, 'topic');
|
||||
Topic.loadRelations(topic, 'agenda_item');
|
||||
$scope.openDialog = function (topic) {
|
||||
ngDialog.open(TopicForm.getDialog(topic));
|
||||
};
|
||||
}
|
||||
])
|
||||
|
||||
.controller('TopicCreateCtrl', [
|
||||
'$scope',
|
||||
'$state',
|
||||
'Topic',
|
||||
'TopicForm',
|
||||
'Agenda',
|
||||
'AgendaUpdate',
|
||||
function($scope, $state, Topic, TopicForm, Agenda, AgendaUpdate) {
|
||||
$scope.topic = {};
|
||||
$scope.model = {};
|
||||
$scope.model.showAsAgendaItem = true;
|
||||
// get all form fields
|
||||
$scope.formFields = TopicForm.getFormFields();
|
||||
// save form
|
||||
$scope.save = function (topic) {
|
||||
Topic.create(topic).then(
|
||||
function (success) {
|
||||
// type: Value 1 means a non hidden agenda item, value 2 means a hidden agenda item,
|
||||
// see openslides.agenda.models.Item.ITEM_TYPE.
|
||||
var changes = [{key: 'type', value: (topic.showAsAgendaItem ? 1 : 2)},
|
||||
{key: 'parent_id', value: topic.agenda_parent_item_id}];
|
||||
AgendaUpdate.saveChanges(success.agenda_item_id,changes);
|
||||
});
|
||||
$scope.closeThisDialog();
|
||||
};
|
||||
}
|
||||
])
|
||||
|
||||
.controller('TopicUpdateCtrl', [
|
||||
'$scope',
|
||||
'$state',
|
||||
'Topic',
|
||||
'TopicForm',
|
||||
'Agenda',
|
||||
'AgendaUpdate',
|
||||
'topic',
|
||||
function($scope, $state, Topic, TopicForm, Agenda, AgendaUpdate, topic) {
|
||||
Topic.loadRelations(topic, 'agenda_item');
|
||||
$scope.alert = {};
|
||||
// set initial values for form model by create deep copy of topic object
|
||||
// so list/detail view is not updated while editing
|
||||
$scope.model = angular.copy(topic);
|
||||
// get all form fields
|
||||
$scope.formFields = TopicForm.getFormFields();
|
||||
for (var i = 0; i < $scope.formFields.length; i++) {
|
||||
if ($scope.formFields[i].key == "showAsAgendaItem") {
|
||||
// get state from agenda item (hidden/internal or agenda item)
|
||||
$scope.formFields[i].defaultValue = !topic.agenda_item.is_hidden;
|
||||
} else if ($scope.formFields[i].key == "agenda_parent_item_id") {
|
||||
$scope.formFields[i].defaultValue = topic.agenda_item.parent_id;
|
||||
}
|
||||
}
|
||||
// save form
|
||||
$scope.save = function (topic) {
|
||||
Topic.create(topic).then(
|
||||
function(success) {
|
||||
// type: Value 1 means a non hidden agenda item, value 2 means a hidden agenda item,
|
||||
// see openslides.agenda.models.Item.ITEM_TYPE.
|
||||
var changes = [{key: 'type', value: (topic.showAsAgendaItem ? 1 : 2)},
|
||||
{key: 'parent_id', value: topic.agenda_parent_item_id}];
|
||||
AgendaUpdate.saveChanges(success.agenda_item_id,changes);
|
||||
$scope.closeThisDialog();
|
||||
}, function (error) {
|
||||
// save error: revert all changes by restore
|
||||
// (refresh) original topic object from server
|
||||
Topic.refresh(topic);
|
||||
var message = '';
|
||||
for (var e in error.data) {
|
||||
message += e + ': ' + error.data[e] + ' ';
|
||||
}
|
||||
$scope.alert = {type: 'danger', msg: message, show: true};
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
])
|
||||
|
||||
.controller('TopicImportCtrl', [
|
||||
'$scope',
|
||||
'gettext',
|
||||
'Agenda',
|
||||
'Topic',
|
||||
function($scope, gettext, Agenda, Topic) {
|
||||
// Big TODO: Change wording from "item" to "topic".
|
||||
// import from textarea
|
||||
$scope.importByLine = function () {
|
||||
if ($scope.itemlist) {
|
||||
$scope.titleItems = $scope.itemlist[0].split("\n");
|
||||
$scope.importcounter = 0;
|
||||
$scope.titleItems.forEach(function(title, index) {
|
||||
var item = {title: title};
|
||||
// TODO: create all items in bulk mode
|
||||
Topic.create(item).then(
|
||||
function(success) {
|
||||
// find related agenda item
|
||||
Agenda.find(success.agenda_item_id).then(function(item) {
|
||||
// import all items as type AGENDA_ITEM = 1
|
||||
item.type = 1;
|
||||
item.weight = 1000 + index;
|
||||
Agenda.save(item);
|
||||
});
|
||||
$scope.importcounter++;
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// *** CSV import ***
|
||||
// set initial data for csv import
|
||||
$scope.items = [];
|
||||
$scope.separator = ',';
|
||||
$scope.encoding = 'UTF-8';
|
||||
$scope.encodingOptions = ['UTF-8', 'ISO-8859-1'];
|
||||
$scope.accept = '.csv, .txt';
|
||||
$scope.csv = {
|
||||
content: null,
|
||||
header: true,
|
||||
headerVisible: false,
|
||||
separator: $scope.separator,
|
||||
separatorVisible: false,
|
||||
encoding: $scope.encoding,
|
||||
encodingVisible: false,
|
||||
accept: $scope.accept,
|
||||
result: null
|
||||
};
|
||||
// set csv file encoding
|
||||
$scope.setEncoding = function () {
|
||||
$scope.csv.encoding = $scope.encoding;
|
||||
};
|
||||
// set csv file encoding
|
||||
$scope.setSeparator = function () {
|
||||
$scope.csv.separator = $scope.separator;
|
||||
};
|
||||
// detect if csv file is loaded
|
||||
$scope.$watch('csv.result', function () {
|
||||
$scope.items = [];
|
||||
var quotionRe = /^"(.*)"$/;
|
||||
angular.forEach($scope.csv.result, function (item, index) {
|
||||
// title
|
||||
if (item.title) {
|
||||
item.title = item.title.replace(quotionRe, '$1');
|
||||
}
|
||||
if (!item.title) {
|
||||
item.importerror = true;
|
||||
item.title_error = gettext('Error: Title is required.');
|
||||
}
|
||||
// text
|
||||
if (item.text) {
|
||||
item.text = item.text.replace(quotionRe, '$1');
|
||||
}
|
||||
// duration
|
||||
if (item.duration) {
|
||||
item.duration = item.duration.replace(quotionRe, '$1');
|
||||
}
|
||||
// comment
|
||||
if (item.comment) {
|
||||
item.comment = item.comment.replace(quotionRe, '$1');
|
||||
}
|
||||
// is_hidden
|
||||
if (item.is_hidden) {
|
||||
item.is_hidden = item.is_hidden.replace(quotionRe, '$1');
|
||||
if (item.is_hidden == '1') {
|
||||
item.type = 2;
|
||||
} else {
|
||||
item.type = 1;
|
||||
}
|
||||
} else {
|
||||
item.type = 1;
|
||||
}
|
||||
// set weight for right csv row order
|
||||
// (Use 1000+ to protect existing items and prevent collision
|
||||
// with new items which use weight 10000 as default.)
|
||||
item.weight = 1000 + index;
|
||||
$scope.items.push(item);
|
||||
});
|
||||
});
|
||||
|
||||
// import from csv file
|
||||
$scope.import = function () {
|
||||
$scope.csvImporting = true;
|
||||
angular.forEach($scope.items, function (item) {
|
||||
if (!item.importerror) {
|
||||
Topic.create(item).then(
|
||||
function(success) {
|
||||
item.imported = true;
|
||||
// find related agenda item
|
||||
Agenda.find(success.agenda_item_id).then(function(agendaItem) {
|
||||
agendaItem.duration = item.duration;
|
||||
agendaItem.comment = item.comment;
|
||||
agendaItem.type = item.type;
|
||||
agendaItem.weight = item.weight;
|
||||
Agenda.save(agendaItem);
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
$scope.csvimported = true;
|
||||
};
|
||||
$scope.clear = function () {
|
||||
$scope.csv.result = null;
|
||||
};
|
||||
// download CSV example file
|
||||
$scope.downloadCSVExample = function () {
|
||||
var element = document.getElementById('downloadLink');
|
||||
var csvRows = [
|
||||
// column header line
|
||||
['title', 'text', 'duration', 'comment', 'is_hidden'],
|
||||
// example entries
|
||||
['Demo 1', 'Demo text 1', '1:00', 'test comment', ''],
|
||||
['Break', '', '0:10', '', '1'],
|
||||
['Demo 2', 'Demo text 2', '1:30', '', '']
|
||||
|
||||
];
|
||||
var csvString = csvRows.join("%0A");
|
||||
element.href = 'data:text/csv;charset=utf-8,' + csvString;
|
||||
element.download = 'agenda-example.csv';
|
||||
element.target = '_blank';
|
||||
};
|
||||
}
|
||||
]);
|
||||
|
||||
}());
|
@ -0,0 +1,4 @@
|
||||
<div ng-controller="SlideTopicCtrl" class="content scrollcontent">
|
||||
<h1>{{ topic.agenda_item.getTitle() }}</h1>
|
||||
<div ng-bind-html="topic.text | trusted"></div>
|
||||
</div>
|
@ -6,34 +6,34 @@
|
||||
<translate>Back to overview</translate>
|
||||
</a>
|
||||
<!-- List of speakers -->
|
||||
<a ui-sref="agenda.item.detail({id: customslide.agenda_item_id})" class="btn btn-sm btn-default">
|
||||
<a ui-sref="agenda.item.detail({id: topic.agenda_item_id})" class="btn btn-sm btn-default">
|
||||
<i class="fa fa-microphone fa-lg"></i>
|
||||
<translate>List of speakers</translate>
|
||||
</a>
|
||||
<!-- project -->
|
||||
<a os-perms="core.can_manage_projector" class="btn btn-default btn-sm"
|
||||
ng-class="{ 'btn-primary': customslide.isProjected() }"
|
||||
ng-click="customslide.project()"
|
||||
title="{{ 'Project agenda item' | translate }}">
|
||||
ng-class="{ 'btn-primary': topic.isProjected() }"
|
||||
ng-click="topic.project()"
|
||||
title="{{ 'Project topic' | translate }}">
|
||||
<i class="fa fa-video-camera"></i>
|
||||
</a>
|
||||
<!-- edit -->
|
||||
<a os-perms="agenda.can_manage" ng-click="openDialog(customslide)"
|
||||
<a os-perms="agenda.can_manage" ng-click="openDialog(topic)"
|
||||
class="btn btn-default btn-sm"
|
||||
title="{{ 'Edit' | translate}}">
|
||||
<i class="fa fa-pencil"></i>
|
||||
</a>
|
||||
</div>
|
||||
<h1>{{ customslide.agenda_item.getTitle() }}</h1>
|
||||
<h2 translate>Agenda item</h2>
|
||||
<h1>{{ topic.agenda_item.getTitle() }}</h1>
|
||||
<h2 translate>Topic</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="details">
|
||||
<div ng-bind-html="customslide.text | trusted"></div>
|
||||
<h3 ng-if="customslide.attachments.length > 0" translate>Attachments</h3>
|
||||
<div ng-bind-html="topic.text | trusted"></div>
|
||||
<h3 ng-if="topic.attachments.length > 0" translate>Attachments</h3>
|
||||
<ul>
|
||||
<li ng-repeat="attachment in customslide.attachments">
|
||||
<li ng-repeat="attachment in topic.attachments">
|
||||
<a href="{{ attachment.mediafileUrl }}" target="_blank">
|
||||
{{ attachment.title_or_filename }}
|
||||
</a>
|
@ -1,13 +1,13 @@
|
||||
<h1 ng-if="model.id" translate>Edit agenda item</h1>
|
||||
<h1 ng-if="!model.id" translate>New agenda item</h1>
|
||||
<h1 ng-if="model.id" translate>Edit topic</h1>
|
||||
<h1 ng-if="!model.id" translate>New topic</h1>
|
||||
|
||||
<div uib-alert ng-show="alert.show" ng-class="'alert-' + (alert.type || 'warning')" ng-click="alert={}" close="alert={}">
|
||||
{{ alert.msg }}
|
||||
</div>
|
||||
|
||||
<form name="customslideForm" ng-submit="save(model)">
|
||||
<form name="topicForm" ng-submit="save(model)">
|
||||
<formly-form model="model" fields="formFields">
|
||||
<button type="submit" ng-disabled="customslideForm.$invalid" class="btn btn-primary" translate>
|
||||
<button type="submit" ng-disabled="topicForm.$invalid" class="btn btn-primary" translate>
|
||||
Save
|
||||
</button>
|
||||
<button type="button" ng-click="closeThisDialog()" class="btn btn-default" translate>
|
@ -6,14 +6,13 @@
|
||||
<translate>Back to overview</translate>
|
||||
</a>
|
||||
</div>
|
||||
<h1 translate>Import agenda items</h1>
|
||||
<h1 translate>Import topics</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="details">
|
||||
<h2 translate>Import by copy/paste</h2>
|
||||
<p translate>Copy and paste your agenda item titles in this textbox.
|
||||
Keep each item in a single line.</p>
|
||||
<p translate>Copy and paste your topic titles in this textbox. Keep each item in a single line.</p>
|
||||
|
||||
<div class="row">
|
||||
<div class="form-group col-sm-6">
|
||||
@ -114,20 +113,20 @@ Keep each item in a single line.</p>
|
||||
<div ng-repeat="item in itemsFailed = (items | filter:{importerror:true})"></div>
|
||||
<i class="fa fa-exclamation-triangle"></i>
|
||||
{{ itemsFailed.length }}
|
||||
<translate>agenda items will be not imported.</translate>
|
||||
<translate>topics will be not imported.</translate>
|
||||
</div>
|
||||
<div>
|
||||
<div ng-repeat="item in itemsPassed = (items | filter:{importerror:false})"></div>
|
||||
<i class="fa fa-check-circle-o fa-lg"></i>
|
||||
{{ items.length - itemsFailed.length }}
|
||||
<translate>items will be imported.</translate>
|
||||
<translate>topics will be imported.</translate>
|
||||
</div>
|
||||
<div ng-repeat="item in itemsImported = (items | filter:{imported:true})"></div>
|
||||
<div ng-if="itemsImported.length > 0" class="text-success">
|
||||
<hr class="smallhr">
|
||||
<i class="fa fa-check-circle fa-lg"></i>
|
||||
{{ itemsImported.length }}
|
||||
<translate>items were successfully imported.</translate>
|
||||
<translate>topics were successfully imported.</translate>
|
||||
</div>
|
||||
|
||||
<div class="spacer">
|
||||
@ -135,7 +134,7 @@ Keep each item in a single line.</p>
|
||||
Clear preview
|
||||
</button>
|
||||
<button ng-if="!csvImporting" ng-click="import()" class="btn btn-primary" translate>
|
||||
Import {{ items.length - itemsFailed.length }} items
|
||||
Import {{ items.length - itemsFailed.length }} topics
|
||||
</button>
|
||||
</div>
|
||||
<div class="spacer">
|
25
openslides/topics/views.py
Normal file
25
openslides/topics/views.py
Normal file
@ -0,0 +1,25 @@
|
||||
from openslides.utils.rest_api import ModelViewSet
|
||||
|
||||
from .access_permissions import TopicAccessPermissions
|
||||
from .models import Topic
|
||||
|
||||
|
||||
class TopicViewSet(ModelViewSet):
|
||||
"""
|
||||
API endpoint for topics.
|
||||
|
||||
There are the following views: metadata, list, retrieve, create,
|
||||
partial_update, update and destroy.
|
||||
"""
|
||||
access_permissions = TopicAccessPermissions()
|
||||
queryset = Topic.objects.all()
|
||||
|
||||
def check_view_permissions(self):
|
||||
"""
|
||||
Returns True if the user has required permissions.
|
||||
"""
|
||||
if self.action in ('list', 'retrieve'):
|
||||
result = self.get_access_permissions().check_permissions(self.request.user)
|
||||
else:
|
||||
result = self.request.user.has_perm('agenda.can_manage')
|
||||
return result
|
@ -1,5 +1,5 @@
|
||||
from openslides.agenda.models import Item
|
||||
from openslides.core.models import CustomSlide
|
||||
from openslides.topics.models import Topic
|
||||
from openslides.utils.test import TestCase
|
||||
|
||||
|
||||
@ -9,7 +9,7 @@ class TestItemManager(TestCase):
|
||||
Test that get_root_and_children needs only one db query.
|
||||
"""
|
||||
for i in range(10):
|
||||
CustomSlide.objects.create(title='item{}'.format(i))
|
||||
Topic.objects.create(title='item{}'.format(i))
|
||||
|
||||
with self.assertNumQueries(1):
|
||||
Item.objects.get_root_and_children()
|
||||
|
@ -3,15 +3,15 @@ import json
|
||||
from rest_framework.test import APIClient
|
||||
|
||||
from openslides.agenda.models import Item
|
||||
from openslides.core.models import CustomSlide
|
||||
from openslides.topics.models import Topic
|
||||
from openslides.utils.test import TestCase
|
||||
|
||||
|
||||
class AgendaTreeTest(TestCase):
|
||||
def setUp(self):
|
||||
CustomSlide.objects.create(title='item1')
|
||||
item2 = CustomSlide.objects.create(title='item2').agenda_item
|
||||
item3 = CustomSlide.objects.create(title='item2a').agenda_item
|
||||
Topic.objects.create(title='item1')
|
||||
item2 = Topic.objects.create(title='item2').agenda_item
|
||||
item3 = Topic.objects.create(title='item2a').agenda_item
|
||||
item3.parent = item2
|
||||
item3.save()
|
||||
self.client = APIClient()
|
||||
@ -90,7 +90,7 @@ class TestAgendaPDF(TestCase):
|
||||
"""
|
||||
Tests that a requst on the pdf-page returns with statuscode 200.
|
||||
"""
|
||||
CustomSlide.objects.create(title='item1')
|
||||
Topic.objects.create(title='item1')
|
||||
self.client.login(username='admin', password='admin')
|
||||
|
||||
response = self.client.get('/agenda/print/')
|
||||
|
@ -5,7 +5,8 @@ from rest_framework.test import APIClient
|
||||
|
||||
from openslides.agenda.models import Item, Speaker
|
||||
from openslides.core.config import config
|
||||
from openslides.core.models import CustomSlide, Projector
|
||||
from openslides.core.models import Projector
|
||||
from openslides.topics.models import Topic
|
||||
from openslides.utils.test import TestCase
|
||||
|
||||
|
||||
@ -16,7 +17,7 @@ class RetrieveItem(TestCase):
|
||||
def setUp(self):
|
||||
self.client = APIClient()
|
||||
config['general_system_enable_anonymous'] = True
|
||||
self.item = CustomSlide.objects.create(title='test_title_Idais2pheepeiz5uph1c').agenda_item
|
||||
self.item = Topic.objects.create(title='test_title_Idais2pheepeiz5uph1c').agenda_item
|
||||
|
||||
def test_normal_by_anonymous_without_perm_to_see_hidden_items(self):
|
||||
group = get_user_model().groups.field.related_model.objects.get(pk=1) # Group with pk 1 is for anonymous users.
|
||||
@ -47,7 +48,7 @@ class ManageSpeaker(TestCase):
|
||||
self.client = APIClient()
|
||||
self.client.login(username='admin', password='admin')
|
||||
|
||||
self.item = CustomSlide.objects.create(title='test_title_aZaedij4gohn5eeQu8fe').agenda_item
|
||||
self.item = Topic.objects.create(title='test_title_aZaedij4gohn5eeQu8fe').agenda_item
|
||||
self.user = get_user_model().objects.create_user(
|
||||
username='test_user_jooSaex1bo5ooPhuphae',
|
||||
password='test_password_e6paev4zeeh9n')
|
||||
@ -164,7 +165,7 @@ class Speak(TestCase):
|
||||
def setUp(self):
|
||||
self.client = APIClient()
|
||||
self.client.login(username='admin', password='admin')
|
||||
self.item = CustomSlide.objects.create(title='test_title_KooDueco3zaiGhiraiho').agenda_item
|
||||
self.item = Topic.objects.create(title='test_title_KooDueco3zaiGhiraiho').agenda_item
|
||||
self.user = get_user_model().objects.create_user(
|
||||
username='test_user_Aigh4vohb3seecha4aa4',
|
||||
password='test_password_eneupeeVo5deilixoo8j')
|
||||
@ -273,19 +274,19 @@ class Numbering(TestCase):
|
||||
def setUp(self):
|
||||
self.client = APIClient()
|
||||
self.client.login(username='admin', password='admin')
|
||||
self.item_1 = CustomSlide.objects.create(title='test_title_thuha8eef7ohXar3eech').agenda_item
|
||||
self.item_1 = Topic.objects.create(title='test_title_thuha8eef7ohXar3eech').agenda_item
|
||||
self.item_1.type = Item.AGENDA_ITEM
|
||||
self.item_1.weight = 1
|
||||
self.item_1.save()
|
||||
self.item_2 = CustomSlide.objects.create(title='test_title_eisah7thuxa1eingaeLo').agenda_item
|
||||
self.item_2 = Topic.objects.create(title='test_title_eisah7thuxa1eingaeLo').agenda_item
|
||||
self.item_2.type = Item.AGENDA_ITEM
|
||||
self.item_2.weight = 2
|
||||
self.item_2.save()
|
||||
self.item_2_1 = CustomSlide.objects.create(title='test_title_Qui0audoaz5gie1phish').agenda_item
|
||||
self.item_2_1 = Topic.objects.create(title='test_title_Qui0audoaz5gie1phish').agenda_item
|
||||
self.item_2_1.type = Item.AGENDA_ITEM
|
||||
self.item_2_1.parent = self.item_2
|
||||
self.item_2_1.save()
|
||||
self.item_3 = CustomSlide.objects.create(title='test_title_ah7tphisheineisgaeLo').agenda_item
|
||||
self.item_3 = Topic.objects.create(title='test_title_ah7tphisheineisgaeLo').agenda_item
|
||||
self.item_3.type = Item.AGENDA_ITEM
|
||||
self.item_3.weight = 3
|
||||
self.item_3.save()
|
||||
|
@ -6,7 +6,8 @@ from rest_framework.test import APIClient
|
||||
|
||||
from openslides import __version__ as version
|
||||
from openslides.core.config import ConfigVariable, config
|
||||
from openslides.core.models import CustomSlide, Projector
|
||||
from openslides.core.models import Projector
|
||||
from openslides.topics.models import Topic
|
||||
from openslides.utils.rest_api import ValidationError
|
||||
from openslides.utils.test import TestCase
|
||||
|
||||
@ -17,10 +18,10 @@ class ProjectorAPI(TestCase):
|
||||
"""
|
||||
def test_slide_on_default_projector(self):
|
||||
self.client.login(username='admin', password='admin')
|
||||
customslide = CustomSlide.objects.create(title='title_que1olaish5Wei7que6i', text='text_aishah8Eh7eQuie5ooji')
|
||||
topic = Topic.objects.create(title='title_que1olaish5Wei7que6i', text='text_aishah8Eh7eQuie5ooji')
|
||||
default_projector = Projector.objects.get(pk=1)
|
||||
default_projector.config = {
|
||||
'aae4a07b26534cfb9af4232f361dce73': {'name': 'core/customslide', 'id': customslide.id}}
|
||||
'aae4a07b26534cfb9af4232f361dce73': {'name': 'topics/topic', 'id': topic.id}}
|
||||
default_projector.save()
|
||||
|
||||
response = self.client.get(reverse('projector-detail', args=['1']))
|
||||
@ -30,9 +31,9 @@ class ProjectorAPI(TestCase):
|
||||
'id': 1,
|
||||
'elements': {
|
||||
'aae4a07b26534cfb9af4232f361dce73':
|
||||
{'id': customslide.id,
|
||||
{'id': topic.id,
|
||||
'uuid': 'aae4a07b26534cfb9af4232f361dce73',
|
||||
'name': 'core/customslide'}},
|
||||
'name': 'topics/topic'}},
|
||||
'scale': 0,
|
||||
'scroll': 0,
|
||||
'width': 1024,
|
||||
|
@ -1,5 +1,5 @@
|
||||
from openslides.agenda.models import Item, Speaker
|
||||
from openslides.core.models import CustomSlide
|
||||
from openslides.topics.models import Topic
|
||||
from openslides.users.models import User
|
||||
from openslides.utils.exceptions import OpenSlidesError
|
||||
from openslides.utils.test import TestCase
|
||||
@ -7,8 +7,8 @@ from openslides.utils.test import TestCase
|
||||
|
||||
class ListOfSpeakerModelTests(TestCase):
|
||||
def setUp(self):
|
||||
self.item1 = CustomSlide.objects.create(title='item1').agenda_item
|
||||
self.item2 = CustomSlide.objects.create(title='item2').agenda_item
|
||||
self.item1 = Topic.objects.create(title='item1').agenda_item
|
||||
self.item2 = Topic.objects.create(title='item2').agenda_item
|
||||
self.speaker1 = User.objects.create(username='user1')
|
||||
self.speaker2 = User.objects.create(username='user2')
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user