def get_teams(self, instance: Organization) -> List[Dict[str, Any]]: teams = cast( List[Dict[str, Any]], TeamBasicSerializer(instance.teams.all(), context=self.context, many=True).data) visible_teams = [ team for team in teams if team["effective_membership_level"] is not None ] return visible_teams
class UserSerializer(serializers.ModelSerializer): has_password = serializers.SerializerMethodField() is_impersonated = serializers.SerializerMethodField() team = TeamBasicSerializer(read_only=True) organization = OrganizationSerializer(read_only=True) organizations = OrganizationBasicSerializer(many=True, read_only=True) set_current_organization = serializers.CharField(write_only=True, required=False) set_current_team = serializers.CharField(write_only=True, required=False) current_password = serializers.CharField(write_only=True, required=False) class Meta: model = User fields = [ "date_joined", "uuid", "distinct_id", "first_name", "email", "email_opt_in", "anonymize_data", "toolbar_mode", "has_password", "is_staff", "is_impersonated", "team", "organization", "organizations", "set_current_organization", "set_current_team", "password", "current_password", # used when changing current password "events_column_config", ] extra_kwargs = { "date_joined": { "read_only": True }, "is_staff": { "read_only": True }, "password": { "write_only": True }, } def get_has_password(self, instance: User) -> bool: return instance.has_usable_password() def get_is_impersonated(self, _) -> Optional[bool]: if "request" not in self.context: return None return is_impersonated_session(self.context["request"]) def validate_set_current_organization(self, value: str) -> Organization: try: organization = Organization.objects.get(id=value) if organization.memberships.filter( user=self.context["request"].user).exists(): return organization except Organization.DoesNotExist: pass raise serializers.ValidationError( f"Object with id={value} does not exist.", code="does_not_exist") def validate_set_current_team(self, value: str) -> Team: try: team = Team.objects.get(pk=value) if self.context["request"].user.teams.filter(pk=team.pk).exists(): return team except Team.DoesNotExist: pass raise serializers.ValidationError( f"Object with id={value} does not exist.", code="does_not_exist") def validate_password_change(self, instance: User, current_password: Optional[str], password: Optional[str]) -> Optional[str]: if password: if instance.password and instance.has_usable_password(): # If user has a password set, we check it's provided to allow updating it. We need to check that is both # usable (properly hashed) and that a password actually exists. if not current_password: raise serializers.ValidationError( { "current_password": [ "This field is required when updating your password." ] }, code="required") if not instance.check_password(current_password): raise serializers.ValidationError( { "current_password": ["Your current password is incorrect."] }, code="incorrect_password") try: validate_password(password, instance) except ValidationError as e: raise serializers.ValidationError({"password": e.messages}) return password def update(self, instance: models.Model, validated_data: Any) -> Any: # Update current_organization and current_team current_organization = validated_data.pop("set_current_organization", None) current_team = validated_data.pop("set_current_team", None) if current_organization: if current_team and not current_organization.teams.filter( pk=current_team.pk).exists(): raise serializers.ValidationError({ "set_current_team": [ "Team must belong to the same organization in set_current_organization." ] }) validated_data["current_organization"] = current_organization validated_data[ "current_team"] = current_team if current_team else current_organization.teams.first( ) elif current_team: validated_data["current_team"] = current_team validated_data["current_organization"] = current_team.organization # Update password current_password = validated_data.pop("current_password", None) password = self.validate_password_change( cast(User, instance), current_password, validated_data.pop("password", None)) updated_attrs = list(validated_data.keys()) instance = cast(User, super().update(instance, validated_data)) if password: instance.set_password(password) instance.save() update_session_auth_hash(self.context["request"], instance) updated_attrs.append("password") report_user_updated(instance, updated_attrs) return instance def to_representation(self, instance: Any) -> Any: user_identify.identify_task.delay(user_id=instance.id) return super().to_representation(instance)
class OrganizationSerializer(serializers.ModelSerializer): membership_level = serializers.SerializerMethodField() setup = ( serializers.SerializerMethodField() ) # Information related to the current state of the onboarding/setup process teams = TeamBasicSerializer(many=True, read_only=True) class Meta: model = Organization fields = [ "id", "name", "created_at", "updated_at", "membership_level", "personalization", "setup", "setup_section_2_completed", "plugins_access_level", "teams", "available_features", ] read_only_fields = [ "id", "created_at", "updated_at", ] extra_kwargs = { "setup_section_2_completed": { "write_only": True } } # `setup` is used for reading this attribute def create(self, validated_data: Dict, *args: Any, **kwargs: Any) -> Organization: serializers.raise_errors_on_nested_writes("create", self, validated_data) organization, _, _ = Organization.objects.bootstrap( self.context["request"].user, **validated_data) return organization def get_membership_level( self, organization: Organization ) -> Optional[OrganizationMembership.Level]: membership = OrganizationMembership.objects.filter( organization=organization, user=self.context["request"].user, ).first() return membership.level if membership is not None else None def get_setup( self, instance: Organization) -> Dict[str, Union[bool, int, str, None]]: if not instance.is_onboarding_active: # As Section 2 is the last one of the setup process (as of today), # if it's completed it means the setup process is done return {"is_active": False, "current_section": None} non_demo_team_id = next( (team.pk for team in instance.teams.filter(is_demo=False)), None) any_project_ingested_events = instance.teams.filter( is_demo=False, ingested_event=True).exists() any_project_completed_snippet_onboarding = instance.teams.filter( is_demo=False, completed_snippet_onboarding=True, ).exists() current_section = 1 if non_demo_team_id and any_project_ingested_events and any_project_completed_snippet_onboarding: # All steps from section 1 completed, move on to section 2 current_section = 2 return { "is_active": True, "current_section": current_section, "any_project_ingested_events": any_project_ingested_events, "any_project_completed_snippet_onboarding": any_project_completed_snippet_onboarding, "non_demo_team_id": non_demo_team_id, "has_invited_team_members": instance.invites.exists() or instance.members.count() > 1, }