Merge pull request #4141 from normanjaeckel/HistoryInformation

Added history information for some motion views.
This commit is contained in:
Oskar Hahn 2019-01-19 16:56:10 +01:00 committed by GitHub
commit a83de77180
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 114 additions and 32 deletions

View File

@ -17,11 +17,12 @@ Core:
- Enabled docs for using OpenSlides with Gunicorn and Uvicorn in big - Enabled docs for using OpenSlides with Gunicorn and Uvicorn in big
mode [#3799, #3817]. mode [#3799, #3817].
- Changed format for elements send via autoupdate [#3926]. - Changed format for elements send via autoupdate [#3926].
- Fixed autoupdate system for related objects [#4140].
- Add a change-id system to get only new elements [#3938]. - Add a change-id system to get only new elements [#3938].
- Switch from Yarn back to npm [#3964]. - Switch from Yarn back to npm [#3964].
- Added password reset link (password reset via email) [#3914]. - Added password reset link (password reset via email) [#3914].
- Added global history mode [#3977]. - Added global history mode [#3977, #4141].
- Projector Refactor [4119, #4130]. - Projector refactoring [4119, #4130].
Agenda: Agenda:
- Added viewpoint to assign multiple items to a new parent item [#4037]. - Added viewpoint to assign multiple items to a new parent item [#4037].

View File

@ -0,0 +1,25 @@
# Generated by Django 2.1.5 on 2019-01-19 15:41
import jsonfield.encoder
import jsonfield.fields
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0012_auto_20190119_1425'),
]
operations = [
migrations.AddField(
model_name='history',
name='restricted',
field=models.BooleanField(default=False),
),
migrations.AlterField(
model_name='history',
name='information',
field=jsonfield.fields.JSONField(dump_kwargs={'cls': jsonfield.encoder.JSONEncoder, 'separators': (',', ':')}, load_kwargs={}),
),
]

View File

@ -286,7 +286,8 @@ class HistoryManager(models.Manager):
element["collection_string"], element["id"] element["collection_string"], element["id"]
), ),
now=history_time, now=history_time,
information=element.get("information", ""), information=element.get("information", []),
restricted=element.get("restricted", False),
user_id=element.get("user_id"), user_id=element.get("user_id"),
full_data=data, full_data=data,
) )
@ -334,7 +335,9 @@ class History(RESTModelMixin, models.Model):
now = models.DateTimeField() now = models.DateTimeField()
information = models.CharField(max_length=255) information = JSONField()
restricted = models.BooleanField(default=False)
user = models.ForeignKey( user = models.ForeignKey(
settings.AUTH_USER_MODEL, null=True, on_delete=SET_NULL_AND_AUTOUPDATE settings.AUTH_USER_MODEL, null=True, on_delete=SET_NULL_AND_AUTOUPDATE

View File

@ -169,5 +169,5 @@ class HistorySerializer(ModelSerializer):
class Meta: class Meta:
model = History model = History
fields = ("id", "element_id", "now", "information", "user") fields = ("id", "element_id", "now", "information", "restricted", "user")
read_only_fields = ("now",) read_only_fields = ("now",)

View File

@ -124,7 +124,7 @@ class MotionViewSet(ModelViewSet):
# Fire autoupdate again to save information to OpenSlides history. # Fire autoupdate again to save information to OpenSlides history.
inform_deleted_data( inform_deleted_data(
[(motion.get_collection_string(), motion.pk)], [(motion.get_collection_string(), motion.pk)],
information="Motion deleted", information=["Motion deleted"],
user_id=request.user.pk, user_id=request.user.pk,
) )
@ -221,10 +221,16 @@ class MotionViewSet(ModelViewSet):
# Send new submitters and supporters via autoupdate because users # Send new submitters and supporters via autoupdate because users
# without permission to see users may not have them but can get it now. # without permission to see users may not have them but can get it now.
# TODO: Skip history.
new_users = list(motion.submitters.all()) new_users = list(motion.submitters.all())
new_users.extend(motion.supporters.all()) new_users.extend(motion.supporters.all())
inform_changed_data(new_users) inform_changed_data(new_users)
# Fire autoupdate again to save information to OpenSlides history.
inform_changed_data(
motion, information=["Motion created"], user_id=request.user.pk,
)
headers = self.get_success_headers(serializer.data) headers = self.get_success_headers(serializer.data)
# Strip out response data so nobody gets unrestricted data. # Strip out response data so nobody gets unrestricted data.
data = ReturnDict(id=serializer.data.get("id"), serializer=serializer) data = ReturnDict(id=serializer.data.get("id"), serializer=serializer)
@ -298,12 +304,13 @@ class MotionViewSet(ModelViewSet):
# Send new supporters via autoupdate because users # Send new supporters via autoupdate because users
# without permission to see users may not have them but can get it now. # without permission to see users may not have them but can get it now.
# TODO: Skip history.
new_users = list(updated_motion.supporters.all()) new_users = list(updated_motion.supporters.all())
inform_changed_data(new_users) inform_changed_data(new_users)
# Fire autoupdate again to save information to OpenSlides history. # Fire autoupdate again to save information to OpenSlides history.
inform_changed_data( inform_changed_data(
updated_motion, information="Motion updated", user_id=request.user.pk updated_motion, information=["Motion updated"], user_id=request.user.pk
) )
# We do not add serializer.data to response so nobody gets unrestricted data here. # We do not add serializer.data to response so nobody gets unrestricted data here.
@ -396,7 +403,7 @@ class MotionViewSet(ModelViewSet):
# write log # write log
motion.write_log([f"Comment {section.name} updated"], request.user) motion.write_log([f"Comment {section.name} updated"], request.user)
message = f"Comment {section.name} updated" message = ["Comment {arg1} updated", section.name]
else: # DELETE else: # DELETE
try: try:
comment = MotionComment.objects.get(motion=motion, section=section) comment = MotionComment.objects.get(motion=motion, section=section)
@ -407,7 +414,12 @@ class MotionViewSet(ModelViewSet):
comment.delete() comment.delete()
motion.write_log([f"Comment {section.name} deleted"], request.user) motion.write_log([f"Comment {section.name} deleted"], request.user)
message = f"Comment {section.name} deleted" message = ["Comment {arg1} deleted", section.name]
# Fire autoupdate again to save information to OpenSlides history.
inform_changed_data(
motion, information=message, user_id=request.user.pk, restricted=True,
)
return Response({"detail": message}) return Response({"detail": message})
@ -475,7 +487,7 @@ class MotionViewSet(ModelViewSet):
motion_result.append(motion) motion_result.append(motion)
# Now inform all clients. # Now inform all clients.
inform_changed_data(motion_result) inform_changed_data(motion_result, information=["Submitters changed"], user_id=request.user.pk)
# Also send all new submitters via autoupdate because users without # Also send all new submitters via autoupdate because users without
# permission to see users may not have them but can get it now. # permission to see users may not have them but can get it now.
@ -512,6 +524,7 @@ class MotionViewSet(ModelViewSet):
motion.write_log(["Motion supported"], request.user) motion.write_log(["Motion supported"], request.user)
# Send new supporter via autoupdate because users without permission # Send new supporter via autoupdate because users without permission
# to see users may not have it but can get it now. # to see users may not have it but can get it now.
# TODO: Skip history.
inform_changed_data([request.user]) inform_changed_data([request.user])
message = "You have supported this motion successfully." message = "You have supported this motion successfully."
else: else:
@ -523,6 +536,11 @@ class MotionViewSet(ModelViewSet):
motion.write_log(["Motion unsupported"], request.user) motion.write_log(["Motion unsupported"], request.user)
message = "You have unsupported this motion successfully." message = "You have unsupported this motion successfully."
# Fire autoupdate again to save information to OpenSlides history.
inform_changed_data(
motion, information=["Supporters changed"], user_id=request.user.pk
)
# Initiate response. # Initiate response.
return Response({"detail": message}) return Response({"detail": message})
@ -569,11 +587,10 @@ class MotionViewSet(ModelViewSet):
person=request.user, person=request.user,
skip_autoupdate=True, skip_autoupdate=True,
) )
inform_changed_data(
motion, # Fire autoupdate again to save information to OpenSlides history.
information=f"State set to {motion.state.name}.", inform_changed_data(motion, information=["State set to {arg1}", motion.state.name], user_id=request.user.pk)
user_id=request.user.pk,
)
return Response({"detail": message}) return Response({"detail": message})
@list_route(methods=["post"]) @list_route(methods=["post"])
@ -640,15 +657,17 @@ class MotionViewSet(ModelViewSet):
skip_autoupdate=True, skip_autoupdate=True,
) )
# Fire autoupdate again to save information to OpenSlides history.
inform_changed_data(
motion, information=["State set to {arg1}", motion.state.name], user_id=request.user.pk,
)
# Finish motion. # Finish motion.
motion_result.append(motion) motion_result.append(motion)
# Now inform all clients.
inform_changed_data(motion_result)
# Send response. # Send response.
return Response( return Response(
{"detail": f"{len(motion_result)} motions successfully updated."} {"detail": f"State of {len(motion_result)} motions successfully set."}
) )
@detail_route(methods=["put"]) @detail_route(methods=["put"])
@ -703,7 +722,12 @@ class MotionViewSet(ModelViewSet):
person=request.user, person=request.user,
skip_autoupdate=True, skip_autoupdate=True,
) )
inform_changed_data(motion)
# Fire autoupdate again to save information to OpenSlides history.
inform_changed_data(
motion, information=["Recommendation set to {arg1}", label], user_id=request.user.pk,
)
return Response({"detail": message}) return Response({"detail": message})
@list_route(methods=["post"]) @list_route(methods=["post"])
@ -784,12 +808,14 @@ class MotionViewSet(ModelViewSet):
skip_autoupdate=True, skip_autoupdate=True,
) )
# Fire autoupdate and save information to OpenSlides history.
inform_changed_data(
motion, information=["Recommendation set to {arg1}", label], user_id=request.user.pk,
)
# Finish motion. # Finish motion.
motion_result.append(motion) motion_result.append(motion)
# Now inform all clients.
inform_changed_data(motion_result)
# Send response. # Send response.
return Response( return Response(
{"detail": f"{len(motion_result)} motions successfully updated."} {"detail": f"{len(motion_result)} motions successfully updated."}
@ -826,7 +852,8 @@ class MotionViewSet(ModelViewSet):
) )
# Now send all changes to the clients. # Now send all changes to the clients.
inform_changed_data(motion) inform_changed_data(motion, information=["State set to {arg1}", motion.state.name], user_id=request.user.pk)
return Response({"detail": "Recommendation followed successfully."}) return Response({"detail": "Recommendation followed successfully."})
@detail_route(methods=["post"]) @detail_route(methods=["post"])
@ -846,7 +873,11 @@ class MotionViewSet(ModelViewSet):
raise ValidationError({"detail": err}) raise ValidationError({"detail": err})
motion.write_log(["Vote created"], request.user, skip_autoupdate=True) motion.write_log(["Vote created"], request.user, skip_autoupdate=True)
inform_changed_data(motion) # Fire autoupdate again to save information to OpenSlides history.
inform_changed_data(
motion, information=["Vote created"], user_id=request.user.pk,
)
return Response( return Response(
{"detail": "Vote created successfully.", "createdPollId": poll.pk} {"detail": "Vote created successfully.", "createdPollId": poll.pk}
) )
@ -939,6 +970,12 @@ class MotionPollViewSet(UpdateModelMixin, DestroyModelMixin, GenericViewSet):
response = super().update(*args, **kwargs) response = super().update(*args, **kwargs)
poll = self.get_object() poll = self.get_object()
poll.motion.write_log(["Vote updated"], self.request.user) poll.motion.write_log(["Vote updated"], self.request.user)
# Fire autoupdate again to save information to OpenSlides history.
inform_changed_data(
poll.motion, information=["Poll updated"], user_id=self.request.user.pk
)
return response return response
def destroy(self, *args, **kwargs): def destroy(self, *args, **kwargs):
@ -948,6 +985,12 @@ class MotionPollViewSet(UpdateModelMixin, DestroyModelMixin, GenericViewSet):
poll = self.get_object() poll = self.get_object()
result = super().destroy(*args, **kwargs) result = super().destroy(*args, **kwargs)
poll.motion.write_log(["Vote deleted"], self.request.user) poll.motion.write_log(["Vote deleted"], self.request.user)
# Fire autoupdate again to save information to OpenSlides history.
inform_changed_data(
poll.motion, information=["Poll deleted"], user_id=self.request.user.pk
)
return result return result
@ -1205,7 +1248,7 @@ class CategoryViewSet(ModelViewSet):
error_message = "Error: At least one identifier of this category does already exist in another category." error_message = "Error: At least one identifier of this category does already exist in another category."
response = Response({"detail": error_message}, status=400) response = Response({"detail": error_message}, status=400)
else: else:
inform_changed_data(instances) inform_changed_data(instances, information=["Number set"], user_id=request.user.pk)
message = f"All motions in category {category} numbered " "successfully." message = f"All motions in category {category} numbered " "successfully."
response = Response({"detail": message}) response = Response({"detail": message})
return response return response
@ -1251,7 +1294,6 @@ class MotionBlockViewSet(ModelViewSet):
its recommendation. It is a POST request without any data. its recommendation. It is a POST request without any data.
""" """
motion_block = self.get_object() motion_block = self.get_object()
instances = []
with transaction.atomic(): with transaction.atomic():
for motion in motion_block.motion_set.all(): for motion in motion_block.motion_set.all():
# Follow recommendation. # Follow recommendation.
@ -1263,8 +1305,10 @@ class MotionBlockViewSet(ModelViewSet):
person=request.user, person=request.user,
skip_autoupdate=True, skip_autoupdate=True,
) )
instances.append(motion) # Fire autoupdate and save information to OpenSlides history.
inform_changed_data(instances) inform_changed_data(
motion, information=["State set to {arg1}", motion.state.name], user_id=request.user.pk
)
return Response({"detail": "Followed recommendations successfully."}) return Response({"detail": "Followed recommendations successfully."})

View File

@ -31,7 +31,8 @@ class Element(ElementBase, total=False):
process. process.
""" """
information: str information: List[str]
restricted: bool
user_id: Optional[int] user_id: Optional[int]
disable_history: bool disable_history: bool
reload: bool reload: bool
@ -51,8 +52,9 @@ AutoupdateFormat = TypedDict(
def inform_changed_data( def inform_changed_data(
instances: Union[Iterable[Model], Model], instances: Union[Iterable[Model], Model],
information: str = "", information: List[str] = None,
user_id: Optional[int] = None, user_id: Optional[int] = None,
restricted: bool = False,
) -> None: ) -> None:
""" """
Informs the autoupdate system and the caching system about the creation or Informs the autoupdate system and the caching system about the creation or
@ -62,6 +64,8 @@ def inform_changed_data(
History creation is enabled. History creation is enabled.
""" """
if information is None:
information = []
root_instances = set() root_instances = set()
if not isinstance(instances, Iterable): if not isinstance(instances, Iterable):
instances = (instances,) instances = (instances,)
@ -81,6 +85,7 @@ def inform_changed_data(
collection_string=root_instance.get_collection_string(), collection_string=root_instance.get_collection_string(),
full_data=root_instance.get_full_data(), full_data=root_instance.get_full_data(),
information=information, information=information,
restricted=restricted,
user_id=user_id, user_id=user_id,
) )
@ -95,8 +100,9 @@ def inform_changed_data(
def inform_deleted_data( def inform_deleted_data(
deleted_elements: Iterable[Tuple[str, int]], deleted_elements: Iterable[Tuple[str, int]],
information: str = "", information: List[str] = None,
user_id: Optional[int] = None, user_id: Optional[int] = None,
restricted: bool = False,
) -> None: ) -> None:
""" """
Informs the autoupdate system and the caching system about the deletion of Informs the autoupdate system and the caching system about the deletion of
@ -104,6 +110,8 @@ def inform_deleted_data(
History creation is enabled. History creation is enabled.
""" """
if information is None:
information = []
elements: Dict[str, Element] = {} elements: Dict[str, Element] = {}
for deleted_element in deleted_elements: for deleted_element in deleted_elements:
key = deleted_element[0] + str(deleted_element[1]) key = deleted_element[0] + str(deleted_element[1])
@ -112,6 +120,7 @@ def inform_deleted_data(
collection_string=deleted_element[0], collection_string=deleted_element[0],
full_data=None, full_data=None,
information=information, information=information,
restricted=restricted,
user_id=user_id, user_id=user_id,
) )