def sort(self, request): """ Sort agenda items. Also checks parent field to prevent hierarchical loops. """ nodes = request.data.get("nodes", []) parent_id = request.data.get("parent_id") items = [] with transaction.atomic(): for index, node in enumerate(nodes): item = Item.objects.get(pk=node["id"]) item.parent_id = parent_id item.weight = index item.save(skip_autoupdate=True) items.append(item) # Now check consistency. TODO: Try to use less DB queries. item = Item.objects.get(pk=node["id"]) ancestor = item.parent while ancestor is not None: if ancestor == item: raise ValidationError( { "detail": "There must not be a hierarchical loop. Please reload the page." } ) ancestor = ancestor.parent inform_changed_data(items) return Response({"detail": "The agenda has been sorted."})
def delete(self, *args, **kwargs): # Hotfix for #4491: Trigger extra autoupdate for motion so that the serializer # field vor motion change recommendations gets updated too. motion = self.motion result = super().delete(*args, **kwargs) inform_changed_data(motion) return result
def begin_speech(self): """ Let the user speak. Set the weight to None and the time to now. If anyone is still speaking, end his speech. """ try: current_speaker = (Speaker.objects.filter(item=self.item, end_time=None) .exclude(begin_time=None).get()) except Speaker.DoesNotExist: pass else: # Do not send an autoupdate for the countdown and the item. This is done # by saving the item and countdown later. current_speaker.end_speech(skip_autoupdate=True) self.weight = None self.begin_time = timezone.now() self.save() # Here, the item is saved and causes an autoupdate. if config['agenda_couple_countdown_and_speakers']: countdown, created = Countdown.objects.get_or_create(pk=1, defaults={ 'default_time': config['projector_default_countdown'], 'countdown_time': config['projector_default_countdown']}) if not created: countdown.control(action='reset', skip_autoupdate=True) countdown.control(action='start', skip_autoupdate=True) inform_changed_data(countdown) # Here, the autoupdate for the countdown is triggered.
def listen_to_related_object_post_save(sender, instance, created, **kwargs): """ Receiver function to create agenda items. It is connected to the signal django.db.models.signals.post_save during app loading. """ if created and hasattr(instance, 'get_agenda_title'): Item.objects.create(content_object=instance) inform_changed_data(False, instance)
def post(self, request, poll_id): # Get poll instance. vc = VoteCollector.objects.get(id=1) if vc.voting_mode == 'MotionPoll': poll_model = MotionPoll conn_model = MotionPollKeypadConnection else: poll_model = AssignmentPoll conn_model = AssignmentPollKeypadConnection try: poll = poll_model.objects.get(id=poll_id) except poll_model.DoesNotExist: return HttpResponse('') # Load json list from request body. votes = json.loads(request.body.decode('utf-8')) keypad_set = set() connections = [] for vote in votes: keypad_id = vote['id'] try: keypad = Keypad.objects.get(keypad_id=keypad_id) except Keypad.DoesNotExist: continue # Mark keypad as in range and update battery level. keypad.in_range = True keypad.battery_level = vote['bl'] keypad.save(skip_autoupdate=True) # Validate vote value. value = vote['value'] if value not in ('Y', 'N', 'A'): continue # Write poll keypad connection. try: conn = conn_model.objects.get(poll=poll, keypad=keypad) except conn_model.DoesNotExist: conn = conn_model(poll=poll, keypad=keypad) conn.serial_number = vote['sn'] conn.value = value if conn.pk: # Save updated connection. conn.save(skip_autoupdate=True) else: # Add new connection to bulk create list. connections.append(conn) keypad_set.add(keypad.id) # Bulk create connections. conn_model.objects.bulk_create(connections) # Trigger auto update. connections = conn_model.objects.filter(poll=poll, keypad_id__in=keypad_set) inform_changed_data(connections) return HttpResponse()
def test_change_one_element(self): topic = Topic.objects.create(title="test_topic") channel_layers[DEFAULT_CHANNEL_LAYER].flush() inform_changed_data(topic) channel_message = self.get_next_message("autoupdate.send_data", require=True) self.assertEqual(len(channel_message["elements"]), 1) self.assertEqual(channel_message["elements"][0]["collection_string"], "topics/topic")
def test_hidden_by_anonymous_with_manage_perms(self): group = Group.objects.get(pk=1) # Group with pk 1 is for anonymous users. permission_string = "agenda.can_manage" app_label, codename = permission_string.split(".") permission = Permission.objects.get( content_type__app_label=app_label, codename=codename ) group.permissions.add(permission) inform_changed_data(group) response = self.client.get(reverse("item-detail", args=[self.item.pk])) self.assertEqual(response.status_code, status.HTTP_200_OK)
def test_change_many_elements(self): topics = ( Topic.objects.create(title='test_topic1'), Topic.objects.create(title='test_topic2'), Topic.objects.create(title='test_topic3')) channel_layers[DEFAULT_CHANNEL_LAYER].flush() inform_changed_data(topics) channel_message = self.get_next_message('autoupdate.send_data', require=True) self.assertEqual(len(channel_message['elements']), 3)
def test_change_one_element(self): topic = Topic.objects.create(title='test_topic') channel_layers[DEFAULT_CHANNEL_LAYER].flush() inform_changed_data(topic) channel_message = self.get_next_message('autoupdate.send_data', require=True) self.assertEqual(len(channel_message['elements']), 1) self.assertEqual( channel_message['elements'][0]['collection_string'], 'topics/topic')
def test_add_someone_else_non_admin(self): admin = get_user_model().objects.get(username="******") group_admin = admin.groups.get(name="Admin") group_delegates = type(group_admin).objects.get(name="Delegates") admin.groups.add(group_delegates) admin.groups.remove(group_admin) inform_changed_data(admin) response = self.client.post( reverse("item-manage-speaker", args=[self.item.pk]), {"user": self.user.pk} ) self.assertEqual(response.status_code, 403)
def nominate_self(self, request, assignment): if assignment.phase == assignment.PHASE_FINISHED: raise ValidationError({'detail': _('You can not candidate to this election because it is finished.')}) if assignment.phase == assignment.PHASE_VOTING and not has_perm(request.user, 'assignments.can_manage'): # To nominate self during voting you have to be a manager. self.permission_denied(request) # If the request.user is already a candidate he can nominate himself nevertheless. assignment.set_candidate(request.user) # Send new candidate via autoupdate because users without permission # to see users may not have it but can get it now. inform_changed_data([request.user]) return _('You were nominated successfully.')
def create_poll(self): """ Creates a new poll for the assignment and adds all candidates to all lists of speakers of related agenda items. """ candidates = self.candidates.all() # Find out the method of the election if config["assignments_poll_vote_values"] == "votes": pollmethod = "votes" elif config["assignments_poll_vote_values"] == "yesnoabstain": pollmethod = "yna" elif config["assignments_poll_vote_values"] == "yesno": pollmethod = "yn" else: # config['assignments_poll_vote_values'] == 'auto' # candidates <= available posts -> yes/no/abstain if len(candidates) <= (self.open_posts - self.elected.count()): pollmethod = "yna" else: pollmethod = "votes" # Create the poll with the candidates. poll = self.polls.create( description=self.poll_description_default, pollmethod=pollmethod ) options = [] related_users = AssignmentRelatedUser.objects.filter( assignment__id=self.id ).exclude(elected=True) for related_user in related_users: options.append( {"candidate": related_user.user, "weight": related_user.weight} ) poll.set_options(options, skip_autoupdate=True) inform_changed_data(self) # Add all candidates to list of speakers of related agenda item # TODO: Try to do this in a bulk create if config["assignments_add_candidates_to_list_of_speakers"]: for candidate in self.candidates: try: Speaker.objects.add( candidate, self.agenda_item, skip_autoupdate=True ) except OpenSlidesError: # The Speaker is already on the list. Do nothing. # TODO: Find a smart way not to catch the error concerning AnonymousUser. pass inform_changed_data(self.agenda_item) return poll
def test_change_only_non_root_rest_element(self): """ Tests that if only a non root_rest_element is called, then only the root_rest_element is in the channel. """ assignment = Assignment.objects.create(title='test_assignment', open_posts=1) poll = assignment.create_poll() channel_layers[DEFAULT_CHANNEL_LAYER].flush() inform_changed_data(poll) channel_message = self.get_next_message('autoupdate.send_data', require=True) self.assertEqual(len(channel_message['elements']), 1)
def test_change_with_non_root_rest_elements(self): """ Tests that if an root_rest_element is called together with one of its child elements, then there is only the root_rest_element in the channel message. """ assignment = Assignment.objects.create(title='test_assignment', open_posts=1) poll = assignment.create_poll() channel_layers[DEFAULT_CHANNEL_LAYER].flush() inform_changed_data((assignment, poll)) channel_message = self.get_next_message('autoupdate.send_data', require=True) self.assertEqual(len(channel_message['elements']), 1)
def test_change_no_autoupdate_model(self): """ Tests that if inform_changed_data() is called with a model that does not support autoupdate, nothing happens. """ group = Group.objects.create(name="test_group") channel_layers[DEFAULT_CHANNEL_LAYER].flush() inform_changed_data(group) with self.assertRaises(AssertionError): # self.get_next_message() with require=True raises a AssertionError # if there is no message in the channel self.get_next_message("autoupdate.send_data", require=True)
def test_get_with_user_without_permissions(self): group = Group.objects.get(pk=1) permission_string = "users.can_see_name" app_label, codename = permission_string.split(".") permission = group.permissions.get( content_type__app_label=app_label, codename=codename ) group.permissions.remove(permission) inform_changed_data(group) config["general_system_enable_anonymous"] = True guest_client = APIClient() response = guest_client.get("/rest/users/user/1/") self.assertEqual(response.status_code, 404)
def test_nominate_self_during_voting_non_admin(self): self.assignment.set_phase(Assignment.PHASE_VOTING) self.assignment.save() admin = get_user_model().objects.get(username="******") group_admin = admin.groups.get(name="Admin") group_delegates = type(group_admin).objects.get(name="Delegates") admin.groups.add(group_delegates) admin.groups.remove(group_admin) inform_changed_data(admin) response = self.client.post( reverse("assignment-candidature-self", args=[self.assignment.pk]) ) self.assertEqual(response.status_code, 403)
def test_delete_other_during_voting_non_admin(self): self.assignment.set_candidate(self.user) self.assignment.set_phase(Assignment.PHASE_VOTING) self.assignment.save() admin = get_user_model().objects.get(username='******') group_staff = admin.groups.get(name='Staff') group_delegates = type(group_staff).objects.get(name='Delegates') admin.groups.add(group_delegates) admin.groups.remove(group_staff) inform_changed_data(admin) response = self.client.delete( reverse('assignment-candidature-other', args=[self.assignment.pk]), {'user': self.user.pk}) self.assertEqual(response.status_code, 403)
def save(self, *args, **kwargs): recommendations = MotionChangeRecommendation.objects.filter( motion=self.motion).exclude(pk=self.pk) if self.collides_with_other_recommendation(recommendations): raise ValidationError( f"The recommendation collides with an existing one (line {self.line_from} - {self.line_to})." ) result = super().save(*args, **kwargs) # Hotfix for #4491: Trigger extra autoupdate for motion so that the serializer # field vor motion change recommendations gets updated too. inform_changed_data(self.motion) return result
def test_withdraw_self_during_voting_non_admin(self): self.assignment.set_candidate( get_user_model().objects.get(username='******')) self.assignment.set_phase(Assignment.PHASE_VOTING) self.assignment.save() admin = get_user_model().objects.get(username='******') group_admin = admin.groups.get(name='Admin') group_delegates = type(group_admin).objects.get(name='Delegates') admin.groups.add(group_delegates) admin.groups.remove(group_admin) inform_changed_data(admin) response = self.client.delete( reverse('assignment-candidature-self', args=[self.assignment.pk])) self.assertEqual(response.status_code, 403)
def nominate_other(self, request, user, assignment): if assignment.is_elected(user): raise ValidationError({'detail': _('User %s is already elected.') % user}) if assignment.phase == assignment.PHASE_FINISHED: detail = _('You can not nominate someone to this election because it is finished.') raise ValidationError({'detail': detail}) if assignment.phase == assignment.PHASE_VOTING and not has_perm(request.user, 'assignments.can_manage'): # To nominate another user during voting you have to be a manager. self.permission_denied(request) if assignment.is_candidate(user): raise ValidationError({'detail': _('User %s is already nominated.') % user}) assignment.set_candidate(user) # Send new candidate via autoupdate because users without permission # to see users may not have it but can get it now. inform_changed_data(user) return _('User %s was nominated successfully.') % user
def test_change_with_non_root_rest_elements(self): """ Tests that if an root_rest_element is called together with one of its child elements, then there is only the root_rest_element in the channel message. """ assignment = Assignment.objects.create(title='test_assignment', open_posts=1) poll = assignment.create_poll() channel_layers[DEFAULT_CHANNEL_LAYER].flush() inform_changed_data((assignment, poll)) channel_message = self.get_next_message('autoupdate.send_data', require=True) self.assertEqual(len(channel_message['elements']), 1)
def test_set_no_manage_perms(self): admin = User.objects.get() admin.groups.add(GROUP_DELEGATE_PK) admin.groups.remove(GROUP_ADMIN_PK) inform_changed_data(admin) response = self.admin_client.post( reverse("user_setpassword"), { "old_password": "******", "new_password": "******", }, ) self.assertEqual(response.status_code, status.HTTP_200_OK) admin = User.objects.get() self.assertTrue( admin.check_password("new_password_ou0wei3tae5ahr7oa1Fu"))
def nominate_self(self, request, assignment): if assignment.phase == assignment.PHASE_FINISHED: raise ValidationError({ "detail": "You can not candidate to this election because it is finished." }) if assignment.phase == assignment.PHASE_VOTING and not has_perm( request.user, "assignments.can_manage"): # To nominate self during voting you have to be a manager. self.permission_denied(request) # If the request.user is already a candidate he can nominate himself nevertheless. assignment.add_candidate(request.user) # Send new candidate via autoupdate because users without permission # to see users may not have it but can get it now. inform_changed_data([request.user]) return "You were nominated successfully."
def listen_to_related_object_post_save(sender, instance, created, **kwargs): """ Receiver function to create agenda items. It is connected to the signal django.db.models.signals.post_save during app loading. Do not run caching and autoupdate if the instance as an attribute skip_autoupdate (regardless of its truthy or falsy conent). """ if hasattr(instance, 'get_agenda_title'): if created: # If the object is created, the related_object has to be sent again. Item.objects.create(content_object=instance) if not hasattr(instance, 'skip_autoupdate'): inform_changed_data(instance) elif not hasattr(instance, 'skip_autoupdate'): # If the object has changed, then also the agenda item has to be sent. inform_changed_data(instance.agenda_item)
def save(self, *args, **kwargs): recommendations = MotionChangeRecommendation.objects.filter( motion=self.motion ).exclude(pk=self.pk) if self.collides_with_other_recommendation(recommendations): raise ValidationError( f"The recommendation collides with an existing one (line {self.line_from} - {self.line_to})." ) result = super().save(*args, **kwargs) # Hotfix for #4491: Trigger extra autoupdate for motion so that the serializer # field vor motion change recommendations gets updated too. inform_changed_data(self.motion) return result
def test_delete_other_during_voting_non_admin(self): self.assignment.set_candidate(self.user) self.assignment.set_phase(Assignment.PHASE_VOTING) self.assignment.save() admin = get_user_model().objects.get(username="******") group_admin = admin.groups.get(name="Admin") group_delegates = type(group_admin).objects.get(name="Delegates") admin.groups.add(group_delegates) admin.groups.remove(group_admin) inform_changed_data(admin) response = self.client.delete( reverse("assignment-candidature-other", args=[self.assignment.pk]), {"user": self.user.pk}, ) self.assertEqual(response.status_code, 403)
def stop(self, request, pk): poll = self.get_object() # Analog polls could not be stopped; they are stopped when # the results are entered. if poll.type == BasePoll.TYPE_ANALOG: raise ValidationError( {"detail": "Analog polls can not be stopped. Please enter votes."} ) if poll.state != BasePoll.STATE_STARTED: raise ValidationError({"detail": "Wrong poll state"}) poll.state = BasePoll.STATE_FINISHED poll.save() inform_changed_data(poll.get_votes()) inform_changed_data(poll.get_options()) return Response()
def stop(self, request, pk): poll = self.get_locked_object() # Analog polls cannot be stopped; they are stopped when # the results are entered. if poll.type == BasePoll.TYPE_ANALOG: raise ValidationError({ "detail": "Analog polls can not be stopped. Please enter votes." }) if poll.state != BasePoll.STATE_STARTED: raise ValidationError({"detail": "Wrong poll state"}) poll.stop() inform_changed_data(poll.get_votes()) inform_changed_data(poll.get_options()) self.extend_history_information(["Voting stopped"]) return Response()
def test_add_without_permission(self): admin = get_user_model().objects.get(username="******") admin.groups.add(GROUP_DELEGATE_PK) admin.groups.remove(GROUP_ADMIN_PK) inform_changed_data(admin) response = self.client.post( reverse("motion-manage-multiple-submitters"), { "motions": [{ "id": self.motion1.id, "submitters": [self.admin.pk] }] }, ) self.assertEqual(response.status_code, 403) self.assertEqual(self.motion1.submitters.count(), 0) self.assertEqual(self.motion2.submitters.count(), 0)
def test_non_admin(self): """ Test to create a motion by a delegate, non staff user. """ self.admin = get_user_model().objects.get(username="******") self.admin.groups.add(GROUP_DELEGATE_PK) self.admin.groups.remove(GROUP_ADMIN_PK) inform_changed_data(self.admin) response = self.client.post( reverse("motion-list"), { "title": "test_title_peiJozae0luew9EeL8bo", "text": "test_text_eHohS8ohr5ahshoah8Oh", }, ) self.assertEqual(response.status_code, status.HTTP_201_CREATED)
def update_keypads_from_votes(self, votes, voting_type): """ Updates the keypds from votes. The voting type has to be a VoteCollector one. The votes has to be validated first. """ if voting_type.startswith('votecollector'): keypads = [] for vote in votes: keypad = vote['keypad'] # Mark keypad as in range and update battery level. if keypad: keypad.in_range = True keypad.battery_level = vote['bl'] keypad.save(skip_autoupdate=True) keypads.append(keypad) # Trigger auto-update for keypads. inform_changed_data(keypads)
def create_votes_types_yn_yna(self, data, poll, check_user, vote_user): """ check_user is used for the voted-array and weight of the vote, vote_user is the one put into the vote """ options = poll.get_options() weight = (check_user.vote_weight if config["users_activate_vote_weight"] else Decimal(1)) for option_id, result in data.items(): option = options.get(pk=option_id) vote = AssignmentVote.objects.create(option=option, user=vote_user, value=result, weight=weight) inform_changed_data(vote, no_delete_on_restriction=True) inform_changed_data(option, no_delete_on_restriction=True) poll.voted.add(check_user)
def create_votes_type_named_pseudoanonymous(self, data, poll, check_user, vote_user): """ check_user is used for the voted-array and weight of the vote, vote_user is the one put into the vote """ options = poll.get_options() for option_id, result in data.items(): option = options.get(pk=option_id) vote = AssignmentVote.objects.create( option=option, user=vote_user, value=result, weight=check_user.vote_weight, ) inform_changed_data(vote, no_delete_on_restriction=True) inform_changed_data(option, no_delete_on_restriction=True) poll.voted.add(check_user)
def assert_can_vote(self, poll, request, vote_user): """ Raises a permission denied, if the user is not allowed to vote (or has already voted). Adds the user to the voted array, so this needs to be reverted if a later error happens! Analog: has to have manage permissions Named & Pseudoanonymous: has to be in a poll group and present """ # if the request user is not the vote user, the delegation must be right if request.user != vote_user and request.user != vote_user.vote_delegated_to: raise ValidationError( { "detail": f"You cannot vote for {vote_user.id} since the vote right was not delegated to you." } ) # If the request user is the vote user, this user must not have any delegation. # It is not allowed to vote for oneself, if the vote is delegated if request.user == vote_user and request.user.vote_delegated_to is not None: raise ValidationError( {"detail": "You cannot vote since your vote right is delegated."} ) if poll.type == BasePoll.TYPE_ANALOG: if not self.has_manage_permissions(): self.permission_denied(request) else: if poll.state != BasePoll.STATE_STARTED: raise ValidationError( {"detail": "You can only vote on a started poll."} ) if not request.user.is_present or not in_some_groups( vote_user.id, list(poll.groups.values_list("pk", flat=True)), exact=True, ): self.permission_denied(request) try: self.add_user_to_voted_array(vote_user, poll) inform_changed_data(poll) except IntegrityError: raise ValidationError({"detail": "You have already voted."})
def sort_related_users(self, request, pk=None): """ Special view endpoint to sort the assignment related users. Expects a list of IDs of the related users (pk of AssignmentRelatedUser model). """ assignment = self.get_object() # Check data related_user_ids = request.data.get("related_users") if not isinstance(related_user_ids, list): raise ValidationError({"detail": "users has to be a list of IDs."}) # Get all related users from AssignmentRelatedUser. related_users = {} for related_user in AssignmentRelatedUser.objects.filter( assignment__id=assignment.id ): related_users[related_user.pk] = related_user # Check all given candidates from the request valid_related_users = [] for related_user_id in related_user_ids: if ( not isinstance(related_user_id, int) or related_users.get(related_user_id) is None ): raise ValidationError({"detail": "Invalid data."}) valid_related_users.append(related_users[related_user_id]) # Sort the related users weight = 1 with transaction.atomic(): for valid_related_user in valid_related_users: valid_related_user.weight = weight valid_related_user.save(skip_autoupdate=True) weight += 1 # send autoupdate inform_changed_data(assignment) # Initiate response. return Response({"detail": "Assignment related users successfully sorted."})
def sort_related_users(self, request, pk=None): """ Special view endpoint to sort the assignment related users. Expects a list of IDs of the related users (pk of AssignmentRelatedUser model). """ assignment = self.get_object() # Check data related_user_ids = request.data.get("related_users") if not isinstance(related_user_ids, list): raise ValidationError({"detail": "users has to be a list of IDs."}) # Get all related users from AssignmentRelatedUser. related_users = {} for related_user in AssignmentRelatedUser.objects.filter( assignment__id=assignment.id ): related_users[related_user.pk] = related_user # Check all given candidates from the request valid_related_users = [] for related_user_id in related_user_ids: if ( not isinstance(related_user_id, int) or related_users.get(related_user_id) is None ): raise ValidationError({"detail": "Invalid data."}) valid_related_users.append(related_users[related_user_id]) # Sort the related users weight = 1 with transaction.atomic(): for valid_related_user in valid_related_users: valid_related_user.weight = weight valid_related_user.save(skip_autoupdate=True) weight += 1 # send autoupdate inform_changed_data(assignment) # Initiate response. return Response({"detail": "Assignment related users successfully sorted."})
def handle_analog_vote(self, data, poll, user): for field in ["votesvalid", "votesinvalid", "votescast"]: setattr(poll, field, data[field]) global_no_enabled = (poll.global_no and poll.pollmethod == AssignmentPoll.POLLMETHOD_VOTES) if global_no_enabled: poll.amount_global_no = data.get("amount_global_no", Decimal(0)) global_abstain_enabled = (poll.global_abstain and poll.pollmethod == AssignmentPoll.POLLMETHOD_VOTES) if global_abstain_enabled: poll.amount_global_abstain = data.get("amount_global_abstain", Decimal(0)) options = poll.get_options() options_data = data.get("options") with transaction.atomic(): for option_id, vote in options_data.items(): option = options.get(pk=int(option_id)) vote_obj, _ = AssignmentVote.objects.get_or_create( option=option, value="Y") vote_obj.weight = vote["Y"] vote_obj.save() if poll.pollmethod in ( AssignmentPoll.POLLMETHOD_YN, AssignmentPoll.POLLMETHOD_YNA, ): vote_obj, _ = AssignmentVote.objects.get_or_create( option=option, value="N") vote_obj.weight = vote["N"] vote_obj.save() if poll.pollmethod == AssignmentPoll.POLLMETHOD_YNA: vote_obj, _ = AssignmentVote.objects.get_or_create( option=option, value="A") vote_obj.weight = vote["A"] vote_obj.save() inform_changed_data(option) poll.save()
def begin_speech(self): """ Let the user speak. Set the weight to None and the time to now. If anyone is still speaking, end his speech. """ try: current_speaker = ( Speaker.objects.filter( list_of_speakers=self.list_of_speakers, end_time=None ) .exclude(begin_time=None) .get() ) except Speaker.DoesNotExist: pass else: # Do not send an autoupdate for the countdown and the list_of_speakers. This is done # by saving the list_of_speakers and countdown later. current_speaker.end_speech(skip_autoupdate=True) self.weight = None self.begin_time = timezone.now() self.save() # Here, the list_of_speakers is saved and causes an autoupdate. if config["agenda_couple_countdown_and_speakers"]: countdown, created = Countdown.objects.get_or_create( pk=1, defaults={ "default_time": config["projector_default_countdown"], "title": "Default countdown", "countdown_time": config["projector_default_countdown"], }, ) if created: restart_id_sequence("core_countdown") else: countdown.control(action="reset", skip_autoupdate=True) countdown.control(action="start", skip_autoupdate=True) inform_changed_data( countdown ) # Here, the autoupdate for the countdown is triggered.
def save(self, skip_autoupdate=False, *args, **kwargs): """ Save the motion. 1. Set the state of a new motion to the default state. 2. Ensure that the identifier is not an empty string. 3. Save the motion object. """ if not self.state: self.reset_state() # Solves the problem, that there can only be one motion with an empty # string as identifier. if not self.identifier and isinstance(self.identifier, str): self.identifier = None # Try to save the motion until it succeeds with a correct identifier. while True: try: # Always skip autoupdate. Maybe we run it later in this method. with transaction.atomic(): super(Motion, self).save(skip_autoupdate=True, *args, **kwargs) # type: ignore except IntegrityError: # Identifier is already used. if hasattr(self, '_identifier_prefix'): # Calculate a new one and try again. self.identifier_number, self.identifier = self.increment_identifier_number( self.identifier_number, self._identifier_prefix, ) else: # Do not calculate a new one but reraise the IntegrityError. # The error is caught in the category sort view. raise else: # Save was successful. End loop. break if not skip_autoupdate: inform_changed_data(self)
def test_retrieve_non_manager_with_read_permission(self): """ Checks, if the sections can be seen by a non manager, but he is in one of the read_groups. """ self.admin.groups.remove( self.group_in) # group_in has motions.can_manage permission self.admin.groups.add(self.group_out) # group_out does not. inform_changed_data(self.admin) section = MotionCommentSection(name="test_name_f3mMD28LMcm29Coelwcm") section.save() section.read_groups.add(self.group_out, self.group_in) inform_changed_data(section) response = self.client.get(reverse("motioncommentsection-list")) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(len(response.data), 1) self.assertEqual(response.data[0]["name"], "test_name_f3mMD28LMcm29Coelwcm")
def test_patch_amendment_paragraphs_no_manage_perms(self): admin = get_user_model().objects.get(username="******") admin.groups.remove(GROUP_ADMIN_PK) admin.groups.add(GROUP_DELEGATE_PK) Submitter.objects.add(admin, self.motion) self.motion.state.allow_submitter_edit = True self.motion.state.save() inform_changed_data(admin) response = self.client.patch( reverse("motion-detail", args=[self.motion.pk]), {"amendment_paragraphs": ["test_paragraph_39fo8qcpcaFMmjfaD2Lb"]}, ) self.assertEqual(response.status_code, status.HTTP_200_OK) motion = Motion.objects.get() self.assertTrue(isinstance(motion.amendment_paragraphs, list)) self.assertEqual(len(motion.amendment_paragraphs), 1) self.assertEqual(motion.amendment_paragraphs[0], "test_paragraph_39fo8qcpcaFMmjfaD2Lb") self.assertEqual(motion.text, "")
def save(self, skip_autoupdate=False, *args, **kwargs): """ Save the motion. 1. Set the state of a new motion to the default state. 2. Ensure that the identifier is not an empty string. 3. Save the motion object. """ if not self.state: self.reset_state() # Solves the problem, that there can only be one motion with an empty # string as identifier. if not self.identifier and isinstance(self.identifier, str): self.identifier = None # Try to save the motion until it succeeds with a correct identifier. while True: try: # Always skip autoupdate. Maybe we run it later in this method. with transaction.atomic(): super(Motion, self).save( # type: ignore skip_autoupdate=True, *args, **kwargs ) except IntegrityError: # Identifier is already used. if hasattr(self, "_identifier_prefix"): # Calculate a new one and try again. self.identifier_number, self.identifier = self.increment_identifier_number( self.identifier_number, self._identifier_prefix ) else: # Do not calculate a new one but reraise the IntegrityError. # The error is caught in the category sort view. raise else: # Save was successful. End loop. break if not skip_autoupdate: inform_changed_data(self)
def test_removal_of_supporters(self): admin = get_user_model().objects.get(username='******') group_staff = admin.groups.get(name='Staff') admin.groups.remove(group_staff) inform_changed_data(admin) self.motion.submitters.add(admin) supporter = get_user_model().objects.create_user( username='******', password='******') self.motion.supporters.add(supporter) config['motions_remove_supporters'] = True self.assertEqual(self.motion.supporters.count(), 1) response = self.client.patch( reverse('motion-detail', args=[self.motion.pk]), {'title': 'new_title_ohph1aedie5Du8sai2ye'}) self.assertEqual(response.status_code, status.HTTP_200_OK) motion = Motion.objects.get() self.assertEqual(motion.title, 'new_title_ohph1aedie5Du8sai2ye') self.assertEqual(motion.supporters.count(), 0)
def create(self, validated_data): """ Customized create method. Set information about related agenda item into agenda_item_update_information container. """ agenda_type = validated_data.pop("agenda_type", None) agenda_parent_id = validated_data.pop("agenda_parent_id", None) agenda_comment = validated_data.pop("agenda_comment", None) agenda_duration = validated_data.pop("agenda_duration", None) agenda_weight = validated_data.pop("agenda_weight", None) attachments = validated_data.pop("attachments", []) topic = Topic(**validated_data) topic.agenda_item_update_information["type"] = agenda_type topic.agenda_item_update_information["parent_id"] = agenda_parent_id topic.agenda_item_update_information["comment"] = agenda_comment topic.agenda_item_update_information["duration"] = agenda_duration topic.agenda_item_update_information["weight"] = agenda_weight topic.save(skip_autoupdate=True) topic.attachments.add(*attachments) inform_changed_data(topic) return topic
def nominate_other(self, request, user, assignment): if assignment.is_elected(user): raise ValidationError( {'detail': _('User %s is already elected.') % user}) if assignment.phase == assignment.PHASE_FINISHED: detail = _( 'You can not nominate someone to this election because it is finished.' ) raise ValidationError({'detail': detail}) if assignment.phase == assignment.PHASE_VOTING and not has_perm( request.user, 'assignments.can_manage'): # To nominate another user during voting you have to be a manager. self.permission_denied(request) if assignment.is_candidate(user): raise ValidationError( {'detail': _('User %s is already nominated.') % user}) assignment.set_candidate(user) # Send new candidate via autoupdate because users without permission # to see users may not have it but can get it now. inform_changed_data(user) return _('User %s was nominated successfully.') % user
def test_set_no_can_change_password(self): admin = User.objects.get() admin.groups.add(GROUP_DELEGATE_PK) admin.groups.remove(GROUP_ADMIN_PK) can_change_password_permission = Permission.objects.get( content_type__app_label="users", codename="can_change_password") delegate_group = Group.objects.get(pk=GROUP_DELEGATE_PK) delegate_group.permissions.remove(can_change_password_permission) inform_changed_data(delegate_group) inform_changed_data(admin) response = self.admin_client.post( reverse("user_setpassword"), { "old_password": "******", "new_password": "******", }, ) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) admin = User.objects.get() self.assertTrue(admin.check_password("admin"))
def post(self, request): # Load json list from request body. votes = json.loads(request.body.decode('utf-8')) keypads = [] for vote in votes: keypad_id = vote['id'] try: keypad = Keypad.objects.get(keypad_id=keypad_id) except Keypad.DoesNotExist: continue # Mark keypad as in range and update battery level. keypad.in_range = True keypad.battery_level = vote['bl'] keypad.save(skip_autoupdate=True) keypads.append(keypad) # Trigger auto-update. inform_changed_data(keypads) return HttpResponse()
def create(self, validated_data): """ Customized create method. Set information about related agenda item into agenda_item_update_information container. """ agenda_type = validated_data.pop("agenda_type", None) agenda_parent_id = validated_data.pop("agenda_parent_id", None) agenda_comment = validated_data.pop("agenda_comment", None) agenda_duration = validated_data.pop("agenda_duration", None) agenda_weight = validated_data.pop("agenda_weight", None) attachments = validated_data.pop("attachments", []) topic = Topic(**validated_data) topic.agenda_item_update_information["type"] = agenda_type topic.agenda_item_update_information["parent_id"] = agenda_parent_id topic.agenda_item_update_information["comment"] = agenda_comment topic.agenda_item_update_information["duration"] = agenda_duration topic.agenda_item_update_information["weight"] = agenda_weight topic.save(skip_autoupdate=True) topic.attachments.add(*attachments) inform_changed_data(topic) return topic
def post(self, request): # Load json list from request body. votes = json.loads(request.body.decode('utf-8')) keypads = [] for vote in votes: keypad_id = vote['id'] try: keypad = Keypad.objects.get(keypad_id=keypad_id) except Keypad.DoesNotExist: continue # Mark keypad as in range and update battery level. keypad.in_range = True keypad.battery_level = vote['bl'] keypad.save(skip_autoupdate=True) keypads.append(keypad) # Trigger auto-update. inform_changed_data(keypads) return HttpResponse()
def degrade_admin(self, can_manage_config=False, can_manage_logos_and_fonts=False): admin = get_user_model().objects.get(username="******") admin.groups.remove(GROUP_ADMIN_PK) admin.groups.add(GROUP_DELEGATE_PK) if can_manage_config or can_manage_logos_and_fonts: delegate_group = get_group_model().objects.get(pk=GROUP_DELEGATE_PK) if can_manage_config: delegate_group.permissions.add( Permission.objects.get( content_type__app_label="core", codename="can_manage_config" ) ) if can_manage_logos_and_fonts: delegate_group.permissions.add( Permission.objects.get( content_type__app_label="core", codename="can_manage_logos_and_fonts", ) ) inform_changed_data(delegate_group) inform_changed_data(admin)
def setUp(self): self.client = APIClient() self.client.login(username="******", password="******") self.admin = get_user_model().objects.get() self.group_out = get_group_model().objects.get( pk=GROUP_DELEGATE_PK ) # The admin should not be in this group # Put the admin into the staff group, becaust in the admin group, he has all permissions for # every single comment section. self.admin.groups.add(GROUP_STAFF_PK) self.admin.groups.remove(GROUP_ADMIN_PK) inform_changed_data(self.admin) self.group_in = get_group_model().objects.get(pk=GROUP_STAFF_PK) self.motion = Motion( title="test_title_SlqfMw(waso0saWMPqcZ", text="test_text_f30skclqS9wWF=xdfaSL", ) self.motion.save() self.section_no_groups = MotionCommentSection( name='test_name_gj4F§(fj"(edm"§F3f3fs' ) self.section_no_groups.save() self.section_read = MotionCommentSection(name="test_name_2wv30(d2S&kvelkakl39") self.section_read.save() self.section_read.read_groups.add( self.group_in, self.group_out ) # Group out for testing multiple groups self.section_read.write_groups.add(self.group_out) self.section_read_write = MotionCommentSection( name="test_name_a3m9sd0(Mw2%slkrv30," ) self.section_read_write.save() self.section_read_write.read_groups.add(self.group_in) self.section_read_write.write_groups.add(self.group_in)
def create(self, request, *args, **kwargs): """ Creates an agenda item and adds the content object to the agenda. Request args should specify the content object: { "collection": <The collection string>, "id": <The content object id> } """ collection = request.data.get("collection") id = request.data.get("id") if not isinstance(collection, str): raise ValidationError( {"detail": "The collection needs to be a string"}) if not isinstance(id, int): raise ValidationError({"detail": "The id needs to be an int"}) try: model = get_model_from_collection_string(collection) except ValueError: raise ValidationError({"detail": "Invalid collection"}) try: content_object = model.objects.get(pk=id) except model.DoesNotExist: raise ValidationError({"detail": "The id is invalid"}) if not hasattr(content_object, "get_agenda_title_information"): raise ValidationError( {"detail": "The collection does not have agenda items"}) try: item = Item.objects.create(content_object=content_object) except IntegrityError: raise ValidationError( {"detail": "The item is already in the agenda"}) inform_changed_data(content_object) return Response({id: item.id})
def test_internal_by_anonymous_without_perm_to_see_internal_items(self): group = Group.objects.get(pk=1) # Group with pk 1 is for anonymous users. permission_string = "agenda.can_see_internal_items" app_label, codename = permission_string.split(".") permission = group.permissions.get( content_type__app_label=app_label, codename=codename ) group.permissions.remove(permission) inform_changed_data(group) self.item.type = Item.INTERNAL_ITEM self.item.save() response = self.client.get(reverse("item-detail", args=[self.item.pk])) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual( sorted(response.data.keys()), sorted( ( "id", "title_information", "speakers", "speaker_list_closed", "content_object", ) ), ) forbidden_keys = ( "item_number", "title_with_type", "comment", "closed", "type", "is_internal", "is_hidden", "duration", "weight", "parent", ) for key in forbidden_keys: self.assertFalse(key in response.data.keys())
def create_votes_types_yn_yna( self, data, poll, vote_weight, vote_user, request_user ): """ Helper function for handle_(named|pseudoanonymous)_vote Assumes data is already validated vote_user is the user whose vote is given request_user is the user who gives the vote, may be a delegate """ options = poll.get_options() weight = vote_weight if config["users_activate_vote_weight"] else Decimal(1) for option_id, result in data.items(): option = options.get(pk=option_id) vote = AssignmentVote.objects.create( option=option, user=vote_user, delegated_user=request_user, value=result, weight=weight, ) inform_changed_data(vote, no_delete_on_restriction=True) inform_changed_data(option, no_delete_on_restriction=True)
def sort_speakers(self, request, pk=None): """ Special view endpoint to sort the list of speakers. Expects a list of IDs of the speakers. """ # Retrieve item. item = self.get_object() # Check data speaker_ids = request.data.get('speakers') if not isinstance(speaker_ids, list): raise ValidationError( {'detail': _('Invalid data.')}) # Get all speakers speakers = {} for speaker in item.speakers.filter(begin_time=None): speakers[speaker.pk] = speaker # Check and sort speakers valid_speakers = [] for speaker_id in speaker_ids: if not isinstance(speaker_id, int) or speakers.get(speaker_id) is None: raise ValidationError( {'detail': _('Invalid data.')}) valid_speakers.append(speakers[speaker_id]) weight = 0 with transaction.atomic(): for speaker in valid_speakers: speaker.weight = weight speaker.save(skip_autoupdate=True) weight += 1 # send autoupdate inform_changed_data(item) # Initiate response. return Response({'detail': _('List of speakers successfully sorted.')})
def update(self, *args, **kwargs): """ Customized view endpoint to update all children if the item type has changed. """ old_type = self.get_object().type response = super().update(*args, **kwargs) # Update all children if the item type has changed. item = self.get_object() if old_type != item.type: items_to_update = [] # Recursively add children to items_to_update. def add_item(item): items_to_update.append(item) for child in item.children.all(): add_item(child) add_item(item) inform_changed_data(items_to_update) return response
def post(self, request, poll_id=0, keypad_id=0): keypad = super(KeypadCallback, self).post(request, poll_id, keypad_id) if keypad: inform_changed_data(keypad) return HttpResponse()
def post(self, request, poll_id): # Get assignment poll. try: poll = AssignmentPoll.objects.get(id=poll_id) except AssignmentPoll.DoesNotExist: return HttpResponse('') # Load json list from request body. votes = json.loads(request.body.decode('utf-8')) candidate_count = poll.assignment.related_users.all().count() keypad_set = set() connections = [] for vote in votes: keypad_id = vote['id'] try: keypad = Keypad.objects.get(keypad_id=keypad_id) except Keypad.DoesNotExist: continue # Mark keypad as in range and update battery level. keypad.in_range = True keypad.battery_level = vote['bl'] keypad.save(skip_autoupdate=True) # Validate vote value. try: value = int(vote['value']) except ValueError: continue if value < 0 or value > 9: # Invalid candidate number. continue # Get the selected candidate. candidate_id = None if 0 < value <= candidate_count: candidate_id = AssignmentOption.objects.filter(poll=poll_id).order_by('weight').all()[value - 1].candidate_id # Write poll connection. try: conn = AssignmentPollKeypadConnection.objects.get(poll=poll, keypad=keypad) except AssignmentPollKeypadConnection.DoesNotExist: conn = AssignmentPollKeypadConnection(poll=poll, keypad=keypad) conn.serial_number = vote['sn'] conn.value = str(value) conn.candidate_id = candidate_id if conn.pk: # Save updated connection. conn.save(skip_autoupdate=True) else: # Add new connection to bulk create list. connections.append(conn) keypad_set.add(keypad.id) # Bulk create connections. AssignmentPollKeypadConnection.objects.bulk_create(connections) # Trigger auto update. connections = AssignmentPollKeypadConnection.objects.filter(poll=poll, keypad_id__in=keypad_set) inform_changed_data(connections) return HttpResponse()