Repository: fossasia/open-event-server

Issue:
Session & Speaker: User can change his session/speaker details after CFS is closed
**Describe the bug**
<!-- A clear and concise description of what the bug is. -->
User can change his session/speaker details after CFS is closed.

**To Reproduce**

Steps to reproduce the behaviour:

1. Submit a Proposal and Confirm it.
2. Wait for CFS to get closed and then edit your proposal.
3. See error

**Expected behaviour**
<!-- A clear and concise description of what you expected to happen. -->
User should not have the right to edit the proposal once CFS is ended.

See Also : https://github.com/fossasia/open-event-frontend/issues/3768#issuecomment-571141434


Discussion:
Can I work on this issue? 

@hedonhermdev Yes sure

Resolving patch:
diff --git a/app/api/helpers/speaker.py b/app/api/helpers/speaker.py
new file mode 100644
--- /dev/null
+++ b/app/api/helpers/speaker.py
@@ -0,0 +1,21 @@
+from app.models.speakers_call import SpeakersCall
+from app.api.helpers.permission_manager import has_access
+from app.api.helpers.exceptions import ForbiddenException
+
+from datetime import datetime
+
+
+def can_edit_after_cfs_ends(event_id):
+    """
+    Method to check that user has permission to edit the speaker or session
+    after the CFS ends
+    """
+    speakers_call = SpeakersCall.query.filter_by(event_id=event_id, deleted_at=None).one()
+    if speakers_call:
+        speakers_call_tz = speakers_call.ends_at.tzinfo
+        return not(speakers_call.ends_at <= datetime.now().replace(tzinfo=speakers_call_tz)
+            and not (has_access('is_admin') or has_access('is_organizer', event_id=event_id) or
+                     has_access('is_coorganizer', event_id=event_id)))
+    else:
+        raise ForbiddenException({'source': '/data/event-id'},
+                                 'Speaker Calls for event {id} not found'.format(id=event_id))
diff --git a/app/api/sessions.py b/app/api/sessions.py
--- a/app/api/sessions.py
+++ b/app/api/sessions.py
@@ -9,6 +9,7 @@
 from app.api.helpers.notification import send_notif_new_session_organizer, send_notif_session_accept_reject
 from app.api.helpers.permission_manager import has_access
 from app.api.helpers.query import event_query
+from app.api.helpers.speaker import can_edit_after_cfs_ends
 from app.api.helpers.utilities import require_relationship
 from app.api.schema.sessions import SessionSchema
 from app.models import db
@@ -148,6 +149,10 @@ def before_update_object(self, session, data, view_kwargs):
         if session.is_locked and data.get('is_locked') == session.is_locked:
             raise ForbiddenException({'source': '/data/attributes/is-locked'}, "Locked sessions cannot be edited")
 
+        if not can_edit_after_cfs_ends(session.event_id):
+            raise ForbiddenException({'source': ''},
+                                     "Cannot edit session after the call for speaker is ended")
+
     def after_update_object(self, session, data, view_kwargs):
         """ Send email if session accepted or rejected """
 
diff --git a/app/api/speakers.py b/app/api/speakers.py
--- a/app/api/speakers.py
+++ b/app/api/speakers.py
@@ -9,6 +9,7 @@
 from app.api.helpers.permission_manager import has_access
 from app.api.helpers.query import event_query
 from app.api.helpers.utilities import require_relationship
+from app.api.helpers.speaker import can_edit_after_cfs_ends
 from app.api.schema.speakers import SpeakerSchema
 from app.models import db
 from app.models.event import Event
@@ -132,6 +133,10 @@ def before_update_object(self, speaker, data, view_kwargs):
         :param view_kwargs:
         :return:
         """
+        if not can_edit_after_cfs_ends(speaker.event_id):
+            raise ForbiddenException({'source': ''},
+                                     "Cannot edit speaker after the call for speaker is ended")
+
         if data.get('photo_url') and data['photo_url'] != speaker.photo_url:
             start_image_resizing_tasks(speaker, data['photo_url'])
 


Test patch:
diff --git a/tests/hook_main.py b/tests/hook_main.py
--- a/tests/hook_main.py
+++ b/tests/hook_main.py
@@ -1068,6 +1068,8 @@ def session_patch(transaction):
     """
     with stash['app'].app_context():
         session = SessionFactory()
+        speakers_call = SpeakersCallFactory()
+        db.session.add(speakers_call)
         db.session.add(session)
         db.session.commit()
 
@@ -1081,6 +1083,8 @@ def session_delete(transaction):
     """
     with stash['app'].app_context():
         session = SessionFactory()
+        speakers_call = SpeakersCallFactory()
+        db.session.add(speakers_call)
         db.session.add(session)
         db.session.commit()
 
@@ -1265,6 +1269,8 @@ def speaker_patch(transaction):
     """
     with stash['app'].app_context():
         speaker = SpeakerFactory()
+        speakers_call = SpeakersCallFactory()
+        db.session.add(speakers_call)
         db.session.add(speaker)
         db.session.commit()
 
@@ -1278,6 +1284,8 @@ def speaker_delete(transaction):
     """
     with stash['app'].app_context():
         speaker = SpeakerFactory()
+        speakers_call = SpeakersCallFactory()
+        db.session.add(speakers_call)
         db.session.add(speaker)
         db.session.commit()