diff --git a/migrations/versions/2026_06_02_2138-10419d21e3d0_add_opt_in_fields_to_user.py b/migrations/versions/2026_06_02_2138-10419d21e3d0_add_opt_in_fields_to_user.py new file mode 100644 index 0000000..d07cdf1 --- /dev/null +++ b/migrations/versions/2026_06_02_2138-10419d21e3d0_add_opt_in_fields_to_user.py @@ -0,0 +1,38 @@ +"""add opt in fields to user + +Revision ID: 10419d21e3d0 +Revises: 92f9a97e9001 +Create Date: 2026-06-02 21:38:25.621650 + +""" + +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + +# revision identifiers, used by Alembic. +revision: str = "10419d21e3d0" +down_revision: Union[str, None] = "92f9a97e9001" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + """Upgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + op.add_column( + "user", sa.Column("share_my_data_to_sponsor", sa.Boolean(), nullable=True) + ) + op.add_column( + "user", sa.Column("retain_my_data_for_next_pycon", sa.Boolean(), nullable=True) + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + """Downgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column("user", "retain_my_data_for_next_pycon") + op.drop_column("user", "share_my_data_to_sponsor") + # ### end Alembic commands ### diff --git a/models/User.py b/models/User.py index 9979b50..f7a8ed6 100644 --- a/models/User.py +++ b/models/User.py @@ -90,6 +90,12 @@ class User(Base): share_my_public_social_media: Mapped[bool] = mapped_column( "share_my_public_social_media", Boolean, nullable=True ) + share_my_data_to_sponsor: Mapped[bool] = mapped_column( + "share_my_data_to_sponsor", Boolean, nullable=True + ) + retain_my_data_for_next_pycon: Mapped[bool] = mapped_column( + "retain_my_data_for_next_pycon", Boolean, nullable=True + ) terms_agreed: Mapped[bool] = mapped_column( "terms_agreed", Boolean, nullable=True, default=False ) diff --git a/repository/user.py b/repository/user.py index f741b12..dcf6a39 100644 --- a/repository/user.py +++ b/repository/user.py @@ -84,6 +84,8 @@ def create_user( linkedin_username: str = None, twitter_username: str = None, instagram_username: str = None, + share_my_data_to_sponsor: bool = False, + retain_my_data_for_next_pycon: bool = False, terms_agreed: bool = False, privacy_agreed: bool = False, coc_acknowledged: bool = False, @@ -131,6 +133,8 @@ def create_user( linkedin_username=linkedin_username, twitter_username=twitter_username, instagram_username=instagram_username, + share_my_data_to_sponsor=share_my_data_to_sponsor, + retain_my_data_for_next_pycon=retain_my_data_for_next_pycon, terms_agreed=terms_agreed, privacy_agreed=privacy_agreed, coc_acknowledged=coc_acknowledged, diff --git a/routes/tests/test_user_profile.py b/routes/tests/test_user_profile.py index 061b0f0..350f6bc 100644 --- a/routes/tests/test_user_profile.py +++ b/routes/tests/test_user_profile.py @@ -199,6 +199,8 @@ async def test_update_user_profile(self): "share_my_job_and_company": True, "share_my_interest": True, "share_my_public_social_media": True, + "share_my_data_to_sponsor": True, + "retain_my_data_for_next_pycon": True, }, ) @@ -211,6 +213,8 @@ async def test_update_user_profile(self): self.assertEqual(new_user.share_my_job_and_company, True) self.assertEqual(new_user.share_my_interest, True) self.assertEqual(new_user.share_my_public_social_media, True) + self.assertEqual(new_user.share_my_data_to_sponsor, True) + self.assertEqual(new_user.retain_my_data_for_next_pycon, True) # When 2 response = client.put( @@ -241,6 +245,8 @@ async def test_update_user_profile(self): "share_my_job_and_company": True, "share_my_interest": True, "share_my_public_social_media": True, + "share_my_data_to_sponsor": True, + "retain_my_data_for_next_pycon": True, }, ) @@ -320,6 +326,8 @@ async def test_get_user_profile(self): self.assertIn("share_my_location", response.json()) self.assertIn("share_my_interest", response.json()) self.assertIn("share_my_public_social_media", response.json()) + self.assertIn("share_my_data_to_sponsor", response.json()) + self.assertIn("retain_my_data_for_next_pycon", response.json()) async def test_update_profile_with_valid_location(self): """Test update user profile dengan location hierarchy yang valid""" diff --git a/routes/user_profile.py b/routes/user_profile.py index 039324c..482922c 100644 --- a/routes/user_profile.py +++ b/routes/user_profile.py @@ -142,6 +142,8 @@ async def update_user_profile( twitter_username: Optional[str] = Form(None), instagram_username: Optional[str] = Form(None), share_my_public_social_media: Optional[bool] = Form(None), + share_my_data_to_sponsor: Optional[bool] = Form(None), + retain_my_data_for_next_pycon: Optional[bool] = Form(None), coc_acknowledged: bool = Form(...), terms_agreed: bool = Form(...), privacy_agreed: bool = Form(...), @@ -196,6 +198,8 @@ async def update_user_profile( twitter_username=twitter_username, instagram_username=instagram_username, share_my_public_social_media=share_my_public_social_media, + share_my_data_to_sponsor=share_my_data_to_sponsor, + retain_my_data_for_next_pycon=retain_my_data_for_next_pycon, coc_acknowledged=coc_acknowledged, terms_agreed=terms_agreed, privacy_agreed=privacy_agreed, diff --git a/schemas/user_profile.py b/schemas/user_profile.py index 7fd42b0..c1096b1 100644 --- a/schemas/user_profile.py +++ b/schemas/user_profile.py @@ -181,6 +181,8 @@ class UserProfilePublic(UserProfileBase): share_my_location: Optional[bool] = False share_my_interest: Optional[bool] = False share_my_public_social_media: Optional[bool] = False + share_my_data_to_sponsor: Optional[bool] = False + retain_my_data_for_next_pycon: Optional[bool] = False coc_acknowledged: Optional[bool] = False terms_agreed: Optional[bool] = False privacy_agreed: Optional[bool] = False @@ -337,6 +339,12 @@ class UserProfileCreate(UserProfileUpdateBase): share_my_public_social_media: Optional[bool] = Field( None, description="Allow sharing social media profiles." ) + share_my_data_to_sponsor: Optional[bool] = Field( + None, description="Opt-in to share data with sponsors." + ) + retain_my_data_for_next_pycon: Optional[bool] = Field( + None, description="Opt-in to retain data for the next PyCon ID." + ) # Agreements coc_acknowledged: bool = Field(..., description="Code of Conduct acknowledgement.") @@ -389,6 +397,8 @@ class UserProfileEditSuccessResponse(UserProfileDB): "linkedin_username": "", "twitter_username": "string", "instagram_username": "string", + "share_my_data_to_sponsor": True, + "retain_my_data_for_next_pycon": True, "coc_acknowledged": True, "terms_agreed": True, "privacy_agreed": True,