def destroy(self, request, *args, **kwargs): """ Overridden to validate that user is not deleting themselves. """ user_to_delete = self.get_object() if user_to_delete == request.user: raise exceptions.ValidationError( {"detail": "Cannot delete yourself."}) # TODO: Redact PII or delete object and set to NULL dependencies that should be kept user_to_delete.team_set.clear() user_to_delete.is_active = False user_to_delete.save() posthoganalytics.capture( user_to_delete.distinct_id, "this user deleted") # Sent for the user that was deleted posthoganalytics.capture( request.user.distinct_id, "team member deleted", { "deleted_team_member": user_to_delete.distinct_id, }, ) # Sent for the deleter return response.Response(status=status.HTTP_204_NO_CONTENT)
def report_user_action(user: User, event: str, properties: Dict = {}): posthoganalytics.capture( user.distinct_id, event, properties=properties, groups=groups(user.current_organization, user.current_team), )
def create(self, validated_data, **kwargs): is_first_user: bool = not User.objects.exists() realm: str = "cloud" if getattr(settings, "MULTI_TENANCY", False) else "hosted" company_name = validated_data.pop("company_name", validated_data["first_name"]) self._organization, self._team, self._user = User.objects.bootstrap( company_name=company_name, **validated_data) user = self._user login( self.context["request"], user, backend="django.contrib.auth.backends.ModelBackend", ) posthoganalytics.capture( user.distinct_id, "user signed up", properties={ "is_first_user": is_first_user, "is_organization_first_user": True }, ) posthoganalytics.identify( user.distinct_id, properties={ "email": user.email, "realm": realm, "ee_available": settings.EE_AVAILABLE }, ) return user
def store_names_and_properties(team: Team, event: str, properties: Dict) -> None: # In _capture we only prefetch a couple of fields in Team to avoid fetching too much data save = False if not team.ingested_event: # First event for the team captured for user in Team.objects.get(pk=team.pk).users.all(): posthoganalytics.capture(user.distinct_id, "first team event ingested", {"team": str(team.uuid)}) team.ingested_event = True save = True if event not in team.event_names: save = True team.event_names.append(event) for key, value in properties.items(): if key not in team.event_properties: team.event_properties.append(key) save = True if isinstance(value, Number) and key not in team.event_properties_numerical: team.event_properties_numerical.append(key) save = True if save: team.save()
def ready(self): posthoganalytics.api_key = "sTMFPsFhdP1Ssg" posthoganalytics.personal_api_key = os.environ.get( "POSTHOG_PERSONAL_API_KEY") # Skip plugin sync in manage.py scripts and in tests # (the database tables might not yet be created) if (not settings.TEST and not "makemigrations" in sys.argv and not "migrate" in sys.argv and not "manage.py" in " ".join(sys.argv) and not "/mypy" in sys.argv[0]): from posthog.plugins import sync_plugin_config # syncs posthog.json['plugins'] and the Plugin/PluginConfig models sync_plugin_config() if settings.DEBUG: # log development server launch to posthog if os.getenv("RUN_MAIN") == "true": posthoganalytics.capture( get_machine_id(), "development server launched", { "posthog_version": VERSION, "git_rev": get_git_commit(), "git_branch": get_git_branch(), }, ) posthoganalytics.disabled = True elif settings.TEST or os.environ.get("OPT_OUT_CAPTURE"): posthoganalytics.disabled = True
def setup_admin(request): if User.objects.exists(): return redirect("/login") if request.method == "GET": if request.user.is_authenticated: return redirect("/") return render_template("setup_admin.html", request) if request.method == "POST": email = request.POST["email"] password = request.POST["password"] company_name = request.POST.get("company_name") email_opt_in = request.POST.get("emailOptIn") == "on" is_first_user = not User.objects.exists() user = User.objects.create_user( email=email, password=password, first_name=request.POST.get("name"), email_opt_in=email_opt_in, ) Team.objects.create_with_data(users=[user], name=company_name) login(request, user, backend="django.contrib.auth.backends.ModelBackend") posthoganalytics.capture( user.distinct_id, "user signed up", properties={"is_first_user": is_first_user}, ) posthoganalytics.identify( user.distinct_id, properties={ "email": user.email, "company_name": company_name, "name": user.first_name, }, ) return redirect("/")
def signup_to_team_view(request, token): if request.user.is_authenticated: return redirect('/') if not token: return redirect('/') if not User.objects.exists(): return redirect('/setup_admin') try: team = Team.objects.get(signup_token=token) except Team.DoesNotExist: return redirect('/') if request.method == 'POST': email = request.POST['email'] password = request.POST['password'] first_name=request.POST.get('name') email_opt_in=request.POST.get('emailOptIn') if email_opt_in == 'on': email_opt_in = True try: user = User.objects.create_user(email=email, password=password, first_name=first_name, email_opt_in=email_opt_in) except: return render_template('signup_to_team.html', request=request, context={'email': email, 'error': True, 'team': team, 'signup_token': token}) login(request, user, backend='django.contrib.auth.backends.ModelBackend') team.users.add(user) team.save() posthoganalytics.capture(user.distinct_id, 'user signed up', properties={'is_first_user': False}) posthoganalytics.identify(user.distinct_id, {'email_opt_in': user.email_opt_in}) return redirect('/') return render_template('signup_to_team.html', request, context={'team': team, 'signup_token': token})
def setup_admin(request): if User.objects.exists(): return redirect('/login') if request.method == 'GET': if request.user.is_authenticated: return redirect('/') return render_template('setup_admin.html', request) if request.method == 'POST': email = request.POST['email'] password = request.POST['password'] company_name = request.POST.get('company_name') is_first_user = not User.objects.exists() try: user = User.objects.create_user(email=email, password=password, first_name=request.POST.get('name')) except: return render_template('setup_admin.html', request=request, context={'error': True, 'email': request.POST['email'], 'company_name': request.POST.get('company_name'), 'name': request.POST.get('name')}) Team.objects.create_with_data(users=[user], name=company_name) login(request, user, backend='django.contrib.auth.backends.ModelBackend') posthoganalytics.capture(user.distinct_id, 'user signed up', properties={'is_first_user': is_first_user}) posthoganalytics.identify(user.distinct_id, properties={ 'email': user.email, 'company_name': company_name, 'name': user.first_name }) return redirect('/')
def social_create_user(strategy: DjangoStrategy, details, backend, user=None, *args, **kwargs): if user: return {"is_new": False} user_email = details["email"][0] if isinstance(details["email"], (list, tuple)) else details["email"] user_name = details["fullname"] strategy.session_set("user_name", user_name) strategy.session_set("backend", backend.name) from_invite = False invite_id = strategy.session_get("invite_id") if not invite_id: company_name = strategy.session_get("company_name", None) email_opt_in = strategy.session_get("email_opt_in", None) if not company_name or email_opt_in is None: return redirect(finish_social_signup) _, _, user = User.objects.bootstrap( company_name=company_name, first_name=user_name, email=user_email, email_opt_in=email_opt_in, password=None ) else: from_invite = True try: invite: Union[OrganizationInvite, TeamInviteSurrogate] = OrganizationInvite.objects.select_related( "organization" ).get(id=invite_id) except (OrganizationInvite.DoesNotExist, ValidationError): try: invite = TeamInviteSurrogate(invite_id) except Team.DoesNotExist: processed = render_to_string("auth_error.html", {"message": "Invalid invite link!"},) return HttpResponse(processed, status=401) try: invite.validate(user=None, email=user_email) except ValueError as e: processed = render_to_string("auth_error.html", {"message": str(e)},) return HttpResponse(processed, status=401) try: user = strategy.create_user(email=user_email, first_name=user_name, password=None) except Exception as e: capture_exception(e) processed = render_to_string( "auth_error.html", { "message": "Account unable to be created. This account may already exist. Please try again or use different credentials!" }, ) return HttpResponse(processed, status=401) invite.use(user, prevalidated=True) posthoganalytics.capture( user.distinct_id, "user signed up", properties={ "is_first_user": User.objects.count() == 1, "is_first_team_user": not from_invite, "login_provider": backend.name, }, ) return {"is_new": True, "user": user}
def social_create_user(strategy, details, backend, user=None, *args, **kwargs): if user: return {'is_new': False} signup_token = strategy.session_get('signup_token') if signup_token is None: processed = render_to_string('auth_error.html', {'message': "There is no team associated with this account! Please use an invite link from a team to create an account!"}) return HttpResponse(processed, status=401) fields = dict((name, kwargs.get(name, details.get(name))) for name in backend.setting('USER_FIELDS', ['email'])) if not fields: return try: team = Team.objects.get(signup_token=signup_token) except Team.DoesNotExist: processed = render_to_string('auth_error.html', {'message': "We can't find the team associated with this signup token. Please ensure the invite link is provided from an existing team!"}) return HttpResponse(processed, status=401) try: user = strategy.create_user(**fields) except: processed = render_to_string('auth_error.html', {'message': "Account unable to be created. This account may already exist. Please try again or use different credentials!"}) return HttpResponse(processed, status=401) team.users.add(user) team.save() posthoganalytics.capture(user.distinct_id, 'user signed up', properties={'is_first_user': False}) return { 'is_new': True, 'user': user }
def _capture( ip: str, site_url: str, team_id: int, event: str, distinct_id: str, properties: Dict, timestamp: Union[datetime.datetime, str], ) -> None: elements = properties.get("$elements") elements_list = None if elements: del properties["$elements"] elements_list = [ Element( text=el["$el_text"][0:400] if el.get("$el_text") else None, tag_name=el["tag_name"], href=el["attr__href"][0:2048] if el.get("attr__href") else None, attr_class=el["attr__class"].split(" ") if el.get("attr__class") else None, attr_id=el.get("attr__id"), nth_child=el.get("nth_child"), nth_of_type=el.get("nth_of_type"), attributes={key: value for key, value in el.items() if key.startswith("attr__")}, order=index, ) for index, el in enumerate(elements) ] team = Team.objects.only( "slack_incoming_webhook", "event_names", "event_properties", "anonymize_ips", "ingested_event", ).get(pk=team_id) if not team.ingested_event: # First event for the team captured for user in Team.objects.get(pk=team_id).users.all(): posthoganalytics.capture(user.distinct_id, "first team event ingested", {"team": str(team.uuid)}) team.ingested_event = True team.save() if not team.anonymize_ips: properties["$ip"] = ip Event.objects.create( event=event, distinct_id=distinct_id, properties=properties, team=team, site_url=site_url, **({"timestamp": timestamp} if timestamp else {}), **({"elements": elements_list} if elements_list else {}) ) store_names_and_properties(team=team, event=event, properties=properties) if not Person.objects.distinct_ids_exist(team_id=team_id, distinct_ids=[str(distinct_id)]): # Catch race condition where in between getting and creating, # another request already created this user try: Person.objects.create(team_id=team_id, distinct_ids=[str(distinct_id)]) except IntegrityError: pass
def create(self, validated_data): company_name = validated_data.pop("company_name", "") is_first_user: bool = not User.objects.exists() realm: str = "cloud" if not MULTI_TENANCY_MISSING else "hosted" if self.context["request"].user.is_authenticated: raise serializers.ValidationError("Authenticated users may not create additional teams.") if not is_first_user and MULTI_TENANCY_MISSING: raise serializers.ValidationError("This instance does not support multiple teams.") with transaction.atomic(): user = User.objects.create_user(**validated_data) self._team = Team.objects.create_with_data(users=[user], name=company_name) login( self.context["request"], user, backend="django.contrib.auth.backends.ModelBackend", ) posthoganalytics.capture( user.distinct_id, "user signed up", properties={"is_first_user": is_first_user, "is_team_first_user": True}, ) posthoganalytics.identify( user.distinct_id, properties={"email": user.email, "realm": realm, "ee_available": settings.EE_AVAILABLE}, ) return user
def report_user_signed_up( distinct_id: str, is_instance_first_user: bool, is_organization_first_user: bool, new_onboarding_enabled: bool = False, backend_processor: str = "", # which serializer/view processed the request social_provider: str = "", # which third-party provider processed the login (empty = no third-party) ) -> None: """ Reports that a new user has joined. Only triggered when a new user is actually created (i.e. when an existing user joins a new organization, this event is **not** triggered; see `report_user_joined_organization`). """ props = { "is_first_user": is_instance_first_user, "is_organization_first_user": is_organization_first_user, "new_onboarding_enabled": new_onboarding_enabled, "signup_backend_processor": backend_processor, "signup_social_provider": social_provider, "realm": get_instance_realm(), } # TODO: This should be $set_once as user props. posthoganalytics.identify(distinct_id, props) posthoganalytics.capture(distinct_id, "user signed up", properties=props)
def capture_event(name: str, report: Dict[str, Any], dry_run: bool) -> None: if not dry_run: posthoganalytics.api_key = "sTMFPsFhdP1Ssg" disabled = posthoganalytics.disabled posthoganalytics.disabled = False posthoganalytics.capture(get_machine_id(), "instance status report", report) posthoganalytics.disabled = disabled
def signup_to_team_view(request, token): if request.user.is_authenticated: return redirect('/') if not token: return redirect('/') if not User.objects.exists(): return redirect('/setup_admin') try: team = Team.objects.get(signup_token=token) except Team.DoesNotExist: return redirect('/') if request.method == 'POST': email = request.POST['email'] password = request.POST['password'] try: user = User.objects.create_user(email=email, password=password, first_name=request.POST.get('name')) except: return render_template('signup_to_team.html', request=request, context={'email': email, 'error': True, 'team': team}) login(request, user) team.users.add(user) team.save() posthoganalytics.capture(user.distinct_id, 'user signed up', properties={'is_first_user': False}) return redirect('/') return render_template('signup_to_team.html', request, context={'team': team})
def create(self, validated_data: Dict, *args: Any, **kwargs: Any) -> Dashboard: request = self.context["request"] validated_data["created_by"] = request.user team = Team.objects.get(id=self.context["team_id"]) use_template: str = validated_data.pop("use_template", None) dashboard = Dashboard.objects.create(team=team, **validated_data) if use_template: try: create_dashboard_from_template(use_template, dashboard) except AttributeError: raise serializers.ValidationError({"use_template": "Invalid value provided."}) elif request.data.get("items"): for item in request.data["items"]: DashboardItem.objects.create( **{key: value for key, value in item.items() if key not in ("id", "deleted", "dashboard", "team")}, dashboard=dashboard, team=team, ) posthoganalytics.capture( request.user.distinct_id, "dashboard created", {**dashboard.get_analytics_metadata(), "from_template": bool(use_template), "template_key": use_template}, ) return dashboard
def login_view(request): if request.user.is_authenticated: return redirect("/") if not User.objects.exists(): return redirect("/preflight") if request.method == "POST": email = request.POST["email"] password = request.POST["password"] user = cast(Optional[User], authenticate(request, email=email, password=password)) if user is not None: login(request, user, backend="django.contrib.auth.backends.ModelBackend") if user.distinct_id: posthoganalytics.capture(user.distinct_id, "user logged in") return redirect("/") else: return render_template("login.html", request=request, context={ "email": email, "error": True }) return render_template("login.html", request)
def update(self, cohort: Cohort, validated_data: Dict, *args: Any, **kwargs: Any) -> Cohort: # type: ignore request = self.context["request"] cohort.name = validated_data.get("name", cohort.name) cohort.groups = validated_data.get("groups", cohort.groups) deleted_state = validated_data.get("deleted", None) is_deletion_change = deleted_state is not None if is_deletion_change: cohort.deleted = deleted_state if not cohort.is_static and not is_deletion_change: cohort.is_calculating = True cohort.save() if not deleted_state: if cohort.is_static: self._handle_static(cohort, request) else: calculate_cohort.delay(cohort_id=cohort.pk) calculate_cohort_ch.delay(cohort_id=cohort.pk) posthoganalytics.capture( request.user.distinct_id, "cohort updated", { **cohort.get_analytics_metadata(), "updated_by_creator": request.user == cohort.created_by }, ) return cohort
def login_view(request): if request.user.is_authenticated: return redirect('/') if not User.objects.exists(): return redirect('/setup_admin') if request.method == 'POST': email = request.POST['email'] password = request.POST['password'] user = cast(Optional[User], authenticate(request, email=email, password=password)) if user is not None: login(request, user, backend='django.contrib.auth.backends.ModelBackend') if user.distinct_id: posthoganalytics.capture(user.distinct_id, 'user logged in') return redirect('/') else: return render_template('login.html', request=request, context={ 'email': email, 'error': True }) return render_template('login.html', request)
def feature_flag_created(sender, instance, created, raw, using, **kwargs): if instance.created_by: event_name: str = "feature flag created" if created else "feature flag updated" posthoganalytics.capture( instance.created_by.distinct_id, event_name, instance.get_analytics_metadata(), )
def create(self, validated_data, **kwargs): is_instance_first_user: bool = not User.objects.exists() company_name = validated_data.pop("company_name", validated_data["first_name"]) self._organization, self._team, self._user = User.objects.bootstrap( company_name=company_name, **validated_data) user = self._user login( self.context["request"], user, backend="django.contrib.auth.backends.ModelBackend", ) posthoganalytics.identify( user.distinct_id, { "is_first_user": is_instance_first_user, "is_organization_first_user": True }, ) posthoganalytics.capture( user.distinct_id, "user signed up", properties={ "is_first_user": is_instance_first_user, "is_organization_first_user": True }, ) return user
def setup_admin(request): if User.objects.exists(): return redirect("/login") if request.method == "GET": if request.user.is_authenticated: return redirect("/") try: return render_template("setup_admin.html", request) except TemplateDoesNotExist: return HttpResponse( "Frontend not built yet. Please try again shortly or build manually using <code>./bin/start-frontend</code>" ) if request.method == "POST": email = request.POST["email"] password = request.POST["password"] company_name = request.POST.get("company_name") name = request.POST.get("name") email_opt_in = request.POST.get("emailOptIn") == "on" is_first_user = not User.objects.exists() valid_inputs = (is_input_valid("name", name) and is_input_valid("email", email) and is_input_valid("password", password) and is_input_valid("company", company_name)) if not valid_inputs: return render_template( "setup_admin.html", request=request, context={ "email": email, "name": name, "invalid_input": True, "company": company_name }, ) user = User.objects.create_user( email=email, password=password, first_name=name, email_opt_in=email_opt_in, ) team = Team.objects.create_with_data(users=[user], name=company_name) login(request, user, backend="django.contrib.auth.backends.ModelBackend") posthoganalytics.capture( user.distinct_id, "user signed up", properties={"is_first_user": is_first_user}, ) posthoganalytics.identify( user.distinct_id, properties={ "email": user.email, "company_name": company_name, "team_id": team.pk, # TO-DO: handle multiple teams "is_team_first_user": True, }, ) return redirect("/")
def report_user_logged_in( distinct_id: str, social_provider: str = "", # which third-party provider processed the login (empty = no third-party) ) -> None: """ Reports that a user has logged in to PostHog. """ posthoganalytics.capture(distinct_id, "user logged in", properties={"social_provider": social_provider})
def report_user_password_reset(user: User) -> None: """ Reports a user resetting their password. """ posthoganalytics.capture(user.distinct_id, "user password reset", groups=groups(user.current_organization, user.current_team))
def report_org_usage(distinct_id: str, properties: Dict[str, Any]) -> None: """ Triggered daily by Celery scheduler. """ posthoganalytics.capture( distinct_id, "organization usage report", properties, )
def signup_to_team_view(request, token): if request.user.is_authenticated: return redirect("/") if not token: return redirect("/") if not User.objects.exists(): return redirect("/setup_admin") try: team = Team.objects.get(signup_token=token) except Team.DoesNotExist: return redirect("/") if request.method == "POST": email = request.POST["email"] password = request.POST["password"] first_name = request.POST.get("name") email_opt_in = request.POST.get("emailOptIn") == "on" valid_inputs = (is_input_valid("name", first_name) and is_input_valid("email", email) and is_input_valid("password", password)) email_exists = User.objects.filter(email=email).exists() if email_exists or not valid_inputs: return render_template( "signup_to_team.html", request=request, context={ "email": email, "name": first_name, "error": email_exists, "invalid_input": not valid_inputs, "team": team, "signup_token": token, }, ) user = User.objects.create_user( email=email, password=password, first_name=first_name, email_opt_in=email_opt_in, ) login(request, user, backend="django.contrib.auth.backends.ModelBackend") team.users.add(user) team.save() posthoganalytics.capture(user.distinct_id, "user signed up", properties={"is_first_user": False}) posthoganalytics.identify(user.distinct_id, {"email_opt_in": user.email_opt_in}) return redirect("/") return render_template("signup_to_team.html", request, context={ "team": team, "signup_token": token })
def update(self, instance: FeatureFlagOverride, validated_data: Dict) -> FeatureFlagOverride: self._ensure_team_and_feature_flag_match(validated_data) request = self.context["request"] instance = super().update(instance, validated_data) posthoganalytics.capture(request.user.distinct_id, self._analytics_updated_event_name, instance.get_analytics_metadata()) return instance
def report_user_updated(user: User, updated_attrs: List[str]) -> None: """ Reports a user has been updated. This includes current_team, current_organization & password. """ updated_attrs.sort() posthoganalytics.capture( user.distinct_id, "user updated", properties={"updated_attrs": updated_attrs}, )
def ready(self): posthoganalytics.api_key = "sTMFPsFhdP1Ssg" if settings.DEBUG: # log development server launch to posthog if os.getenv("RUN_MAIN") == "true": posthoganalytics.capture(get_machine_id(), "development server launched") posthoganalytics.disabled = True elif settings.TEST or os.environ.get("OPT_OUT_CAPTURE"): posthoganalytics.disabled = True
def create(self, validated_data: Dict, *args: Any, **kwargs: Any) -> SessionsFilter: request = self.context["request"] instance = SessionsFilter.objects.create( team=request.user.team, created_by=request.user, **validated_data, ) posthoganalytics.capture(instance.created_by.distinct_id, "sessions filter created") return instance