Merge pull request #3336 from normanjaeckel/RemoveElementsProjector

Unproject elements when they are deleted. Fixed #3292.
This commit is contained in:
Norman Jäckel 2017-08-12 13:35:09 +02:00 committed by GitHub
commit 0c8bf5c5ba
10 changed files with 147 additions and 20 deletions

View File

@ -62,6 +62,7 @@ Core:
- Highlight list entries in a light blue, if a related object is projected - Highlight list entries in a light blue, if a related object is projected
(e. g. a list of speakers of a motion) [#3301]. (e. g. a list of speakers of a motion) [#3301].
- Select the projector resolution with a slider and an aspect ratio [#3311]. - Select the projector resolution with a slider and an aspect ratio [#3311].
- Fixed bug the elements are projected and the deleted [#3336].
- Delay the 'could not load projector' error 3 seconds to not irritate users - Delay the 'could not load projector' error 3 seconds to not irritate users
with a slow internet connection [#3323]. with a slow internet connection [#3323].
- Added config value for standard font size in PDF [#3332]. - Added config value for standard font size in PDF [#3332].

View File

@ -10,7 +10,7 @@ from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy from django.utils.translation import ugettext_lazy
from openslides.core.config import config from openslides.core.config import config
from openslides.core.models import Countdown from openslides.core.models import Countdown, Projector
from openslides.utils.exceptions import OpenSlidesError from openslides.utils.exceptions import OpenSlidesError
from openslides.utils.models import RESTModelMixin from openslides.utils.models import RESTModelMixin
from openslides.utils.utils import to_roman from openslides.utils.utils import to_roman
@ -284,6 +284,17 @@ class Item(RESTModelMixin, models.Model):
def __str__(self): def __str__(self):
return self.title return self.title
def delete(self, skip_autoupdate=False, *args, **kwargs):
"""
Customized method to delete an agenda item. Ensures that a respective
list of speakers projector element is disabled.
"""
Projector.remove_any(
skip_autoupdate=skip_autoupdate,
name='agenda/list-of-speakers',
id=self.pk)
return super().delete(skip_autoupdate=skip_autoupdate, *args, **kwargs)
@property @property
def title(self): def title(self):
""" """

View File

@ -34,6 +34,8 @@ def listen_to_related_object_post_delete(sender, instance, **kwargs):
if hasattr(instance, 'get_agenda_title'): if hasattr(instance, 'get_agenda_title'):
content_type = ContentType.objects.get_for_model(instance) content_type = ContentType.objects.get_for_model(instance)
try: try:
# Attention: This delete() call is also necessary to remove
# respective active list of speakers projector elements.
Item.objects.get(object_id=instance.pk, content_type=content_type).delete() Item.objects.get(object_id=instance.pk, content_type=content_type).delete()
except Item.DoesNotExist: except Item.DoesNotExist:
# Item does not exist so we do not have to delete it. # Item does not exist so we do not have to delete it.

View File

@ -8,7 +8,7 @@ from django.utils.translation import ugettext_noop
from openslides.agenda.models import Item, Speaker from openslides.agenda.models import Item, Speaker
from openslides.core.config import config from openslides.core.config import config
from openslides.core.models import Tag from openslides.core.models import Projector, Tag
from openslides.poll.models import ( from openslides.poll.models import (
BaseOption, BaseOption,
BasePoll, BasePoll,
@ -165,14 +165,16 @@ class Assignment(RESTModelMixin, models.Model):
def __str__(self): def __str__(self):
return self.title return self.title
def get_slide_context(self, **context): def delete(self, skip_autoupdate=False, *args, **kwargs):
""" """
Retuns the context to generate the assignment slide. Customized method to delete an assignment. Ensures that a respective
assignment projector element is disabled.
""" """
return super().get_slide_context( Projector.remove_any(
polls=self.polls.filter(published=True), skip_autoupdate=skip_autoupdate,
vote_results=self.vote_results(only_published=True), name='assignments/assignment',
**context) id=self.pk)
return super().delete(skip_autoupdate=skip_autoupdate, *args, **kwargs)
@property @property
def candidates(self): def candidates(self):
@ -415,6 +417,18 @@ class AssignmentPoll(RESTModelMixin, CollectDefaultVotesMixin,
class Meta: class Meta:
default_permissions = () default_permissions = ()
def delete(self, skip_autoupdate=False, *args, **kwargs):
"""
Customized method to delete an assignment poll. Ensures that a respective
assignment projector element (with poll, so called poll slide) is disabled.
"""
Projector.remove_any(
skip_autoupdate=skip_autoupdate,
name='assignments/assignment',
id=self.assignment.pk,
poll=self.pk)
return super().delete(skip_autoupdate=skip_autoupdate, *args, **kwargs)
def get_assignment(self): def get_assignment(self):
return self.assignment return self.assignment
@ -432,9 +446,6 @@ class AssignmentPoll(RESTModelMixin, CollectDefaultVotesMixin,
def get_percent_base_choice(self): def get_percent_base_choice(self):
return config['assignments_poll_100_percent_base'] return config['assignments_poll_100_percent_base']
def get_slide_context(self, **context):
return super().get_slide_context(poll=self)
def get_root_rest_element(self): def get_root_rest_element(self):
""" """
Returns the assignment to this instance which is the root REST element. Returns the assignment to this instance which is the root REST element.

View File

@ -192,6 +192,36 @@ class Projector(RESTModelMixin, models.Model):
return output return output
@classmethod
def remove_any(cls, skip_autoupdate=False, **kwargs):
"""
Removes all projector elements from all projectors with matching kwargs.
Additional properties of active projector elements are ignored:
Example: Sending {'name': 'assignments/assignment', 'id': 1} will remove
also projector elements with {'name': 'assignments/assignment', 'id': 1, 'poll': 2}.
"""
# Loop over all projectors.
for projector in cls.objects.all():
change_projector_config = False
projector_config = {}
# Loop over all projector elements of this projector.
for key, value in projector.config.items():
# Check if the kwargs match this element.
for kwarg_key, kwarg_value in kwargs.items():
if not value.get(kwarg_key) == kwarg_value:
# No match so the element should stay. Write it into
# new config field and break the loop.
projector_config[key] = value
break
else:
# All kwargs match this projector element. So mark this
# projector to be changed. Do not write it into new config field.
change_projector_config = True
if change_projector_config:
projector.config = projector_config
projector.save(skip_autoupdate=skip_autoupdate)
class ProjectionDefault(RESTModelMixin, models.Model): class ProjectionDefault(RESTModelMixin, models.Model):
""" """
@ -308,6 +338,17 @@ class ProjectorMessage(RESTModelMixin, models.Model):
class Meta: class Meta:
default_permissions = () default_permissions = ()
def delete(self, skip_autoupdate=False, *args, **kwargs):
"""
Customized method to delete a projector message. Ensures that a respective
projector message projector element is disabled.
"""
Projector.remove_any(
skip_autoupdate=skip_autoupdate,
name='core/projector-message',
id=self.pk)
return super().delete(skip_autoupdate=skip_autoupdate, *args, **kwargs)
class Countdown(RESTModelMixin, models.Model): class Countdown(RESTModelMixin, models.Model):
""" """
@ -326,6 +367,17 @@ class Countdown(RESTModelMixin, models.Model):
class Meta: class Meta:
default_permissions = () default_permissions = ()
def delete(self, skip_autoupdate=False, *args, **kwargs):
"""
Customized method to delete a countdown. Ensures that a respective
countdown projector element is disabled.
"""
Projector.remove_any(
skip_autoupdate=skip_autoupdate,
name='core/countdown',
id=self.pk)
return super().delete(skip_autoupdate=skip_autoupdate, *args, **kwargs)
def control(self, action): def control(self, action):
if action not in ('start', 'stop', 'reset'): if action not in ('start', 'stop', 'reset'):
raise ValueError("Action must be 'start', 'stop' or 'reset', not {}.".format(action)) raise ValueError("Action must be 'start', 'stop' or 'reset', not {}.".format(action))

View File

@ -1343,10 +1343,6 @@ angular.module('OpenSlidesApp.core.site', [
Countdown.create(countdown); Countdown.create(countdown);
}; };
$scope.removeCountdown = function (countdown) { $scope.removeCountdown = function (countdown) {
var isProjectedIds = countdown.isProjected();
_.forEach(isProjectedIds, function(id) {
countdown.project(id);
});
Countdown.destroy(countdown.id); Countdown.destroy(countdown.id);
}; };
@ -1359,10 +1355,6 @@ angular.module('OpenSlidesApp.core.site', [
ProjectorMessage.create(message); ProjectorMessage.create(message);
}; };
$scope.removeMessage = function (message) { $scope.removeMessage = function (message) {
var isProjectedIds = message.isProjected();
_.forEach(isProjectedIds, function(id) {
message.project(id);
});
ProjectorMessage.destroy(message.id); ProjectorMessage.destroy(message.id);
}; };

View File

@ -2,6 +2,7 @@ from django.conf import settings
from django.db import models from django.db import models
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from ..core.models import Projector
from ..utils.autoupdate import inform_changed_data from ..utils.autoupdate import inform_changed_data
from ..utils.models import RESTModelMixin from ..utils.models import RESTModelMixin
from .access_permissions import MediafileAccessPermissions from .access_permissions import MediafileAccessPermissions
@ -63,6 +64,17 @@ class Mediafile(RESTModelMixin, models.Model):
inform_changed_data(self.uploader) inform_changed_data(self.uploader)
return result return result
def delete(self, skip_autoupdate=False, *args, **kwargs):
"""
Customized method to delete a mediafile. Ensures that a respective
mediafile projector element is disabled.
"""
Projector.remove_any(
skip_autoupdate=skip_autoupdate,
name='mediafiles/mediafile',
id=self.pk)
return super().delete(skip_autoupdate=skip_autoupdate, *args, **kwargs)
def get_filesize(self): def get_filesize(self):
""" """
Transforms bytes to kilobytes or megabytes. Returns the size as string. Transforms bytes to kilobytes or megabytes. Returns the size as string.

View File

@ -10,7 +10,7 @@ from jsonfield import JSONField
from openslides.agenda.models import Item from openslides.agenda.models import Item
from openslides.core.config import config from openslides.core.config import config
from openslides.core.models import Tag from openslides.core.models import Projector, Tag
from openslides.mediafiles.models import Mediafile from openslides.mediafiles.models import Mediafile
from openslides.poll.models import ( from openslides.poll.models import (
BaseOption, BaseOption,
@ -300,6 +300,17 @@ class Motion(RESTModelMixin, models.Model):
if not skip_autoupdate: if not skip_autoupdate:
inform_changed_data(self) inform_changed_data(self)
def delete(self, skip_autoupdate=False, *args, **kwargs):
"""
Customized method to delete a motion. Ensures that a respective
motion projector element is disabled.
"""
Projector.remove_any(
skip_autoupdate=skip_autoupdate,
name='motions/motion',
id=self.pk)
return super().delete(skip_autoupdate=skip_autoupdate, *args, **kwargs)
def version_data_changed(self, version): def version_data_changed(self, version):
""" """
Compare the version with the last version of the motion. Compare the version with the last version of the motion.
@ -859,6 +870,17 @@ class MotionBlock(RESTModelMixin, models.Model):
def __str__(self): def __str__(self):
return self.title return self.title
def delete(self, skip_autoupdate=False, *args, **kwargs):
"""
Customized method to delete a motion block. Ensures that a respective
motion block projector element is disabled.
"""
Projector.remove_any(
skip_autoupdate=skip_autoupdate,
name='motions/motion-block',
id=self.pk)
return super().delete(skip_autoupdate=skip_autoupdate, *args, **kwargs)
@property @property
def agenda_item(self): def agenda_item(self):
""" """

View File

@ -2,6 +2,7 @@ from django.contrib.contenttypes.fields import GenericRelation
from django.db import models from django.db import models
from ..agenda.models import Item from ..agenda.models import Item
from ..core.models import Projector
from ..mediafiles.models import Mediafile from ..mediafiles.models import Mediafile
from ..utils.models import RESTModelMixin from ..utils.models import RESTModelMixin
from .access_permissions import TopicAccessPermissions from .access_permissions import TopicAccessPermissions
@ -42,6 +43,17 @@ class Topic(RESTModelMixin, models.Model):
def __str__(self): def __str__(self):
return self.title return self.title
def delete(self, skip_autoupdate=False, *args, **kwargs):
"""
Customized method to delete a topic. Ensures that a respective
topic projector element is disabled.
"""
Projector.remove_any(
skip_autoupdate=skip_autoupdate,
name='topics/topic',
id=self.pk)
return super().delete(skip_autoupdate=skip_autoupdate, *args, **kwargs)
@property @property
def agenda_item(self): def agenda_item(self):
""" """

View File

@ -13,6 +13,7 @@ from django.db import models
from django.db.models import Prefetch, Q from django.db.models import Prefetch, Q
from jsonfield import JSONField from jsonfield import JSONField
from ..core.models import Projector
from ..utils.collection import CollectionElement from ..utils.collection import CollectionElement
from ..utils.models import RESTModelMixin from ..utils.models import RESTModelMixin
from .access_permissions import ( from .access_permissions import (
@ -210,6 +211,17 @@ class User(RESTModelMixin, PermissionsMixin, AbstractBaseUser):
CollectionElement.from_instance(self) CollectionElement.from_instance(self)
return super().save(*args, **kwargs) return super().save(*args, **kwargs)
def delete(self, skip_autoupdate=False, *args, **kwargs):
"""
Customized method to delete an user. Ensures that a respective
user projector element is disabled.
"""
Projector.remove_any(
skip_autoupdate=skip_autoupdate,
name='users/user',
id=self.pk)
return super().delete(skip_autoupdate=skip_autoupdate, *args, **kwargs)
def has_perm(self, perm): def has_perm(self, perm):
""" """
This method is closed. Do not use it but use openslides.utils.auth.has_perm. This method is closed. Do not use it but use openslides.utils.auth.has_perm.