We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
My Sessions: Server is returning duplicated data
**Describe the bug**
My Session is showing session's multiple times 


**Expected behaviour**
Server should return session only once

**Screenshots**
[![multiple session](http://img.youtube.com/vi/EMXLyCehYCM/0.jpg)](http://www.youtube.com/watch?v=EMXLyCehYCM "multiple session")

Responses for Reference :
https://github.com/fossasia/open-event-frontend/pull/3811#issuecomment-575883439
https://github.com/fossasia/open-event-frontend/pull/3811#issuecomment-575882180

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `app/api/sessions.py`
Content:
```
1 from flask_jwt_extended import current_user
2 from flask_rest_jsonapi import ResourceDetail, ResourceList, ResourceRelationship
3 
4 from app.api.bootstrap import api
5 from app.api.events import Event
6 from app.api.helpers.custom_forms import validate_custom_form_constraints_request
7 from app.api.helpers.db import get_count, safe_query, safe_query_kwargs, save_to_db
8 from app.api.helpers.errors import ForbiddenError
9 from app.api.helpers.files import make_frontend_url
10 from app.api.helpers.mail import send_email_new_session, send_email_session_accept_reject
11 from app.api.helpers.notification import (
12     send_notif_new_session_organizer,
13     send_notif_session_accept_reject,
14 )
15 from app.api.helpers.permission_manager import has_access
16 from app.api.helpers.query import event_query
17 from app.api.helpers.speaker import can_edit_after_cfs_ends
18 from app.api.helpers.utilities import require_relationship
19 from app.api.schema.sessions import SessionSchema
20 from app.models import db
21 from app.models.microlocation import Microlocation
22 from app.models.session import Session
23 from app.models.session_speaker_link import SessionsSpeakersLink
24 from app.models.session_type import SessionType
25 from app.models.speaker import Speaker
26 from app.models.track import Track
27 from app.models.user import User
28 from app.settings import get_settings
29 
30 
31 class SessionListPost(ResourceList):
32     """
33     List Sessions
34     """
35 
36     def before_post(self, args, kwargs, data):
37         """
38         before post method to check for required relationship and proper permission
39         :param args:
40         :param kwargs:
41         :param data:
42         :return:
43         """
44         require_relationship(['event', 'track'], data)
45         data['creator_id'] = current_user.id
46         if (
47             get_count(
48                 db.session.query(Event).filter_by(
49                     id=int(data['event']), is_sessions_speakers_enabled=False
50                 )
51             )
52             > 0
53         ):
54             raise ForbiddenError({'pointer': ''}, "Sessions are disabled for this Event")
55 
56         data['complex_field_values'] = validate_custom_form_constraints_request(
57             'session', self.schema, Session(event_id=data['event']), data
58         )
59 
60     def after_create_object(self, session, data, view_kwargs):
61         """
62         method to send email for creation of new session
63         mails session link to the concerned user
64         :param session:
65         :param data:
66         :param view_kwargs:
67         :return:
68         """
69         if session.event.get_owner():
70             event_name = session.event.name
71             owner = session.event.get_owner()
72             owner_email = owner.email
73             event = session.event
74             link = make_frontend_url(
75                 "/events/{}/sessions/{}".format(event.identifier, session.id)
76             )
77             send_email_new_session(owner_email, event_name, link)
78             send_notif_new_session_organizer(owner, event_name, link, session.id)
79 
80         for speaker in session.speakers:
81             session_speaker_link = SessionsSpeakersLink(
82                 session_state=session.state,
83                 session_id=session.id,
84                 event_id=session.event.id,
85                 speaker_id=speaker.id,
86             )
87             save_to_db(session_speaker_link, "Session Speaker Link Saved")
88 
89     decorators = (api.has_permission('create_event'),)
90     schema = SessionSchema
91     data_layer = {
92         'session': db.session,
93         'model': Session,
94         'methods': {'after_create_object': after_create_object},
95     }
96 
97 
98 class SessionList(ResourceList):
99     """
100     List Sessions
101     """
102 
103     def query(self, view_kwargs):
104         """
105         query method for SessionList class
106         :param view_kwargs:
107         :return:
108         """
109         query_ = self.session.query(Session)
110         if view_kwargs.get('track_id') is not None:
111             track = safe_query_kwargs(Track, view_kwargs, 'track_id')
112             query_ = query_.join(Track).filter(Track.id == track.id)
113         if view_kwargs.get('session_type_id') is not None:
114             session_type = safe_query_kwargs(SessionType, view_kwargs, 'session_type_id')
115             query_ = query_.join(SessionType).filter(SessionType.id == session_type.id)
116         if view_kwargs.get('microlocation_id') is not None:
117             microlocation = safe_query_kwargs(
118                 Microlocation, view_kwargs, 'microlocation_id',
119             )
120             query_ = query_.join(Microlocation).filter(
121                 Microlocation.id == microlocation.id
122             )
123         if view_kwargs.get('user_id') is not None:
124             user = safe_query_kwargs(User, view_kwargs, 'user_id')
125             query_ = (
126                 query_.join(User)
127                 .join(Speaker)
128                 .filter(
129                     (
130                         User.id == user.id
131                         or Session.speakers.any(Speaker.user_id == user.id)
132                     )
133                 )
134             )
135         query_ = event_query(query_, view_kwargs)
136         if view_kwargs.get('speaker_id'):
137             speaker = safe_query_kwargs(Speaker, view_kwargs, 'speaker_id')
138             # session-speaker :: many-to-many relationship
139             query_ = Session.query.filter(Session.speakers.any(id=speaker.id))
140 
141         return query_
142 
143     view_kwargs = True
144     methods = ['GET']
145     schema = SessionSchema
146     data_layer = {'session': db.session, 'model': Session, 'methods': {'query': query}}
147 
148 
149 class SessionDetail(ResourceDetail):
150     """
151     Session detail by id
152     """
153 
154     def before_get_object(self, view_kwargs):
155         """
156         before get method to get the resource id for fetching details
157         :param view_kwargs:
158         :return:
159         """
160         if view_kwargs.get('event_identifier'):
161             event = safe_query(
162                 Event, 'identifier', view_kwargs['event_identifier'], 'identifier'
163             )
164             view_kwargs['event_id'] = event.id
165 
166     def before_update_object(self, session, data, view_kwargs):
167         """
168         before update method to verify if session is locked before updating session object
169         :param event:
170         :param data:
171         :param view_kwargs:
172         :return:
173         """
174         if session.is_locked:
175             if not (
176                 has_access('is_admin')
177                 or has_access('is_organizer', event_id=session.event_id)
178             ):
179                 raise ForbiddenError(
180                     {'source': '/data/attributes/is-locked'},
181                     "You don't have enough permissions to change this property",
182                 )
183 
184         if session.is_locked and data.get('is_locked') != session.is_locked:
185             raise ForbiddenError(
186                 {'source': '/data/attributes/is-locked'},
187                 "Locked sessions cannot be edited",
188             )
189 
190         if not can_edit_after_cfs_ends(session.event_id):
191             raise ForbiddenError(
192                 {'source': ''}, "Cannot edit session after the call for speaker is ended"
193             )
194 
195         data['complex_field_values'] = validate_custom_form_constraints_request(
196             'session', self.resource.schema, session, data
197         )
198 
199     def after_update_object(self, session, data, view_kwargs):
200         """ Send email if session accepted or rejected """
201 
202         if (
203             'state' in data
204             and data.get('send_email', None)
205             and (session.state == 'accepted' or session.state == 'rejected')
206         ):
207 
208             event = session.event
209             # Email for speaker
210             speakers = session.speakers
211             for speaker in speakers:
212                 frontend_url = get_settings()['frontend_url']
213                 link = "{}/events/{}/sessions/{}".format(
214                     frontend_url, event.identifier, session.id
215                 )
216                 if not speaker.is_email_overridden:
217                     send_email_session_accept_reject(speaker.email, session, link)
218                     send_notif_session_accept_reject(
219                         speaker, session.title, session.state, link, session.id
220                     )
221 
222             # Email for owner
223             if session.event.get_owner():
224                 owner = session.event.get_owner()
225                 owner_email = owner.email
226                 frontend_url = get_settings()['frontend_url']
227                 link = "{}/events/{}/sessions/{}".format(
228                     frontend_url, event.identifier, session.id
229                 )
230                 send_email_session_accept_reject(owner_email, session, link)
231                 send_notif_session_accept_reject(
232                     owner, session.title, session.state, link, session.id
233                 )
234         if 'state' in data:
235             entry_count = SessionsSpeakersLink.query.filter_by(session_id=session.id)
236             if entry_count.count() == 0:
237                 is_patch_request = False
238             else:
239                 is_patch_request = True
240 
241             if is_patch_request:
242                 for focus_session in entry_count:
243                     focus_session.session_state = session.state
244                 db.session.commit()
245             else:
246                 current_session = Session.query.filter_by(id=session.id).first()
247                 for speaker in current_session.speakers:
248                     session_speaker_link = SessionsSpeakersLink(
249                         session_state=session.state,
250                         session_id=session.id,
251                         event_id=session.event.id,
252                         speaker_id=speaker.id,
253                     )
254                     save_to_db(session_speaker_link, "Session Speaker Link Saved")
255 
256     decorators = (api.has_permission('is_speaker_for_session', methods="PATCH,DELETE"),)
257     schema = SessionSchema
258     data_layer = {
259         'session': db.session,
260         'model': Session,
261         'methods': {
262             'before_update_object': before_update_object,
263             'before_get_object': before_get_object,
264             'after_update_object': after_update_object,
265         },
266     }
267 
268 
269 class SessionRelationshipRequired(ResourceRelationship):
270     """
271     Session Relationship
272     """
273 
274     schema = SessionSchema
275     decorators = (api.has_permission('is_speaker_for_session', methods="PATCH,DELETE"),)
276     methods = ['GET', 'PATCH']
277     data_layer = {'session': db.session, 'model': Session}
278 
279 
280 class SessionRelationshipOptional(ResourceRelationship):
281     """
282     Session Relationship
283     """
284 
285     schema = SessionSchema
286     decorators = (api.has_permission('is_speaker_for_session', methods="PATCH,DELETE"),)
287     data_layer = {'session': db.session, 'model': Session}
288
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/app/api/sessions.py b/app/api/sessions.py
--- a/app/api/sessions.py
+++ b/app/api/sessions.py
@@ -1,5 +1,7 @@
+from flask import request
 from flask_jwt_extended import current_user
 from flask_rest_jsonapi import ResourceDetail, ResourceList, ResourceRelationship
+from flask_rest_jsonapi.querystring import QueryStringManager as QSManager
 
 from app.api.bootstrap import api
 from app.api.events import Event
@@ -95,6 +97,28 @@
     }
 
 
+def get_distinct_sort_fields(schema, model, sort=True):
+    """Due to the poor code of flask-rest-jsonapi, distinct query needed
+       in sessions API to remove duplicate sessions can't be sorted on
+       returning subquery, thus we need to add all sort fields in distinct
+       group and repeat it in sort group as well"""
+    fields = []
+    qs = QSManager(request.args, schema)
+    for sort_opt in qs.sorting:
+        field = sort_opt['field']
+        if not hasattr(model, field):
+            continue
+        field = getattr(model, field)
+        if sort:
+            field = getattr(field, sort_opt['order'])()
+        fields.append(field)
+    field = Session.id
+    if sort:
+        field = field.desc()
+    fields.append(field)
+    return fields
+
+
 class SessionList(ResourceList):
     """
     List Sessions
@@ -131,6 +155,8 @@
                         or Session.speakers.any(Speaker.user_id == user.id)
                     )
                 )
+                .distinct(*get_distinct_sort_fields(SessionSchema, Session, sort=False))
+                .order_by(*get_distinct_sort_fields(SessionSchema, Session))
             )
         query_ = event_query(query_, view_kwargs)
         if view_kwargs.get('speaker_id'):

{"golden_diff": "diff --git a/app/api/sessions.py b/app/api/sessions.py\n--- a/app/api/sessions.py\n+++ b/app/api/sessions.py\n@@ -1,5 +1,7 @@\n+from flask import request\n from flask_jwt_extended import current_user\n from flask_rest_jsonapi import ResourceDetail, ResourceList, ResourceRelationship\n+from flask_rest_jsonapi.querystring import QueryStringManager as QSManager\n \n from app.api.bootstrap import api\n from app.api.events import Event\n@@ -95,6 +97,28 @@\n     }\n \n \n+def get_distinct_sort_fields(schema, model, sort=True):\n+    \"\"\"Due to the poor code of flask-rest-jsonapi, distinct query needed\n+       in sessions API to remove duplicate sessions can't be sorted on\n+       returning subquery, thus we need to add all sort fields in distinct\n+       group and repeat it in sort group as well\"\"\"\n+    fields = []\n+    qs = QSManager(request.args, schema)\n+    for sort_opt in qs.sorting:\n+        field = sort_opt['field']\n+        if not hasattr(model, field):\n+            continue\n+        field = getattr(model, field)\n+        if sort:\n+            field = getattr(field, sort_opt['order'])()\n+        fields.append(field)\n+    field = Session.id\n+    if sort:\n+        field = field.desc()\n+    fields.append(field)\n+    return fields\n+\n+\n class SessionList(ResourceList):\n     \"\"\"\n     List Sessions\n@@ -131,6 +155,8 @@\n                         or Session.speakers.any(Speaker.user_id == user.id)\n                     )\n                 )\n+                .distinct(*get_distinct_sort_fields(SessionSchema, Session, sort=False))\n+                .order_by(*get_distinct_sort_fields(SessionSchema, Session))\n             )\n         query_ = event_query(query_, view_kwargs)\n         if view_kwargs.get('speaker_id'):\n", "issue": "My Sessions: Server is returning duplicated data\n**Describe the bug**\r\nMy Session is showing session's multiple times \r\n\r\n\r\n**Expected behaviour**\r\nServer should return session only once\r\n\r\n**Screenshots**\r\n[![multiple session](http://img.youtube.com/vi/EMXLyCehYCM/0.jpg)](http://www.youtube.com/watch?v=EMXLyCehYCM \"multiple session\")\r\n\r\nResponses for Reference :\r\nhttps://github.com/fossasia/open-event-frontend/pull/3811#issuecomment-575883439\r\nhttps://github.com/fossasia/open-event-frontend/pull/3811#issuecomment-575882180\n", "before_files": [{"content": "from flask_jwt_extended import current_user\nfrom flask_rest_jsonapi import ResourceDetail, ResourceList, ResourceRelationship\n\nfrom app.api.bootstrap import api\nfrom app.api.events import Event\nfrom app.api.helpers.custom_forms import validate_custom_form_constraints_request\nfrom app.api.helpers.db import get_count, safe_query, safe_query_kwargs, save_to_db\nfrom app.api.helpers.errors import ForbiddenError\nfrom app.api.helpers.files import make_frontend_url\nfrom app.api.helpers.mail import send_email_new_session, send_email_session_accept_reject\nfrom app.api.helpers.notification import (\n    send_notif_new_session_organizer,\n    send_notif_session_accept_reject,\n)\nfrom app.api.helpers.permission_manager import has_access\nfrom app.api.helpers.query import event_query\nfrom app.api.helpers.speaker import can_edit_after_cfs_ends\nfrom app.api.helpers.utilities import require_relationship\nfrom app.api.schema.sessions import SessionSchema\nfrom app.models import db\nfrom app.models.microlocation import Microlocation\nfrom app.models.session import Session\nfrom app.models.session_speaker_link import SessionsSpeakersLink\nfrom app.models.session_type import SessionType\nfrom app.models.speaker import Speaker\nfrom app.models.track import Track\nfrom app.models.user import User\nfrom app.settings import get_settings\n\n\nclass SessionListPost(ResourceList):\n    \"\"\"\n    List Sessions\n    \"\"\"\n\n    def before_post(self, args, kwargs, data):\n        \"\"\"\n        before post method to check for required relationship and proper permission\n        :param args:\n        :param kwargs:\n        :param data:\n        :return:\n        \"\"\"\n        require_relationship(['event', 'track'], data)\n        data['creator_id'] = current_user.id\n        if (\n            get_count(\n                db.session.query(Event).filter_by(\n                    id=int(data['event']), is_sessions_speakers_enabled=False\n                )\n            )\n            > 0\n        ):\n            raise ForbiddenError({'pointer': ''}, \"Sessions are disabled for this Event\")\n\n        data['complex_field_values'] = validate_custom_form_constraints_request(\n            'session', self.schema, Session(event_id=data['event']), data\n        )\n\n    def after_create_object(self, session, data, view_kwargs):\n        \"\"\"\n        method to send email for creation of new session\n        mails session link to the concerned user\n        :param session:\n        :param data:\n        :param view_kwargs:\n        :return:\n        \"\"\"\n        if session.event.get_owner():\n            event_name = session.event.name\n            owner = session.event.get_owner()\n            owner_email = owner.email\n            event = session.event\n            link = make_frontend_url(\n                \"/events/{}/sessions/{}\".format(event.identifier, session.id)\n            )\n            send_email_new_session(owner_email, event_name, link)\n            send_notif_new_session_organizer(owner, event_name, link, session.id)\n\n        for speaker in session.speakers:\n            session_speaker_link = SessionsSpeakersLink(\n                session_state=session.state,\n                session_id=session.id,\n                event_id=session.event.id,\n                speaker_id=speaker.id,\n            )\n            save_to_db(session_speaker_link, \"Session Speaker Link Saved\")\n\n    decorators = (api.has_permission('create_event'),)\n    schema = SessionSchema\n    data_layer = {\n        'session': db.session,\n        'model': Session,\n        'methods': {'after_create_object': after_create_object},\n    }\n\n\nclass SessionList(ResourceList):\n    \"\"\"\n    List Sessions\n    \"\"\"\n\n    def query(self, view_kwargs):\n        \"\"\"\n        query method for SessionList class\n        :param view_kwargs:\n        :return:\n        \"\"\"\n        query_ = self.session.query(Session)\n        if view_kwargs.get('track_id') is not None:\n            track = safe_query_kwargs(Track, view_kwargs, 'track_id')\n            query_ = query_.join(Track).filter(Track.id == track.id)\n        if view_kwargs.get('session_type_id') is not None:\n            session_type = safe_query_kwargs(SessionType, view_kwargs, 'session_type_id')\n            query_ = query_.join(SessionType).filter(SessionType.id == session_type.id)\n        if view_kwargs.get('microlocation_id') is not None:\n            microlocation = safe_query_kwargs(\n                Microlocation, view_kwargs, 'microlocation_id',\n            )\n            query_ = query_.join(Microlocation).filter(\n                Microlocation.id == microlocation.id\n            )\n        if view_kwargs.get('user_id') is not None:\n            user = safe_query_kwargs(User, view_kwargs, 'user_id')\n            query_ = (\n                query_.join(User)\n                .join(Speaker)\n                .filter(\n                    (\n                        User.id == user.id\n                        or Session.speakers.any(Speaker.user_id == user.id)\n                    )\n                )\n            )\n        query_ = event_query(query_, view_kwargs)\n        if view_kwargs.get('speaker_id'):\n            speaker = safe_query_kwargs(Speaker, view_kwargs, 'speaker_id')\n            # session-speaker :: many-to-many relationship\n            query_ = Session.query.filter(Session.speakers.any(id=speaker.id))\n\n        return query_\n\n    view_kwargs = True\n    methods = ['GET']\n    schema = SessionSchema\n    data_layer = {'session': db.session, 'model': Session, 'methods': {'query': query}}\n\n\nclass SessionDetail(ResourceDetail):\n    \"\"\"\n    Session detail by id\n    \"\"\"\n\n    def before_get_object(self, view_kwargs):\n        \"\"\"\n        before get method to get the resource id for fetching details\n        :param view_kwargs:\n        :return:\n        \"\"\"\n        if view_kwargs.get('event_identifier'):\n            event = safe_query(\n                Event, 'identifier', view_kwargs['event_identifier'], 'identifier'\n            )\n            view_kwargs['event_id'] = event.id\n\n    def before_update_object(self, session, data, view_kwargs):\n        \"\"\"\n        before update method to verify if session is locked before updating session object\n        :param event:\n        :param data:\n        :param view_kwargs:\n        :return:\n        \"\"\"\n        if session.is_locked:\n            if not (\n                has_access('is_admin')\n                or has_access('is_organizer', event_id=session.event_id)\n            ):\n                raise ForbiddenError(\n                    {'source': '/data/attributes/is-locked'},\n                    \"You don't have enough permissions to change this property\",\n                )\n\n        if session.is_locked and data.get('is_locked') != session.is_locked:\n            raise ForbiddenError(\n                {'source': '/data/attributes/is-locked'},\n                \"Locked sessions cannot be edited\",\n            )\n\n        if not can_edit_after_cfs_ends(session.event_id):\n            raise ForbiddenError(\n                {'source': ''}, \"Cannot edit session after the call for speaker is ended\"\n            )\n\n        data['complex_field_values'] = validate_custom_form_constraints_request(\n            'session', self.resource.schema, session, data\n        )\n\n    def after_update_object(self, session, data, view_kwargs):\n        \"\"\" Send email if session accepted or rejected \"\"\"\n\n        if (\n            'state' in data\n            and data.get('send_email', None)\n            and (session.state == 'accepted' or session.state == 'rejected')\n        ):\n\n            event = session.event\n            # Email for speaker\n            speakers = session.speakers\n            for speaker in speakers:\n                frontend_url = get_settings()['frontend_url']\n                link = \"{}/events/{}/sessions/{}\".format(\n                    frontend_url, event.identifier, session.id\n                )\n                if not speaker.is_email_overridden:\n                    send_email_session_accept_reject(speaker.email, session, link)\n                    send_notif_session_accept_reject(\n                        speaker, session.title, session.state, link, session.id\n                    )\n\n            # Email for owner\n            if session.event.get_owner():\n                owner = session.event.get_owner()\n                owner_email = owner.email\n                frontend_url = get_settings()['frontend_url']\n                link = \"{}/events/{}/sessions/{}\".format(\n                    frontend_url, event.identifier, session.id\n                )\n                send_email_session_accept_reject(owner_email, session, link)\n                send_notif_session_accept_reject(\n                    owner, session.title, session.state, link, session.id\n                )\n        if 'state' in data:\n            entry_count = SessionsSpeakersLink.query.filter_by(session_id=session.id)\n            if entry_count.count() == 0:\n                is_patch_request = False\n            else:\n                is_patch_request = True\n\n            if is_patch_request:\n                for focus_session in entry_count:\n                    focus_session.session_state = session.state\n                db.session.commit()\n            else:\n                current_session = Session.query.filter_by(id=session.id).first()\n                for speaker in current_session.speakers:\n                    session_speaker_link = SessionsSpeakersLink(\n                        session_state=session.state,\n                        session_id=session.id,\n                        event_id=session.event.id,\n                        speaker_id=speaker.id,\n                    )\n                    save_to_db(session_speaker_link, \"Session Speaker Link Saved\")\n\n    decorators = (api.has_permission('is_speaker_for_session', methods=\"PATCH,DELETE\"),)\n    schema = SessionSchema\n    data_layer = {\n        'session': db.session,\n        'model': Session,\n        'methods': {\n            'before_update_object': before_update_object,\n            'before_get_object': before_get_object,\n            'after_update_object': after_update_object,\n        },\n    }\n\n\nclass SessionRelationshipRequired(ResourceRelationship):\n    \"\"\"\n    Session Relationship\n    \"\"\"\n\n    schema = SessionSchema\n    decorators = (api.has_permission('is_speaker_for_session', methods=\"PATCH,DELETE\"),)\n    methods = ['GET', 'PATCH']\n    data_layer = {'session': db.session, 'model': Session}\n\n\nclass SessionRelationshipOptional(ResourceRelationship):\n    \"\"\"\n    Session Relationship\n    \"\"\"\n\n    schema = SessionSchema\n    decorators = (api.has_permission('is_speaker_for_session', methods=\"PATCH,DELETE\"),)\n    data_layer = {'session': db.session, 'model': Session}\n", "path": "app/api/sessions.py"}], "after_files": [{"content": "from flask import request\nfrom flask_jwt_extended import current_user\nfrom flask_rest_jsonapi import ResourceDetail, ResourceList, ResourceRelationship\nfrom flask_rest_jsonapi.querystring import QueryStringManager as QSManager\n\nfrom app.api.bootstrap import api\nfrom app.api.events import Event\nfrom app.api.helpers.custom_forms import validate_custom_form_constraints_request\nfrom app.api.helpers.db import get_count, safe_query, safe_query_kwargs, save_to_db\nfrom app.api.helpers.errors import ForbiddenError\nfrom app.api.helpers.files import make_frontend_url\nfrom app.api.helpers.mail import send_email_new_session, send_email_session_accept_reject\nfrom app.api.helpers.notification import (\n    send_notif_new_session_organizer,\n    send_notif_session_accept_reject,\n)\nfrom app.api.helpers.permission_manager import has_access\nfrom app.api.helpers.query import event_query\nfrom app.api.helpers.speaker import can_edit_after_cfs_ends\nfrom app.api.helpers.utilities import require_relationship\nfrom app.api.schema.sessions import SessionSchema\nfrom app.models import db\nfrom app.models.microlocation import Microlocation\nfrom app.models.session import Session\nfrom app.models.session_speaker_link import SessionsSpeakersLink\nfrom app.models.session_type import SessionType\nfrom app.models.speaker import Speaker\nfrom app.models.track import Track\nfrom app.models.user import User\nfrom app.settings import get_settings\n\n\nclass SessionListPost(ResourceList):\n    \"\"\"\n    List Sessions\n    \"\"\"\n\n    def before_post(self, args, kwargs, data):\n        \"\"\"\n        before post method to check for required relationship and proper permission\n        :param args:\n        :param kwargs:\n        :param data:\n        :return:\n        \"\"\"\n        require_relationship(['event', 'track'], data)\n        data['creator_id'] = current_user.id\n        if (\n            get_count(\n                db.session.query(Event).filter_by(\n                    id=int(data['event']), is_sessions_speakers_enabled=False\n                )\n            )\n            > 0\n        ):\n            raise ForbiddenError({'pointer': ''}, \"Sessions are disabled for this Event\")\n\n        data['complex_field_values'] = validate_custom_form_constraints_request(\n            'session', self.schema, Session(event_id=data['event']), data\n        )\n\n    def after_create_object(self, session, data, view_kwargs):\n        \"\"\"\n        method to send email for creation of new session\n        mails session link to the concerned user\n        :param session:\n        :param data:\n        :param view_kwargs:\n        :return:\n        \"\"\"\n        if session.event.get_owner():\n            event_name = session.event.name\n            owner = session.event.get_owner()\n            owner_email = owner.email\n            event = session.event\n            link = make_frontend_url(\n                \"/events/{}/sessions/{}\".format(event.identifier, session.id)\n            )\n            send_email_new_session(owner_email, event_name, link)\n            send_notif_new_session_organizer(owner, event_name, link, session.id)\n\n        for speaker in session.speakers:\n            session_speaker_link = SessionsSpeakersLink(\n                session_state=session.state,\n                session_id=session.id,\n                event_id=session.event.id,\n                speaker_id=speaker.id,\n            )\n            save_to_db(session_speaker_link, \"Session Speaker Link Saved\")\n\n    decorators = (api.has_permission('create_event'),)\n    schema = SessionSchema\n    data_layer = {\n        'session': db.session,\n        'model': Session,\n        'methods': {'after_create_object': after_create_object},\n    }\n\n\ndef get_distinct_sort_fields(schema, model, sort=True):\n    \"\"\"Due to the poor code of flask-rest-jsonapi, distinct query needed\n       in sessions API to remove duplicate sessions can't be sorted on\n       returning subquery, thus we need to add all sort fields in distinct\n       group and repeat it in sort group as well\"\"\"\n    fields = []\n    qs = QSManager(request.args, schema)\n    for sort_opt in qs.sorting:\n        field = sort_opt['field']\n        if not hasattr(model, field):\n            continue\n        field = getattr(model, field)\n        if sort:\n            field = getattr(field, sort_opt['order'])()\n        fields.append(field)\n    field = Session.id\n    if sort:\n        field = field.desc()\n    fields.append(field)\n    return fields\n\n\nclass SessionList(ResourceList):\n    \"\"\"\n    List Sessions\n    \"\"\"\n\n    def query(self, view_kwargs):\n        \"\"\"\n        query method for SessionList class\n        :param view_kwargs:\n        :return:\n        \"\"\"\n        query_ = self.session.query(Session)\n        if view_kwargs.get('track_id') is not None:\n            track = safe_query_kwargs(Track, view_kwargs, 'track_id')\n            query_ = query_.join(Track).filter(Track.id == track.id)\n        if view_kwargs.get('session_type_id') is not None:\n            session_type = safe_query_kwargs(SessionType, view_kwargs, 'session_type_id')\n            query_ = query_.join(SessionType).filter(SessionType.id == session_type.id)\n        if view_kwargs.get('microlocation_id') is not None:\n            microlocation = safe_query_kwargs(\n                Microlocation, view_kwargs, 'microlocation_id',\n            )\n            query_ = query_.join(Microlocation).filter(\n                Microlocation.id == microlocation.id\n            )\n        if view_kwargs.get('user_id') is not None:\n            user = safe_query_kwargs(User, view_kwargs, 'user_id')\n            query_ = (\n                query_.join(User)\n                .join(Speaker)\n                .filter(\n                    (\n                        User.id == user.id\n                        or Session.speakers.any(Speaker.user_id == user.id)\n                    )\n                )\n                .distinct(*get_distinct_sort_fields(SessionSchema, Session, sort=False))\n                .order_by(*get_distinct_sort_fields(SessionSchema, Session))\n            )\n        query_ = event_query(query_, view_kwargs)\n        if view_kwargs.get('speaker_id'):\n            speaker = safe_query_kwargs(Speaker, view_kwargs, 'speaker_id')\n            # session-speaker :: many-to-many relationship\n            query_ = Session.query.filter(Session.speakers.any(id=speaker.id))\n\n        return query_\n\n    view_kwargs = True\n    methods = ['GET']\n    schema = SessionSchema\n    data_layer = {'session': db.session, 'model': Session, 'methods': {'query': query}}\n\n\nclass SessionDetail(ResourceDetail):\n    \"\"\"\n    Session detail by id\n    \"\"\"\n\n    def before_get_object(self, view_kwargs):\n        \"\"\"\n        before get method to get the resource id for fetching details\n        :param view_kwargs:\n        :return:\n        \"\"\"\n        if view_kwargs.get('event_identifier'):\n            event = safe_query(\n                Event, 'identifier', view_kwargs['event_identifier'], 'identifier'\n            )\n            view_kwargs['event_id'] = event.id\n\n    def before_update_object(self, session, data, view_kwargs):\n        \"\"\"\n        before update method to verify if session is locked before updating session object\n        :param event:\n        :param data:\n        :param view_kwargs:\n        :return:\n        \"\"\"\n        if session.is_locked:\n            if not (\n                has_access('is_admin')\n                or has_access('is_organizer', event_id=session.event_id)\n            ):\n                raise ForbiddenError(\n                    {'source': '/data/attributes/is-locked'},\n                    \"You don't have enough permissions to change this property\",\n                )\n\n        if session.is_locked and data.get('is_locked') != session.is_locked:\n            raise ForbiddenError(\n                {'source': '/data/attributes/is-locked'},\n                \"Locked sessions cannot be edited\",\n            )\n\n        if not can_edit_after_cfs_ends(session.event_id):\n            raise ForbiddenError(\n                {'source': ''}, \"Cannot edit session after the call for speaker is ended\"\n            )\n\n        data['complex_field_values'] = validate_custom_form_constraints_request(\n            'session', self.resource.schema, session, data\n        )\n\n    def after_update_object(self, session, data, view_kwargs):\n        \"\"\" Send email if session accepted or rejected \"\"\"\n\n        if (\n            'state' in data\n            and data.get('send_email', None)\n            and (session.state == 'accepted' or session.state == 'rejected')\n        ):\n\n            event = session.event\n            # Email for speaker\n            speakers = session.speakers\n            for speaker in speakers:\n                frontend_url = get_settings()['frontend_url']\n                link = \"{}/events/{}/sessions/{}\".format(\n                    frontend_url, event.identifier, session.id\n                )\n                if not speaker.is_email_overridden:\n                    send_email_session_accept_reject(speaker.email, session, link)\n                    send_notif_session_accept_reject(\n                        speaker, session.title, session.state, link, session.id\n                    )\n\n            # Email for owner\n            if session.event.get_owner():\n                owner = session.event.get_owner()\n                owner_email = owner.email\n                frontend_url = get_settings()['frontend_url']\n                link = \"{}/events/{}/sessions/{}\".format(\n                    frontend_url, event.identifier, session.id\n                )\n                send_email_session_accept_reject(owner_email, session, link)\n                send_notif_session_accept_reject(\n                    owner, session.title, session.state, link, session.id\n                )\n        if 'state' in data:\n            entry_count = SessionsSpeakersLink.query.filter_by(session_id=session.id)\n            if entry_count.count() == 0:\n                is_patch_request = False\n            else:\n                is_patch_request = True\n\n            if is_patch_request:\n                for focus_session in entry_count:\n                    focus_session.session_state = session.state\n                db.session.commit()\n            else:\n                current_session = Session.query.filter_by(id=session.id).first()\n                for speaker in current_session.speakers:\n                    session_speaker_link = SessionsSpeakersLink(\n                        session_state=session.state,\n                        session_id=session.id,\n                        event_id=session.event.id,\n                        speaker_id=speaker.id,\n                    )\n                    save_to_db(session_speaker_link, \"Session Speaker Link Saved\")\n\n    decorators = (api.has_permission('is_speaker_for_session', methods=\"PATCH,DELETE\"),)\n    schema = SessionSchema\n    data_layer = {\n        'session': db.session,\n        'model': Session,\n        'methods': {\n            'before_update_object': before_update_object,\n            'before_get_object': before_get_object,\n            'after_update_object': after_update_object,\n        },\n    }\n\n\nclass SessionRelationshipRequired(ResourceRelationship):\n    \"\"\"\n    Session Relationship\n    \"\"\"\n\n    schema = SessionSchema\n    decorators = (api.has_permission('is_speaker_for_session', methods=\"PATCH,DELETE\"),)\n    methods = ['GET', 'PATCH']\n    data_layer = {'session': db.session, 'model': Session}\n\n\nclass SessionRelationshipOptional(ResourceRelationship):\n    \"\"\"\n    Session Relationship\n    \"\"\"\n\n    schema = SessionSchema\n    decorators = (api.has_permission('is_speaker_for_session', methods=\"PATCH,DELETE\"),)\n    data_layer = {'session': db.session, 'model': Session}\n", "path": "app/api/sessions.py"}]}