From 55e2e21c6612a5410ffa059b9fbe38095203855b Mon Sep 17 00:00:00 2001 From: nnhathung Date: Thu, 3 Aug 2023 09:53:48 +0700 Subject: [PATCH 01/15] fix issue user request check in not admin --- app/api/user_check_in.py | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/app/api/user_check_in.py b/app/api/user_check_in.py index 1fc172d7c0..94a8df3f64 100644 --- a/app/api/user_check_in.py +++ b/app/api/user_check_in.py @@ -5,7 +5,6 @@ from sqlalchemy.orm.exc import NoResultFound from app.api.helpers.errors import UnprocessableEntityError -from app.api.helpers.permission_manager import has_access from app.api.helpers.permissions import jwt_required from app.api.helpers.static import STATION_TYPE from app.api.helpers.user_check_in import ( @@ -77,31 +76,16 @@ def before_post(_args, _kwargs, data): :return: """ require_relationship(['station'], data) - if not has_access('is_coorganizer', station=data.get('station')): - raise ObjectNotFound( - {'parameter': 'station'}, - f"Station: {data['station']} not found", - ) try: station = db.session.query(Station).filter_by(id=data.get('station')).one() except NoResultFound: raise ObjectNotFound({'parameter': data.get('station')}, "Station: not found") require_relationship(['ticket_holder'], data) - if not has_access('is_coorganizer', ticket_holder=data.get('ticket_holder')): - raise ObjectNotFound( - {'parameter': 'ticket_holder'}, - f"TicketHolder: {data['ticket_holder']} not found", - ) if station.station_type != STATION_TYPE.get('registration') or data.get( 'session' ): require_relationship(['session'], data) - if not has_access('is_coorganizer', session=data.get('session')): - raise ObjectNotFound( - {'parameter': 'session'}, - f"Session: {data['session']} not found", - ) def before_create_object(self, data, _view_kwargs): """ From b4625286612d8692105aca7f55374a91fd9f798f Mon Sep 17 00:00:00 2001 From: nnhathung Date: Mon, 7 Aug 2023 16:53:34 +0700 Subject: [PATCH 02/15] format code --- app/api/custom/attendees.py | 33 ++++++++++++++++ app/api/schema/attendees.py | 2 + app/api/user_check_in.py | 39 ++++++++++++++++--- app/models/ticket_holder.py | 2 + .../rev-2023-08-07-15:52:49-3e8e18c0bebe_.py | 29 ++++++++++++++ 5 files changed, 100 insertions(+), 5 deletions(-) create mode 100644 migrations/versions/rev-2023-08-07-15:52:49-3e8e18c0bebe_.py diff --git a/app/api/custom/attendees.py b/app/api/custom/attendees.py index 8ba665edc6..c621445f63 100644 --- a/app/api/custom/attendees.py +++ b/app/api/custom/attendees.py @@ -1,13 +1,16 @@ from flask import Blueprint, abort, jsonify, make_response, request from flask_jwt_extended import current_user +from flask_rest_jsonapi.exceptions import ObjectNotFound from sqlalchemy.orm.exc import NoResultFound +from app.api.helpers.db import safe_query_by_id from app.api.helpers.errors import ForbiddenError, NotFoundError, UnprocessableEntityError from app.api.helpers.mail import send_email_to_attendees from app.api.helpers.permission_manager import has_access from app.api.helpers.permissions import jwt_required from app.models import db from app.models.order import Order +from app.models.ticket_holder import TicketHolder attendee_blueprint = Blueprint('attendee_blueprint', __name__, url_prefix='/v1') @@ -45,3 +48,33 @@ def send_receipt(): return jsonify(message="receipt sent to attendees") else: raise UnprocessableEntityError({'source': ''}, 'Order identifier missing') + + +@attendee_blueprint.route( + '/events//attendees//state', methods=['GET'] +) +@jwt_required +def check_attendee_state(attendee_id: int, event_id: int): + """ + API to check attendee state is check in/registered + @param event_id: event id + @param attendee_id: attendee id + """ + from app.models.event import Event + + Event.query.get_or_404(event_id) + try: + attendee = safe_query_by_id(TicketHolder, attendee_id) + except ObjectNotFound: + raise NotFoundError({'parameter': '{attendee_id}'}, "Attendee not found") + if event_id != attendee.event_id: + raise UnprocessableEntityError( + {'parameter': 'Attendee'}, + "Attendee not belong to this event", + ) + return jsonify( + { + 'is_registered': attendee.is_registered, + 'register_times': attendee.register_times, + } + ) diff --git a/app/api/schema/attendees.py b/app/api/schema/attendees.py index c74a78962a..d398dee96d 100644 --- a/app/api/schema/attendees.py +++ b/app/api/schema/attendees.py @@ -70,6 +70,8 @@ def validate_json(self, data, original_data): checkout_times = fields.Str(allow_none=True) attendee_notes = fields.Str(allow_none=True) is_checked_out = fields.Boolean() + is_registered = fields.Boolean() + register_times = fields.Str(allow_none=True) pdf_url = fields.Url(dump_only=True) complex_field_values = CustomFormValueField(allow_none=True) is_consent_form_field = fields.Boolean(allow_none=True) diff --git a/app/api/user_check_in.py b/app/api/user_check_in.py index efba85cce7..8343b3f50f 100644 --- a/app/api/user_check_in.py +++ b/app/api/user_check_in.py @@ -4,7 +4,9 @@ from flask_rest_jsonapi.exceptions import ObjectNotFound from sqlalchemy.orm.exc import NoResultFound +from app.api.helpers.db import save_to_db from app.api.helpers.errors import UnprocessableEntityError +from app.api.helpers.permission_manager import has_access from app.api.helpers.permissions import jwt_required from app.api.helpers.static import STATION_TYPE from app.api.helpers.user_check_in import ( @@ -18,6 +20,7 @@ from app.models.session import Session from app.models.session_type import SessionType from app.models.station import Station +from app.models.ticket_holder import TicketHolder from app.models.track import Track from app.models.user_check_in import UserCheckIn @@ -95,11 +98,29 @@ def before_create_object(self, data, _view_kwargs): :return: """ station = self.session.query(Station).filter_by(id=data.get('station')).one() + current_time = datetime.datetime.utcnow() if not has_access('is_coorganizer', event_id=station.event_id): raise UnprocessableEntityError( {'parameter': 'station'}, "Only admin/organiser/coorganizer of event only able to check in", ) + try: + attendee = ( + self.session.query(TicketHolder) + .filter_by(id=data.get('ticket_holder')) + .one() + ) + except NoResultFound: + raise ObjectNotFound( + {'parameter': data.get('attendee')}, "Attendee: not found" + ) + + if attendee.event_id != station.event_id: + raise UnprocessableEntityError( + {'parameter': 'Attendee'}, + "Attendee not belong to this event", + ) + if station.station_type != STATION_TYPE.get('registration'): # validate if microlocation_id from session matches with station session = self.session.query(Session).filter_by(id=data.get('session')).one() @@ -138,6 +159,7 @@ def before_create_object(self, data, _view_kwargs): validate_check_in_out_status( station=station, attendee_data=attendee_check_in_status ) + data['check_in_out_at'] = current_time else: if station.station_type == STATION_TYPE.get('registration'): attendee_check_in_status = ( @@ -157,6 +179,10 @@ def before_create_object(self, data, _view_kwargs): }, "Attendee already registered.", ) + # update register time for attendee + attendee.is_registered = True + attendee.register_times = current_time + save_to_db(attendee) if station.station_type == STATION_TYPE.get('daily'): attendee_check_in_status = ( self.session.query(UserCheckIn) @@ -177,11 +203,14 @@ def before_create_object(self, data, _view_kwargs): "Attendee already check daily on station.", ) - if station.station_type in ( - STATION_TYPE.get('check in'), - STATION_TYPE.get('check out'), - ): - data['check_in_out_at'] = datetime.datetime.utcnow() + if station.station_type == STATION_TYPE.get('check in'): + attendee.is_checked_in = True + attendee.checkin_times = current_time + save_to_db(attendee) + if station.station_type == STATION_TYPE.get('check out'): + attendee.is_checked_out = True + attendee.checkout_times = current_time + save_to_db(attendee) schema = UserCheckInSchema methods = [ diff --git a/app/models/ticket_holder.py b/app/models/ticket_holder.py index 3c956428c1..dc12ef6c55 100644 --- a/app/models/ticket_holder.py +++ b/app/models/ticket_holder.py @@ -54,9 +54,11 @@ class TicketHolder(SoftDeletionModel): order_id: int = db.Column(db.Integer, db.ForeignKey('orders.id', ondelete='CASCADE')) is_checked_in: bool = db.Column(db.Boolean, default=False) is_checked_out: bool = db.Column(db.Boolean, default=False) + is_registered: bool = db.Column(db.Boolean, default=False) device_name_checkin: str = db.Column(db.String) checkin_times: str = db.Column(db.String) checkout_times: str = db.Column(db.String) + register_times: str = db.Column(db.String) attendee_notes: str = db.Column(db.String) event_id: int = db.Column( db.Integer, db.ForeignKey('events.id', ondelete='CASCADE'), nullable=False diff --git a/migrations/versions/rev-2023-08-07-15:52:49-3e8e18c0bebe_.py b/migrations/versions/rev-2023-08-07-15:52:49-3e8e18c0bebe_.py new file mode 100644 index 0000000000..875768ed45 --- /dev/null +++ b/migrations/versions/rev-2023-08-07-15:52:49-3e8e18c0bebe_.py @@ -0,0 +1,29 @@ +"""empty message + +Revision ID: 3e8e18c0bebe +Revises: 8b5bc48e1d4c +Create Date: 2023-08-07 15:52:49.656233 + +""" + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '3e8e18c0bebe' +down_revision = '8b5bc48e1d4c' + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('ticket_holders', sa.Column('is_registered', sa.Boolean(), server_default='False', nullable=True)) + op.add_column('ticket_holders', sa.Column('register_times', sa.String(), nullable=True)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('ticket_holders', 'register_times') + op.drop_column('ticket_holders', 'is_registered') + # ### end Alembic commands ### From 78d9630ea3d2ccce4358c10b9b60bea7e52c0ab5 Mon Sep 17 00:00:00 2001 From: nnhathung Date: Mon, 7 Aug 2023 18:28:32 +0700 Subject: [PATCH 03/15] feature-9040 + feature-9042: update unit test --- .../api/attendee/test_attendee_state.py | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 tests/all/integration/api/attendee/test_attendee_state.py diff --git a/tests/all/integration/api/attendee/test_attendee_state.py b/tests/all/integration/api/attendee/test_attendee_state.py new file mode 100644 index 0000000000..0f601f5df8 --- /dev/null +++ b/tests/all/integration/api/attendee/test_attendee_state.py @@ -0,0 +1,77 @@ +import json + +from tests.factories.attendee import AttendeeOrderTicketSubFactory, AttendeeSubFactory +from tests.factories.event import EventFactoryBasic +from tests.factories.microlocation import MicrolocationSubFactory +from tests.factories.session import SessionSubFactory +from tests.factories.station import StationSubFactory +from tests.factories.ticket import TicketSubFactory + + +def get_minimal_attendee(db, user): + attendee = AttendeeOrderTicketSubFactory( + email=None, address=None, city=None, state=None, country=None, order__user=user + ) + db.session.commit() + + return attendee + + +def test_attendee_not_register_yet(db, client, jwt, user): + attendee = get_minimal_attendee(db, user) + response = client.get( + f'/v1/events/{attendee.event_id}/attendees/{attendee.id}/state', + content_type='application/vnd.api+json', + headers=jwt, + ) + assert response.status_code == 200 + assert json.loads(response.data)['is_registered'] is False + + +def test_attendee_registered(db, client, jwt, user): + user.is_super_admin = True + event = EventFactoryBasic() + microlocation = MicrolocationSubFactory(event=event) + ticket = TicketSubFactory(event=event) + station = StationSubFactory( + event=event, microlocation=microlocation, station_type='registration' + ) + session = SessionSubFactory( + event=event, + microlocation=microlocation, + ) + attendee = AttendeeSubFactory( + event=event, + ticket=ticket, + ) + db.session.commit() + data = json.dumps( + { + "data": { + "type": "user_check_in", + "attributes": {}, + "relationships": { + "station": {"data": {"id": str(station.id), "type": "station"}}, + "session": {"data": {"id": str(session.id), "type": "session"}}, + "ticket_holder": { + "data": {"id": str(attendee.id), "type": "attendee"} + }, + }, + } + } + ) + + client.post( + '/v1/user-check-in', + content_type='application/vnd.api+json', + headers=jwt, + data=data, + ) + + response = client.get( + f'/v1/events/{event.id}/attendees/{attendee.id}/state', + content_type='application/vnd.api+json', + headers=jwt, + ) + assert response.status_code == 200 + assert json.loads(response.data)['is_registered'] is True From 7254fa40309e9c3d18497a203a796d90d89c17ad Mon Sep 17 00:00:00 2001 From: nnhathung Date: Mon, 7 Aug 2023 23:28:12 +0700 Subject: [PATCH 04/15] fix UT --- app/api/user_check_in.py | 1 - .../integration/api/users_check_in/test_users_check_in_api.py | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/api/user_check_in.py b/app/api/user_check_in.py index 8343b3f50f..94d31c4fbf 100644 --- a/app/api/user_check_in.py +++ b/app/api/user_check_in.py @@ -206,7 +206,6 @@ def before_create_object(self, data, _view_kwargs): if station.station_type == STATION_TYPE.get('check in'): attendee.is_checked_in = True attendee.checkin_times = current_time - save_to_db(attendee) if station.station_type == STATION_TYPE.get('check out'): attendee.is_checked_out = True attendee.checkout_times = current_time diff --git a/tests/all/integration/api/users_check_in/test_users_check_in_api.py b/tests/all/integration/api/users_check_in/test_users_check_in_api.py index ccdfe631b8..129932e010 100644 --- a/tests/all/integration/api/users_check_in/test_users_check_in_api.py +++ b/tests/all/integration/api/users_check_in/test_users_check_in_api.py @@ -4,7 +4,7 @@ from tests.factories.event import EventFactoryBasic from tests.factories.microlocation import MicrolocationSubFactory from tests.factories.session import SessionSubFactory -from tests.factories.station import StationFactory +from tests.factories.station import StationSubFactory from tests.factories.ticket import TicketFactory @@ -18,7 +18,7 @@ def test_create_station_from_user_check_in(db, client, jwt, user): ticket = TicketFactory( event=event, ) - station = StationFactory( + station = StationSubFactory( event=event, microlocation=microlocation, station_type='registration' ) session = SessionSubFactory( From b6ee688ff5331b2aa6a616b92e53c0dcea5354f8 Mon Sep 17 00:00:00 2001 From: nnhathung Date: Tue, 8 Aug 2023 11:11:30 +0700 Subject: [PATCH 05/15] update payload + api route --- app/api/custom/attendees.py | 30 ++++++++++----- docs/api/blueprint/attendees.apib | 38 ++++++++++++++++++- .../api/attendee/test_attendee_state.py | 9 ++++- 3 files changed, 64 insertions(+), 13 deletions(-) diff --git a/app/api/custom/attendees.py b/app/api/custom/attendees.py index c621445f63..db24a26269 100644 --- a/app/api/custom/attendees.py +++ b/app/api/custom/attendees.py @@ -50,24 +50,29 @@ def send_receipt(): raise UnprocessableEntityError({'source': ''}, 'Order identifier missing') -@attendee_blueprint.route( - '/events//attendees//state', methods=['GET'] -) +@attendee_blueprint.route('/states', methods=['GET']) @jwt_required -def check_attendee_state(attendee_id: int, event_id: int): +def check_attendee_state(): """ API to check attendee state is check in/registered - @param event_id: event id - @param attendee_id: attendee id """ from app.models.event import Event - Event.query.get_or_404(event_id) + event_id = request.args.get('event_id') + attendee_id = request.args.get('attendee_id') + if event_id is not None: + validate_param_as_id(event_id) + if attendee_id is not None: + validate_param_as_id(attendee_id) + try: + event = safe_query_by_id(Event, event_id) + except ObjectNotFound: + raise NotFoundError({'parameter': f'{event_id}'}, "Event not found") try: attendee = safe_query_by_id(TicketHolder, attendee_id) except ObjectNotFound: - raise NotFoundError({'parameter': '{attendee_id}'}, "Attendee not found") - if event_id != attendee.event_id: + raise NotFoundError({'parameter': f'{attendee_id}'}, "Attendee not found") + if event.id != attendee.event_id: raise UnprocessableEntityError( {'parameter': 'Attendee'}, "Attendee not belong to this event", @@ -78,3 +83,10 @@ def check_attendee_state(attendee_id: int, event_id: int): 'register_times': attendee.register_times, } ) + + +def validate_param_as_id(param): + if not (isinstance(param, int) or (isinstance(param, str) and param.isdigit())): + raise UnprocessableEntityError( + {'parameter': f'{param}'}, f'{param} is not a valid id' + ) diff --git a/docs/api/blueprint/attendees.apib b/docs/api/blueprint/attendees.apib index 03c48c35a0..1aefd5dd99 100644 --- a/docs/api/blueprint/attendees.apib +++ b/docs/api/blueprint/attendees.apib @@ -15,6 +15,8 @@ Related to ticket holders(attendees) of an event (free, paid, donation) to the e | `is-checked-out` | If the attendee has checked out | boolean | - | | `attendee-notes` | Comma separated attendee notes | string | - | | `pdf-url` | pdf url of the Attendee | url | - | +| `is-registered` | If the attendee is registered | boolean | - | +| `register-times` | Comma separated register times | string | - | ## Send order receipts [v1/attendees/send-receipt] @@ -109,6 +111,7 @@ Get a list of attendees of an order. "deleted-at": null, "work-address": null, "checkin-times": null, + "register-times": null, "state": "example", "country": "IN", "lastname": "UnDoe", @@ -116,6 +119,7 @@ Get a list of attendees of an order. "phone": null, "company": null, "is-checked-in": false, + "is-registered": false, "gender": null, "shipping-address": null, "blog": null, @@ -146,7 +150,7 @@ Get a list of attendees of an order. "self": "/v1/orders/7201904e-c695-4251-a30a-61765a37ff24/attendees" } } - + ## List Attendees under an event [/v1/events/{event_id}/attendees] + Parameters + event_id: 1 (integer) - Identifier of the event @@ -206,6 +210,7 @@ Get a list of attendees of an event. "deleted-at": null, "work-address": null, "checkin-times": null, + "register-times": null, "state": "example", "country": "IN", "lastname": "UnDoe", @@ -213,6 +218,7 @@ Get a list of attendees of an event. "phone": null, "company": null, "is-checked-in": false, + "is-registered": false, "gender": null, "shipping-address": null, "blog": null, @@ -279,6 +285,8 @@ Search a list of attendees of an event. "phone": null, "company": null, "is-checked-in": false, + "is-registered": false, + "register-times": null, "gender": null, "shipping-address": null, "blog": null, @@ -364,6 +372,8 @@ Get a list of attendees of a ticket. "phone": null, "company": null, "is-checked-in": false, + "is-registered": false, + "register-times": null, "gender": null, "shipping-address": null, "blog": null, @@ -445,6 +455,8 @@ Get a single attendee. "phone": null, "company": null, "is-checked-in": false, + "is-registered": false, + "register-times": null, "gender": null, "shipping-address": null, "blog": null, @@ -589,4 +601,26 @@ Delete a single attendee. "version": "1.0" } } - \ No newline at end of file + +## Get Attendee State [/v1/states{?event_id,attendee_id}] ++ Parameters + + event_id: 1 (integer) - ID of the event in + + attendee_id: 1 (integer) - ID of the attendee + +### Get Attendee State [GET] +Check attendee state if attendee is registered or not. + ++ Request + + + Headers + + Accept: application/vnd.api+json + + Authorization: JWT + ++ Response 200 (application/json) + + { + "is_registered": true, + "register_times": "2023-08-08 03:03:52.827812" + } diff --git a/tests/all/integration/api/attendee/test_attendee_state.py b/tests/all/integration/api/attendee/test_attendee_state.py index 0f601f5df8..a5ebbf8390 100644 --- a/tests/all/integration/api/attendee/test_attendee_state.py +++ b/tests/all/integration/api/attendee/test_attendee_state.py @@ -19,10 +19,12 @@ def get_minimal_attendee(db, user): def test_attendee_not_register_yet(db, client, jwt, user): attendee = get_minimal_attendee(db, user) + data = {'event_id': attendee.event_id, 'attendee_id': attendee.id} response = client.get( - f'/v1/events/{attendee.event_id}/attendees/{attendee.id}/state', + '/v1/states', content_type='application/vnd.api+json', headers=jwt, + query_string=data, ) assert response.status_code == 200 assert json.loads(response.data)['is_registered'] is False @@ -68,10 +70,13 @@ def test_attendee_registered(db, client, jwt, user): data=data, ) + data = {'event_id': event.id, 'attendee_id': attendee.id} + response = client.get( - f'/v1/events/{event.id}/attendees/{attendee.id}/state', + '/v1/states', content_type='application/vnd.api+json', headers=jwt, + query_string=data, ) assert response.status_code == 200 assert json.loads(response.data)['is_registered'] is True From a934caace9be233187f17b6bf4dcd98f1a9c0229 Mon Sep 17 00:00:00 2001 From: nnhathung Date: Tue, 8 Aug 2023 11:38:21 +0700 Subject: [PATCH 06/15] update unit test --- tests/all/integration/api/attendee/test_attendee_state.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/all/integration/api/attendee/test_attendee_state.py b/tests/all/integration/api/attendee/test_attendee_state.py index a5ebbf8390..d25695e0ec 100644 --- a/tests/all/integration/api/attendee/test_attendee_state.py +++ b/tests/all/integration/api/attendee/test_attendee_state.py @@ -22,7 +22,7 @@ def test_attendee_not_register_yet(db, client, jwt, user): data = {'event_id': attendee.event_id, 'attendee_id': attendee.id} response = client.get( '/v1/states', - content_type='application/vnd.api+json', + content_type='application/json', headers=jwt, query_string=data, ) @@ -74,7 +74,7 @@ def test_attendee_registered(db, client, jwt, user): response = client.get( '/v1/states', - content_type='application/vnd.api+json', + content_type='application/json', headers=jwt, query_string=data, ) From f4c78779e5a8e2348453eeb2a0a074b6b17ecd60 Mon Sep 17 00:00:00 2001 From: nnhathung Date: Tue, 8 Aug 2023 13:56:18 +0700 Subject: [PATCH 07/15] update payload for attendee state api --- docs/api/blueprint/attendees.apib | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api/blueprint/attendees.apib b/docs/api/blueprint/attendees.apib index 1aefd5dd99..d565ecc374 100644 --- a/docs/api/blueprint/attendees.apib +++ b/docs/api/blueprint/attendees.apib @@ -604,8 +604,8 @@ Delete a single attendee. ## Get Attendee State [/v1/states{?event_id,attendee_id}] + Parameters - + event_id: 1 (integer) - ID of the event in - + attendee_id: 1 (integer) - ID of the attendee + + event_id: 1 (integer) - Identifier of the event + + attendee_id: 1 (integer) - ID of the attendee in the form of an integer ### Get Attendee State [GET] Check attendee state if attendee is registered or not. From dd3b39284a3d24ca7078da1573abb03ac5a1072a Mon Sep 17 00:00:00 2001 From: nnhathung Date: Tue, 8 Aug 2023 16:05:14 +0700 Subject: [PATCH 08/15] fix payload --- app/api/user_check_in.py | 8 -------- tests/hook_main.py | 36 ++++++++++++++++++++++++++++++++---- 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/app/api/user_check_in.py b/app/api/user_check_in.py index 94d31c4fbf..f09eaecab3 100644 --- a/app/api/user_check_in.py +++ b/app/api/user_check_in.py @@ -203,14 +203,6 @@ def before_create_object(self, data, _view_kwargs): "Attendee already check daily on station.", ) - if station.station_type == STATION_TYPE.get('check in'): - attendee.is_checked_in = True - attendee.checkin_times = current_time - if station.station_type == STATION_TYPE.get('check out'): - attendee.is_checked_out = True - attendee.checkout_times = current_time - save_to_db(attendee) - schema = UserCheckInSchema methods = [ 'POST', diff --git a/tests/hook_main.py b/tests/hook_main.py index a94fa26fd3..96f4beec62 100644 --- a/tests/hook_main.py +++ b/tests/hook_main.py @@ -50,11 +50,11 @@ from tests.factories.sponsor import SponsorFactory from tests.factories.speakers_call import SpeakersCallFactory from tests.factories.tax import TaxFactory -from tests.factories.station import StationFactory +from tests.factories.station import StationFactory, StationSubFactory from tests.factories.station_store_pax import StationStorePaxFactory from tests.factories.session import SessionFactory, SessionFactoryBasic, SessionSubFactory from tests.factories.speaker import SpeakerFactory -from tests.factories.ticket import TicketFactory +from tests.factories.ticket import TicketFactory, TicketSubFactory from tests.factories.attendee import ( AttendeeFactory, AttendeeOrderSubFactory, @@ -89,7 +89,6 @@ _create_taxed_tickets, ) - stash = {} api_username = "open_event_test_user@fossasia.org" api_password = "fossasia" @@ -2181,6 +2180,35 @@ def get_attendees_from_ticket(transaction): db.session.commit() +@hooks.before("Attendees > Get Attendee State > Get Attendee State") +def get_attendee_state(transaction): + """ + GET /v1/states{?event_id,attendee_id} + :param transaction: + :return: + """ + with stash['app'].app_context(): + event = EventFactoryBasic() + microlocation = MicrolocationSubFactory( + event=event, + ) + ticket = TicketSubFactory( + event=event, + ) + StationSubFactory( + event=event, microlocation=microlocation, station_type='registration' + ) + SessionSubFactory( + event=event, + microlocation=microlocation, + ) + AttendeeSubFactory( + event=event, + ticket=ticket, + ) + db.session.commit() + + # ------------------------- Tracks ------------------------- @hooks.before("Tracks > Tracks Collection > Create Track") def track_post(transaction): @@ -5139,7 +5167,7 @@ def create_user_check_in(transaction): ticket = TicketFactory( event=event, ) - StationFactory( + StationSubFactory( event=event, microlocation=microlocation, station_type='registration' ) SessionSubFactory( From 5947c7de0c2671cd1cd6489c4abeee6a52bdf2d4 Mon Sep 17 00:00:00 2001 From: nnhathung Date: Tue, 8 Aug 2023 16:20:26 +0700 Subject: [PATCH 09/15] fix ci/cd --- app/api/custom/attendees.py | 5 +++++ .../api/attendee/test_attendee_state.py | 20 +++++++++++++++++++ tests/hook_main.py | 12 +++++------ 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/app/api/custom/attendees.py b/app/api/custom/attendees.py index db24a26269..083e567a9d 100644 --- a/app/api/custom/attendees.py +++ b/app/api/custom/attendees.py @@ -55,6 +55,7 @@ def send_receipt(): def check_attendee_state(): """ API to check attendee state is check in/registered + @return: user is registered or not """ from app.models.event import Event @@ -86,6 +87,10 @@ def check_attendee_state(): def validate_param_as_id(param): + """ + validate id if integer or not + @param param: param to check + """ if not (isinstance(param, int) or (isinstance(param, str) and param.isdigit())): raise UnprocessableEntityError( {'parameter': f'{param}'}, f'{param} is not a valid id' diff --git a/tests/all/integration/api/attendee/test_attendee_state.py b/tests/all/integration/api/attendee/test_attendee_state.py index d25695e0ec..6db065ad0c 100644 --- a/tests/all/integration/api/attendee/test_attendee_state.py +++ b/tests/all/integration/api/attendee/test_attendee_state.py @@ -9,6 +9,12 @@ def get_minimal_attendee(db, user): + """ + create attendee + @param db: db + @param user: user + @return: attendee created + """ attendee = AttendeeOrderTicketSubFactory( email=None, address=None, city=None, state=None, country=None, order__user=user ) @@ -18,6 +24,13 @@ def get_minimal_attendee(db, user): def test_attendee_not_register_yet(db, client, jwt, user): + """ + Testing for case attendee not register yet + @param db: db + @param client: client + @param jwt: jwt + @param user: user + """ attendee = get_minimal_attendee(db, user) data = {'event_id': attendee.event_id, 'attendee_id': attendee.id} response = client.get( @@ -31,6 +44,13 @@ def test_attendee_not_register_yet(db, client, jwt, user): def test_attendee_registered(db, client, jwt, user): + """ + Test user is already registered + @param db: db + @param client: client + @param jwt: jwt + @param user: user + """ user.is_super_admin = True event = EventFactoryBasic() microlocation = MicrolocationSubFactory(event=event) diff --git a/tests/hook_main.py b/tests/hook_main.py index 96f4beec62..5d21f920e0 100644 --- a/tests/hook_main.py +++ b/tests/hook_main.py @@ -521,7 +521,7 @@ def group_event_get_list(transaction): :return: """ with stash['app'].app_context(): - event = EventFactoryBasic() + EventFactoryBasic() group = GroupFactory() db.session.add(group) db.session.commit() @@ -842,7 +842,7 @@ def group_get_list_from_user(transaction): :return: """ with stash['app'].app_context(): - event = EventFactoryBasic() + EventFactoryBasic() group = GroupFactory() db.session.add(group) db.session.commit() @@ -856,7 +856,7 @@ def group_post(transaction): :return: """ with stash['app'].app_context(): - event = EventFactoryBasic() + EventFactoryBasic() group = GroupFactory() db.session.add(group) db.session.commit() @@ -870,7 +870,7 @@ def group_get_detail(transaction): :return: """ with stash['app'].app_context(): - event = EventFactoryBasic() + EventFactoryBasic() group = GroupFactory() db.session.add(group) db.session.commit() @@ -901,7 +901,7 @@ def group_patch(transaction): :return: """ with stash['app'].app_context(): - event = EventFactoryBasic() + EventFactoryBasic() group = GroupFactory() db.session.add(group) db.session.commit() @@ -915,7 +915,7 @@ def group_delete(transaction): :return: """ with stash['app'].app_context(): - event = EventFactoryBasic() + EventFactoryBasic() group = GroupFactory() db.session.add(group) db.session.commit() From 02471ac8959e00d97ddaff52a03b595699251cc4 Mon Sep 17 00:00:00 2001 From: nnhathung Date: Tue, 8 Aug 2023 16:29:47 +0700 Subject: [PATCH 10/15] fix ci/cd --- tests/hook_main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/hook_main.py b/tests/hook_main.py index 5d21f920e0..9164c21b4b 100644 --- a/tests/hook_main.py +++ b/tests/hook_main.py @@ -828,7 +828,7 @@ def group_get_list(transaction): :return: """ with stash['app'].app_context(): - event = EventFactoryBasic() + EventFactoryBasic() group = GroupFactory() db.session.add(group) db.session.commit() From 30cef4de58563ad52ccf11f722cd9a8359fbc8fd Mon Sep 17 00:00:00 2001 From: nnhathung Date: Tue, 8 Aug 2023 19:24:37 +0700 Subject: [PATCH 11/15] fix conflict --- app/api/user_check_in.py | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/app/api/user_check_in.py b/app/api/user_check_in.py index f09eaecab3..9965976456 100644 --- a/app/api/user_check_in.py +++ b/app/api/user_check_in.py @@ -97,8 +97,10 @@ def before_create_object(self, data, _view_kwargs): :param _view_kwargs: :return: """ - station = self.session.query(Station).filter_by(id=data.get('station')).one() - current_time = datetime.datetime.utcnow() + try: + station = db.session.query(Station).filter_by(id=data.get('station')).one() + except NoResultFound: + raise ObjectNotFound({'parameter': data.get('station')}, "Station: not found") if not has_access('is_coorganizer', event_id=station.event_id): raise UnprocessableEntityError( {'parameter': 'station'}, @@ -123,20 +125,28 @@ def before_create_object(self, data, _view_kwargs): if station.station_type != STATION_TYPE.get('registration'): # validate if microlocation_id from session matches with station - session = self.session.query(Session).filter_by(id=data.get('session')).one() + session = ( + self.session.query(Session).filter_by(id=data.get('session')).first() + ) + if session is None: + raise ObjectNotFound( + {'parameter': data.get('session')}, "Session: not found" + ) validate_microlocation(station=station, session=session) if session.session_type_id: session_type = ( self.session.query(SessionType) .filter(SessionType.id == session.session_type_id) - .one() + .first() ) - data['session_name'] = session_type.name + if session_type is not None: + data['session_name'] = session_type.name if session.track_id: track = ( - self.session.query(Track).filter(Track.id == session.track_id).one() + self.session.query(Track).filter(Track.id == session.track_id).first() ) - data['track_name'] = track.name + if track is not None: + data['track_name'] = track.name data['speaker_name'] = ', '.join( [str(speaker.name) for speaker in session.speakers] ) From fc0afd5e8878c1e681c45471b4bd8fabddc114fd Mon Sep 17 00:00:00 2001 From: nnhathung Date: Tue, 8 Aug 2023 19:27:33 +0700 Subject: [PATCH 12/15] fix conflict --- app/api/user_check_in.py | 1 + 1 file changed, 1 insertion(+) diff --git a/app/api/user_check_in.py b/app/api/user_check_in.py index 9965976456..fb0c6ab090 100644 --- a/app/api/user_check_in.py +++ b/app/api/user_check_in.py @@ -101,6 +101,7 @@ def before_create_object(self, data, _view_kwargs): station = db.session.query(Station).filter_by(id=data.get('station')).one() except NoResultFound: raise ObjectNotFound({'parameter': data.get('station')}, "Station: not found") + current_time = datetime.datetime.utcnow() if not has_access('is_coorganizer', event_id=station.event_id): raise UnprocessableEntityError( {'parameter': 'station'}, From ab615b0ba47a9fc0591e4b37ed435ae7007c9383 Mon Sep 17 00:00:00 2001 From: nnhathung Date: Wed, 9 Aug 2023 00:01:11 +0700 Subject: [PATCH 13/15] validat mising request params --- app/api/custom/attendees.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/api/custom/attendees.py b/app/api/custom/attendees.py index 083e567a9d..3dca6d02f3 100644 --- a/app/api/custom/attendees.py +++ b/app/api/custom/attendees.py @@ -59,6 +59,14 @@ def check_attendee_state(): """ from app.models.event import Event + if not request.args.get('event_id', False): + raise NotFoundError( + {'parameter': 'event_id'}, "event_id is missing from your request" + ) + if not request.args.get('attendee_id', False): + raise NotFoundError( + {'parameter': 'attendee_id'}, "attendee_id is missing from your request" + ) event_id = request.args.get('event_id') attendee_id = request.args.get('attendee_id') if event_id is not None: From 364106c260e82f840917e0667b9feb7541215def Mon Sep 17 00:00:00 2001 From: nnhathung Date: Wed, 9 Aug 2023 09:16:20 +0700 Subject: [PATCH 14/15] re-run pipeline --- app/api/custom/attendees.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/api/custom/attendees.py b/app/api/custom/attendees.py index 3dca6d02f3..120fa8cf19 100644 --- a/app/api/custom/attendees.py +++ b/app/api/custom/attendees.py @@ -61,11 +61,11 @@ def check_attendee_state(): if not request.args.get('event_id', False): raise NotFoundError( - {'parameter': 'event_id'}, "event_id is missing from your request" + {'parameter': 'event_id'}, "event_id is missing from your request." ) if not request.args.get('attendee_id', False): raise NotFoundError( - {'parameter': 'attendee_id'}, "attendee_id is missing from your request" + {'parameter': 'attendee_id'}, "attendee_id is missing from your request." ) event_id = request.args.get('event_id') attendee_id = request.args.get('attendee_id') From 5df85155ed42613c169cdb601b5676dc57f8ef73 Mon Sep 17 00:00:00 2001 From: nnhathung Date: Wed, 9 Aug 2023 09:34:59 +0700 Subject: [PATCH 15/15] re-run pipeline --- app/api/custom/attendees.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/api/custom/attendees.py b/app/api/custom/attendees.py index 120fa8cf19..b9f1de1e25 100644 --- a/app/api/custom/attendees.py +++ b/app/api/custom/attendees.py @@ -76,15 +76,15 @@ def check_attendee_state(): try: event = safe_query_by_id(Event, event_id) except ObjectNotFound: - raise NotFoundError({'parameter': f'{event_id}'}, "Event not found") + raise NotFoundError({'parameter': f'{event_id}'}, "Event not found.") try: attendee = safe_query_by_id(TicketHolder, attendee_id) except ObjectNotFound: - raise NotFoundError({'parameter': f'{attendee_id}'}, "Attendee not found") + raise NotFoundError({'parameter': f'{attendee_id}'}, "Attendee not found.") if event.id != attendee.event_id: raise UnprocessableEntityError( {'parameter': 'Attendee'}, - "Attendee not belong to this event", + "Attendee not belong to this event.", ) return jsonify( {