Skip to content

Allow forwarding motions with attachments #2949

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 17 commits into
base: main
Choose a base branch
from

Conversation

vkrasnovyd
Copy link
Contributor

Closes #2889

@vkrasnovyd vkrasnovyd added this to the 4.3 milestone May 16, 2025
Copy link
Member

@luisa-beerboom luisa-beerboom left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I already wrote some of this in my code reviews, but there are some new points as well:

  1. Amendment attachments should also be forwarded, so please make sure to pass that information into every create_forwarded_amendment payload
  2. If multiple motions (and amendments!) use the same mediafile as an attachment, is it duplicated multiple times? You should write a test and, if yes, look into avoiding that. We shouldn't unnecessarily clutter the media service.
  3. From what I can see the ids of your mediafiles and motions usually form a perfect sequence in the tests, just to ensure that the code is actually matching the content up correctly, you should probably write a test with some random order of model to attachments (smth like motion 1 -> [attachment 4]; 2 -> [1, 6]; 3 -> [7,1]; 4 -> [5]; 5 -> [1]), just to ensure it actually works
  4. You are using events to write the mediafile and meeting_mediafile models, which can be a bit unsafe because, if you write into events directly, the back relations will not be filled automatically, meaning that you'll have to add them manually. Best way to test if you're doing that is probably by constructing a minimum viable dataset with at least one orga- and meeting mediafile as attachments of a motion, forwarding that motion with attachments and then running the datastore checker to see if the database content is still consistent. The latter can be achieved by using the Checker class from openslides-backend/openslides_backend/models/checker.py, or alternatively seeing if a call to the check_database_all presenter comes back positive.

Comment on lines 601 to 610
new_meeting_mediafile = {
"id": target_meeting_mediafile_id,
"is_public": origin_meeting_mediafile["is_public"],
"mediafile_id": target_mediafile_id,
"meeting_id": meeting_id,
"meta_new": True,
"attachment_ids": [
fqid_from_collection_and_id("motion", instance["id"])
],
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even if they're just empty lists. You should probably set access_group_ids and inherited_access_group_ids. These are called on in various parts of the mediafile-related backend code and these calls are probably not necessarily safe.

Comment on lines 631 to 634
meeting_id: {
"meeting_mediafile_ids": meeting.get("meeting_mediafile_ids", [])
+ list(target_meeting_mediafiles)
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You create the back relations of the meeting_mediafiles here, but I don't see where you create them for the newly created mediafiles

Comment on lines 124 to 125
"mimetype": "text/plain",
"is_directory": True,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

directories wouldn't have a mimetype

@@ -52,3 +52,6 @@ def create_amendments(self, amendment_data: ActionData) -> ActionResults | None:

def should_forward_amendments(self, instance: dict[str, Any]) -> bool:
return True

def should_forward_attachments(self, instance: dict[str, Any]) -> bool:
return False
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the main motion is forwarded with attachments, any amendments forwarded along with it should also be forwarded with attachments. If they are the same attachment, try looking into the possibility of using the same newly created mediafiles, instead of duplicating the same mediafile over and over again. Perhaps the mediafile stuff should be moved to get_updated_instances for that purpose and the amendment code should also receive a map of already duplicated mediafiles to the duplicate ids.

@luisa-beerboom
Copy link
Member

Alternatively use actions instead of writing directly into events, write a new internal one for cloning a mediafile into another meeting if necessary, that's going to be a lot easier and safer and won't require such a massive test.

@vkrasnovyd vkrasnovyd force-pushed the 2889-motion-forward-with-attachments branch from dcd5084 to 5eb0e2e Compare May 22, 2025 08:48
Copy link
Member

@luisa-beerboom luisa-beerboom left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did not look at the tests yet, comments so far are purely on the function code.

The duplicate_mediafile function is pretty long, perhaps look into splitting some parts off into concisely named helper functions

Comment on lines 422 to 427
Duplicates the meeting-wide mediafile models that must be forwarded to
the target motion along with the corresponding mediafiles in the
mediaservice.

Builds meeting_mediafiles replace map for each meeting where the motions
are forwarded.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You seem to be doing this not only for the motions called in this action, but also for the amendments of those motions. These aren't necessarily forwarded even if with_amendments is set, however. To be forwarded an amendment also needs to be in a state with allow_amendment_forwarding set. I don't see you checking for that in this function, so I assume that if an amendment doesn't have this kind of state, you'll still forward the mediafiles, causing the mediafiles to be ported even if the amendment they are attached to isn't.

Also, you call this function in perform. Please take note that this is a base class for both the main action motion.create_forwarded and the sub-action motion.create_forwarded_amendment. So this function is going to be called both in the main action and in every single call of create_forwarded_amendment (potentially once for every call of the main actions update_instance).
To eliminate the chance of accidentally duplicating mediafiles multiple times into the same meeting, perhaps move this method to the create_forwarded and simply pass the map to the create_forwarded_amendment as a parameter when calling it. Alternatively you could leave the function here and just create the map for the main action motions in create_forwarded, pass it to the amendment sub-action, have it update the map with all the additional mediafiles it created with this same function and then have it return the map as a return object that the create_forwarded action can use for the next create_forwarded_amendment call. The latter option also gets rid of the requirement to check for that state, since the amendments given to create_forwarded_amendment are already filtered to be the ones that should be forwarded.

Adjusted the expected_models for 2 tests to match the order in which ids
are assigned to new instances. Current logic:
- Attachments of the lead motions are proceesed first, then -
  attachments of their amendments.
- Within each of these stages, ids of the origin attachments are sorted
  in the ascending order. Ids for the corresponding meeting_mediafiles
  and mediafiles are assigned in this order.
- Each meeting_mediafile has amendments before the lead_motions in the
  attachment_ids field.
"meeting_id": 2,
"mediafile_id": 21,
"is_public": True,
"attachment_ids": ["motion/21", "motion/20"],
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@luisa-beerboom is it possible and reasonable to sort the value in the attachment_ids field of the new meeting_mediafiles after creating the models so the attachments are listed in ascending order (by id)? If yes, what is the correct place for this logic? Currently the amendments are listed before their lead motions which seems to be not intuitive to me

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't say there is much of a necessity to keep this sorted. Based on my knowledge I can say: As far as the client is concerned this field specifically is not even used. We only have it because our system requires back relations. Furthermore even if it was, it's pretty established that relation lists are unordered and that if the client wants things ordered it'll have to do so itself, with the backend only aiding that when necessary through weight fields to allow specific ordering. Any other sorting of relation lists in the backend is usually coincidental.

But just for the sake of answering the question:
There is no standard way of doing that as back-relation lists are usually generated automatically by the relation handling in the backend and the event handling in the datastore. If you really wanted it sorted, the most practical way is probably to overwrite key parts of the relation handling, which is generally not recommended because of the instability (specifically: ensure that the write events generated for the meeting_mediafile are replaced with entire list replacement events containing the new order).
Unless that is done you can generally expect lists that were calculated as back-relations to be ordered according to the order in which they were processed. Based on that I'd say keeping the motions attachment list sorted while also keeping the meeting_mediafiles attachment list sorted in the same way as before is not all too plausible.

@vkrasnovyd vkrasnovyd requested a review from luisa-beerboom May 30, 2025 08:07

def _fetch_and_map_mediafiles(
self, mm_id_target_meeting_ids_map: dict[int, set[int]]
) -> tuple[dict, dict[int, dict[int, dict[str, Any]]], list[dict[str, Any]]]:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add the format of the first dict in the tuple

return self.execute_other_action(MotionCreateForwardedAmendment, amendment_data)

def should_forward_amendments(self, instance: dict[str, Any]) -> bool:
return True

def should_forward_attachments(self, instance: dict[str, Any]) -> bool:
return True
return self.with_attachments
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the same as in motion.create_forwarded, so this code may as well be moved to the base class

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've kept it like that on purpose. It is done like that to avoid mypy error "BaseMotionCreateForwarded" has no attribute "with_attachments".

I can not move initializing of self.with_attachments to the base class because in MotionCreateForwarded its value must be used after super().update_instance(instance) and in the MotionCreateForwardedAmendment - before it.

Comment on lines 33 to 36
@classmethod
def reset_forwarded_state(cls) -> None:
cls.forwarded_attachments.clear()
cls.meeting_mediafile_replace_map.clear()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method clears two class attribute the contents of which, to my understanding, are vital to the correct functioning of this action complex.
I am a bit worried about what would happen if a second motion.create_forwarded action is called while another is still running. I don't think the threads are isolated in a way that would cause the class attributes to not be shared. As such I am afraid that the second call of the action would clear the first calls replace map, causing the first action to fail or, even worse, write bogus data in such a case.

If you are zealous enough, feel free to read through the code in action_worker.py, action_handler.pyandaction_view.pyand assess whether my worry is unfounded. Otherwise I'd suggest you use instance attributes instead and pass them back and forth internally through themotion.create_forwarded_amendment-payload and -result element (the latter is created via the create_action_result_element` method of the Action class).

Comment on lines 1721 to 1723
self.base_test_forward_with_attachments_false(
origin_mediafiles=[{"mediafile_id": 1, "owner_meeting_id": 1}]
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For readabilities sake it would be best to move helper methods like base_test_forward_with_attachments_false above the proper test methods that use them

"meeting_id": 2,
"mediafile_id": 21,
"is_public": True,
"attachment_ids": ["motion/21", "motion/20"],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't say there is much of a necessity to keep this sorted. Based on my knowledge I can say: As far as the client is concerned this field specifically is not even used. We only have it because our system requires back relations. Furthermore even if it was, it's pretty established that relation lists are unordered and that if the client wants things ordered it'll have to do so itself, with the backend only aiding that when necessary through weight fields to allow specific ordering. Any other sorting of relation lists in the backend is usually coincidental.

But just for the sake of answering the question:
There is no standard way of doing that as back-relation lists are usually generated automatically by the relation handling in the backend and the event handling in the datastore. If you really wanted it sorted, the most practical way is probably to overwrite key parts of the relation handling, which is generally not recommended because of the instability (specifically: ensure that the write events generated for the meeting_mediafile are replaced with entire list replacement events containing the new order).
Unless that is done you can generally expect lists that were calculated as back-relations to be ordered according to the order in which they were processed. Based on that I'd say keeping the motions attachment list sorted while also keeping the meeting_mediafiles attachment list sorted in the same way as before is not all too plausible.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add option to forward motion with/without attachment
2 participants