diff --git a/CHANGELOG b/CHANGELOG index 5d047bdd0..6141f7730 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -62,6 +62,7 @@ Core: - Highlight list entries in a light blue, if a related object is projected (e. g. a list of speakers of a motion) [#3301]. - 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 with a slow internet connection [#3323]. - Added config value for standard font size in PDF [#3332]. diff --git a/openslides/agenda/models.py b/openslides/agenda/models.py index dc2f83bb0..090668721 100644 --- a/openslides/agenda/models.py +++ b/openslides/agenda/models.py @@ -10,7 +10,7 @@ from django.utils.translation import ugettext as _ from django.utils.translation import ugettext_lazy 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.models import RESTModelMixin from openslides.utils.utils import to_roman @@ -284,6 +284,17 @@ class Item(RESTModelMixin, models.Model): def __str__(self): 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 def title(self): """ diff --git a/openslides/agenda/signals.py b/openslides/agenda/signals.py index bd6987ba3..68c572dd4 100644 --- a/openslides/agenda/signals.py +++ b/openslides/agenda/signals.py @@ -34,6 +34,8 @@ def listen_to_related_object_post_delete(sender, instance, **kwargs): if hasattr(instance, 'get_agenda_title'): content_type = ContentType.objects.get_for_model(instance) 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() except Item.DoesNotExist: # Item does not exist so we do not have to delete it. diff --git a/openslides/assignments/models.py b/openslides/assignments/models.py index 42d406211..7bdabd27a 100644 --- a/openslides/assignments/models.py +++ b/openslides/assignments/models.py @@ -8,7 +8,7 @@ from django.utils.translation import ugettext_noop from openslides.agenda.models import Item, Speaker from openslides.core.config import config -from openslides.core.models import Tag +from openslides.core.models import Projector, Tag from openslides.poll.models import ( BaseOption, BasePoll, @@ -165,14 +165,16 @@ class Assignment(RESTModelMixin, models.Model): def __str__(self): 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( - polls=self.polls.filter(published=True), - vote_results=self.vote_results(only_published=True), - **context) + Projector.remove_any( + skip_autoupdate=skip_autoupdate, + name='assignments/assignment', + id=self.pk) + return super().delete(skip_autoupdate=skip_autoupdate, *args, **kwargs) @property def candidates(self): @@ -415,6 +417,18 @@ class AssignmentPoll(RESTModelMixin, CollectDefaultVotesMixin, class Meta: 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): return self.assignment @@ -432,9 +446,6 @@ class AssignmentPoll(RESTModelMixin, CollectDefaultVotesMixin, def get_percent_base_choice(self): 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): """ Returns the assignment to this instance which is the root REST element. diff --git a/openslides/core/models.py b/openslides/core/models.py index af019b182..52a165e75 100644 --- a/openslides/core/models.py +++ b/openslides/core/models.py @@ -192,6 +192,36 @@ class Projector(RESTModelMixin, models.Model): 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): """ @@ -308,6 +338,17 @@ class ProjectorMessage(RESTModelMixin, models.Model): class Meta: 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): """ @@ -326,6 +367,17 @@ class Countdown(RESTModelMixin, models.Model): class Meta: 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): if action not in ('start', 'stop', 'reset'): raise ValueError("Action must be 'start', 'stop' or 'reset', not {}.".format(action)) diff --git a/openslides/core/static/js/core/site.js b/openslides/core/static/js/core/site.js index e369aa5ec..51821bb13 100644 --- a/openslides/core/static/js/core/site.js +++ b/openslides/core/static/js/core/site.js @@ -1343,10 +1343,6 @@ angular.module('OpenSlidesApp.core.site', [ Countdown.create(countdown); }; $scope.removeCountdown = function (countdown) { - var isProjectedIds = countdown.isProjected(); - _.forEach(isProjectedIds, function(id) { - countdown.project(id); - }); Countdown.destroy(countdown.id); }; @@ -1359,10 +1355,6 @@ angular.module('OpenSlidesApp.core.site', [ ProjectorMessage.create(message); }; $scope.removeMessage = function (message) { - var isProjectedIds = message.isProjected(); - _.forEach(isProjectedIds, function(id) { - message.project(id); - }); ProjectorMessage.destroy(message.id); }; diff --git a/openslides/mediafiles/models.py b/openslides/mediafiles/models.py index c5b0d6c5f..ccf56b46a 100644 --- a/openslides/mediafiles/models.py +++ b/openslides/mediafiles/models.py @@ -2,6 +2,7 @@ from django.conf import settings from django.db import models from django.utils.translation import ugettext as _ +from ..core.models import Projector from ..utils.autoupdate import inform_changed_data from ..utils.models import RESTModelMixin from .access_permissions import MediafileAccessPermissions @@ -63,6 +64,17 @@ class Mediafile(RESTModelMixin, models.Model): inform_changed_data(self.uploader) 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): """ Transforms bytes to kilobytes or megabytes. Returns the size as string. diff --git a/openslides/motions/models.py b/openslides/motions/models.py index 66508af1a..896b8ab35 100644 --- a/openslides/motions/models.py +++ b/openslides/motions/models.py @@ -10,7 +10,7 @@ from jsonfield import JSONField from openslides.agenda.models import Item 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.poll.models import ( BaseOption, @@ -300,6 +300,17 @@ class Motion(RESTModelMixin, models.Model): if not skip_autoupdate: 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): """ Compare the version with the last version of the motion. @@ -859,6 +870,17 @@ class MotionBlock(RESTModelMixin, models.Model): def __str__(self): 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 def agenda_item(self): """ diff --git a/openslides/topics/models.py b/openslides/topics/models.py index 58c59cb11..cfb2b6b41 100644 --- a/openslides/topics/models.py +++ b/openslides/topics/models.py @@ -2,6 +2,7 @@ from django.contrib.contenttypes.fields import GenericRelation from django.db import models from ..agenda.models import Item +from ..core.models import Projector from ..mediafiles.models import Mediafile from ..utils.models import RESTModelMixin from .access_permissions import TopicAccessPermissions @@ -42,6 +43,17 @@ class Topic(RESTModelMixin, models.Model): def __str__(self): 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 def agenda_item(self): """ diff --git a/openslides/users/models.py b/openslides/users/models.py index c110567eb..d2a39b4b4 100644 --- a/openslides/users/models.py +++ b/openslides/users/models.py @@ -13,6 +13,7 @@ from django.db import models from django.db.models import Prefetch, Q from jsonfield import JSONField +from ..core.models import Projector from ..utils.collection import CollectionElement from ..utils.models import RESTModelMixin from .access_permissions import ( @@ -210,6 +211,17 @@ class User(RESTModelMixin, PermissionsMixin, AbstractBaseUser): CollectionElement.from_instance(self) 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): """ This method is closed. Do not use it but use openslides.utils.auth.has_perm.