diff --git a/.gitignore b/.gitignore index 102af2f..645aa71 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,14 @@ node_modules/ .pnp .pnp.* +# Python +venv/ +.venv/ +__pycache__/ +*.py[cod] +*.pyo +db.sqlite3 + # Environment variables .env .env.local diff --git a/basics/django/.env.example b/basics/django/.env.example new file mode 100644 index 0000000..366b061 --- /dev/null +++ b/basics/django/.env.example @@ -0,0 +1,4 @@ +POSTHOG_API_KEY= +POSTHOG_HOST=https://us.i.posthog.com +DJANGO_SECRET_KEY=your-secret-key-here +DEBUG=True diff --git a/basics/django/.gitignore b/basics/django/.gitignore new file mode 100644 index 0000000..7c4b178 --- /dev/null +++ b/basics/django/.gitignore @@ -0,0 +1,27 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# Django stuff +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Environment +.env +.venv +env/ +venv/ + +# IDE +.vscode/ +.idea/ + +# Static files +staticfiles/ + +# Coverage +.coverage +htmlcov/ diff --git a/basics/django/README.md b/basics/django/README.md new file mode 100644 index 0000000..1dac31a --- /dev/null +++ b/basics/django/README.md @@ -0,0 +1,199 @@ +# PostHog Django example + +This is a [Django](https://djangoproject.com) example demonstrating PostHog integration with product analytics, error tracking, feature flags, and user identification. + +## Features + +- **Product analytics**: Track user events and behaviors +- **Error tracking**: Capture and track exceptions automatically +- **User identification**: Associate events with authenticated users via context +- **Feature flags**: Control feature rollouts with PostHog feature flags +- **Server-side tracking**: All tracking happens server-side with the Python SDK +- **Context middleware**: Automatic session and user context extraction + +## Getting started + +### 1. Install dependencies + +```bash +pip install posthog +``` + +### 2. Configure environment variables + +Create a `.env` file in the root directory: + +```bash +POSTHOG_API_KEY=your_posthog_project_api_key +POSTHOG_HOST=https://us.i.posthog.com +``` + +Get your PostHog API key from your [PostHog project settings](https://app.posthog.com/project/settings). + +### 3. Run migrations + +```bash +python manage.py migrate +``` + +### 4. Run the development server + +```bash +python manage.py runserver +``` + +Open [http://localhost:8000](http://localhost:8000) with your browser to see the app. + +## Project structure + +``` +django/ +├── manage.py # Django management script +├── requirements.txt # Python dependencies +├── .env.example # Environment variable template +├── .gitignore +├── posthog_example/ +│ ├── __init__.py +│ ├── settings.py # Django settings with PostHog config +│ ├── urls.py # URL routing +│ ├── wsgi.py # WSGI application +│ └── asgi.py # ASGI application +└── core/ + ├── __init__.py + ├── apps.py # AppConfig with PostHog initialization + ├── views.py # Views with event tracking examples + ├── urls.py # App URL patterns + └── templates/ + └── core/ + ├── base.html # Base template + ├── home.html # Home/login page + ├── burrito.html # Burrito page with event tracking + ├── dashboard.html # Dashboard with feature flag example + └── profile.html # Profile page +``` + +## Key integration points + +### PostHog initialization (core/apps.py) + +```python +import posthog +from django.conf import settings + +class CoreConfig(AppConfig): + name = 'core' + + def ready(self): + posthog.api_key = settings.POSTHOG_API_KEY + posthog.host = settings.POSTHOG_HOST +``` + +### Django settings configuration (settings.py) + +```python +import os + +# PostHog configuration +POSTHOG_API_KEY = os.environ.get('POSTHOG_API_KEY', '') +POSTHOG_HOST = os.environ.get('POSTHOG_HOST', 'https://us.i.posthog.com') + +MIDDLEWARE = [ + # ... other middleware + 'posthog.integrations.django.PosthogContextMiddleware', +] +``` + +### Built-in context middleware + +The PostHog SDK includes a Django middleware that automatically wraps all requests with a context. It extracts session and user information from request headers and tags all events captured during the request. + +The middleware automatically extracts: + +- **Session ID** from the `X-POSTHOG-SESSION-ID` header +- **Distinct ID** from the `X-POSTHOG-DISTINCT-ID` header +- **Current URL** as `$current_url` +- **Request method** as `$request_method` + +### User identification (core/views.py) + +```python +import posthog + +def login_view(request): + # ... authentication logic + if user: + with posthog.new_context(): + posthog.identify_context(str(user.id)) + posthog.tag('email', user.email) + posthog.tag('username', user.username) + posthog.capture('user_logged_in', properties={ + 'login_method': 'email', + }) +``` + +### Event tracking (core/views.py) + +```python +import posthog + +def consider_burrito(request): + user_id = str(request.user.id) if request.user.is_authenticated else 'anonymous' + + with posthog.new_context(): + posthog.identify_context(user_id) + posthog.capture('burrito_considered', properties={ + 'total_considerations': request.session.get('burrito_count', 0), + }) +``` + +### Feature flags (core/views.py) + +```python +import posthog + +def dashboard_view(request): + user_id = str(request.user.id) if request.user.is_authenticated else 'anonymous' + + show_new_feature = posthog.feature_enabled( + 'new-dashboard-feature', + distinct_id=user_id + ) + + return render(request, 'core/dashboard.html', { + 'show_new_feature': show_new_feature + }) +``` + +### Error tracking (core/views.py) + +Capture exceptions manually using `capture_exception()`: + +```python +import posthog + +def profile_view(request): + try: + risky_operation() + except Exception as e: + posthog.capture_exception(e) +``` + +## Frontend integration (optional) + +If you're using PostHog's JavaScript SDK on the frontend, enable tracing headers to connect frontend sessions with backend events: + +```javascript +posthog.init('', { + api_host: 'https://us.i.posthog.com', + __add_tracing_headers: ['your-backend-domain.com'], +}) +``` + +This automatically adds `X-POSTHOG-SESSION-ID` and `X-POSTHOG-DISTINCT-ID` headers to requests, which the Django middleware extracts to maintain context. + +## Learn more + +- [PostHog Django integration](https://posthog.com/docs/libraries/django) +- [PostHog Python SDK](https://posthog.com/docs/libraries/python) +- [PostHog documentation](https://posthog.com/docs) +- [Django documentation](https://docs.djangoproject.com/) diff --git a/basics/django/core/__init__.py b/basics/django/core/__init__.py new file mode 100644 index 0000000..e471232 --- /dev/null +++ b/basics/django/core/__init__.py @@ -0,0 +1 @@ +# Core app for PostHog Django example diff --git a/basics/django/core/apps.py b/basics/django/core/apps.py new file mode 100644 index 0000000..db0c6aa --- /dev/null +++ b/basics/django/core/apps.py @@ -0,0 +1,37 @@ +""" +Django AppConfig that initializes PostHog when the application starts. + +This ensures the SDK is configured once when Django starts, making it available throughout the application. +""" + +from django.apps import AppConfig +from django.conf import settings + + +class CoreConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'core' + + def ready(self): + """ + Initialize PostHog when Django starts. + + This method is called once when Django starts. We configure the + PostHog SDK here so it's available everywhere in the application. + + Note: Import posthog inside this method to avoid import issues + during Django's startup sequence. + """ + import posthog + + # Configure PostHog with settings from Django settings + posthog.api_key = settings.POSTHOG_API_KEY + posthog.host = settings.POSTHOG_HOST + + # Disable PostHog if configured (useful for testing) + if settings.POSTHOG_DISABLED: + posthog.disabled = True + + # Optional: Enable debug mode in development + if settings.DEBUG: + posthog.debug = True diff --git a/basics/django/core/templates/core/base.html b/basics/django/core/templates/core/base.html new file mode 100644 index 0000000..2f8b667 --- /dev/null +++ b/basics/django/core/templates/core/base.html @@ -0,0 +1,138 @@ + + + + + + {% block title %}PostHog Django example{% endblock %} + + + + {% if user.is_authenticated %} + + {% endif %} + +
+ {% if messages %} +
+ {% for message in messages %} +
{{ message }}
+ {% endfor %} +
+ {% endif %} + + {% block content %}{% endblock %} +
+ + {% block scripts %}{% endblock %} + + diff --git a/basics/django/core/templates/core/burrito.html b/basics/django/core/templates/core/burrito.html new file mode 100644 index 0000000..ebcc79f --- /dev/null +++ b/basics/django/core/templates/core/burrito.html @@ -0,0 +1,54 @@ +{% extends 'core/base.html' %} + +{% block title %}Burrito - PostHog Django example{% endblock %} + +{% block content %} +
+

Burrito consideration tracker

+

This page demonstrates custom event tracking with PostHog.

+
+ +
+

Times considered

+
{{ burrito_count }}
+ +
+ +
+

How event tracking works

+

Each time you click the button, a burrito_considered event is sent to PostHog:

+
from posthog import new_context, identify_context, capture
+
+with new_context():
+    identify_context(user_id)
+    capture('burrito_considered', properties={
+        'total_considerations': count,
+    })
+
+{% endblock %} + +{% block scripts %} + +{% endblock %} diff --git a/basics/django/core/templates/core/dashboard.html b/basics/django/core/templates/core/dashboard.html new file mode 100644 index 0000000..ec59ed1 --- /dev/null +++ b/basics/django/core/templates/core/dashboard.html @@ -0,0 +1,54 @@ +{% extends 'core/base.html' %} + +{% block title %}Dashboard - PostHog Django example{% endblock %} + +{% block content %} +
+

Dashboard

+

Welcome back, {{ user.username }}!

+
+ +
+

Feature flags

+

Feature flags allow you to control feature rollouts and run A/B tests.

+ + {% if show_new_feature %} +
+

New feature enabled!

+

+ This section is only visible because the new-dashboard-feature + flag is enabled for your user. +

+ {% if feature_config %} +

Feature config: {{ feature_config }}

+ {% endif %} +
+ {% else %} +
+

+ The new-dashboard-feature flag is not enabled for your user. + Create this flag in your PostHog project to see it in action. +

+
+ {% endif %} +
+ +
+

How feature flags work

+
# Check if a feature flag is enabled
+show_feature = posthog.feature_enabled(
+    'new-dashboard-feature',
+    distinct_id=user_id,
+    person_properties={
+        'email': user.email,
+        'is_staff': user.is_staff,
+    }
+)
+
+# Get feature flag payload for configuration
+config = posthog.get_feature_flag_payload(
+    'new-dashboard-feature',
+    distinct_id=user_id,
+)
+
+{% endblock %} diff --git a/basics/django/core/templates/core/home.html b/basics/django/core/templates/core/home.html new file mode 100644 index 0000000..cbd3d39 --- /dev/null +++ b/basics/django/core/templates/core/home.html @@ -0,0 +1,37 @@ +{% extends 'core/base.html' %} + +{% block title %}Login - PostHog Django example{% endblock %} + +{% block content %} +
+

PostHog Django example

+

Welcome! This example demonstrates PostHog integration with Django.

+
+ +
+

Login

+

Login to see PostHog analytics in action.

+ +
+ {% csrf_token %} + + + +
+ +

+ Tip: Create a user with python manage.py createsuperuser +

+
+ +
+

What this example demonstrates

+
    +
  • User identification - Users are identified with identify_context() on login
  • +
  • Pageview tracking - Middleware extracts session and user context
  • +
  • Event tracking - Custom events captured with capture() in context
  • +
  • Feature flags - Conditional features with posthog.feature_enabled()
  • +
  • Error tracking - Exceptions captured with capture_exception()
  • +
+
+{% endblock %} diff --git a/basics/django/core/templates/core/profile.html b/basics/django/core/templates/core/profile.html new file mode 100644 index 0000000..1531e47 --- /dev/null +++ b/basics/django/core/templates/core/profile.html @@ -0,0 +1,97 @@ +{% extends 'core/base.html' %} + +{% block title %}Profile - PostHog Django example{% endblock %} + +{% block content %} +
+

Profile

+

This page demonstrates error tracking with PostHog.

+
+ +
+

User information

+ + + + + + + + + + + + + + + + + +
Username:{{ user.username }}
Email:{{ user.email|default:"Not set" }}
Date Joined:{{ user.date_joined }}
Staff Status:{{ user.is_staff|yesno:"Yes,No" }}
+
+ +
+

Error tracking demo

+

Click the buttons below to trigger different types of errors. These errors are caught and sent to PostHog.

+ +
+ + + +
+ + +
+ +
+

How error tracking works

+
import posthog
+
+try:
+    risky_operation()
+except Exception as e:
+    posthog.capture_exception(e)
+
+{% endblock %} + +{% block scripts %} + +{% endblock %} diff --git a/basics/django/core/urls.py b/basics/django/core/urls.py new file mode 100644 index 0000000..9813f72 --- /dev/null +++ b/basics/django/core/urls.py @@ -0,0 +1,30 @@ +""" +URL configuration for the core app. + +This module defines all the URL patterns for the PostHog example views. +""" + +from django.urls import path +from . import views + +urlpatterns = [ + # Home login page + path('', views.home_view, name='home'), + + # Authentication + path('logout/', views.logout_view, name='logout'), + + # Dashboard with feature flags + path('dashboard/', views.dashboard_view, name='dashboard'), + + # Burrito example for event tracking + path('burrito/', views.burrito_view, name='burrito'), + path('api/burrito/consider/', views.consider_burrito_view, name='consider_burrito'), + + # Profile with error tracking + path('profile/', views.profile_view, name='profile'), + path('api/trigger-error/', views.trigger_error_view, name='trigger_error'), + + # Group analytics example + path('api/group-analytics/', views.group_analytics_view, name='group_analytics'), +] diff --git a/basics/django/core/views.py b/basics/django/core/views.py new file mode 100644 index 0000000..6bf534d --- /dev/null +++ b/basics/django/core/views.py @@ -0,0 +1,219 @@ +"""Django views demonstrating PostHog integration patterns""" + +import posthog +from posthog import new_context, identify_context, tag, capture +from django.shortcuts import render, redirect +from django.contrib.auth import authenticate, login, logout +from django.contrib.auth.decorators import login_required +from django.contrib import messages +from django.http import JsonResponse +from django.views.decorators.http import require_POST + + +def home_view(request): + """Home page with login functionality""" + if request.user.is_authenticated: + return redirect('dashboard') + + if request.method == 'POST': + username = request.POST.get('username') + password = request.POST.get('password') + + user = authenticate(request, username=username, password=password) + + if user is not None: + login(request, user) + + # PostHog: Identify user and capture login event + with new_context(): + identify_context(str(user.id)) + + # Set person properties (PII goes in tag, not capture) + tag('email', user.email) + tag('username', user.username) + tag('name', user.get_full_name() or user.username) + tag('is_staff', user.is_staff) + tag('date_joined', user.date_joined.isoformat()) + + capture('user_logged_in', properties={ + 'login_method': 'email', + }) + + return redirect('dashboard') + else: + messages.error(request, 'Invalid username or password') + + return render(request, 'core/home.html') + + +def logout_view(request): + """Logout the current user""" + if request.user.is_authenticated: + user_id = str(request.user.id) + + # PostHog: Track logout before session ends + with new_context(): + identify_context(user_id) + capture('user_logged_out') + + logout(request) + + return redirect('home') + + +@login_required +def dashboard_view(request): + """Dashboard page with feature flag example""" + user_id = str(request.user.id) + + # PostHog: Track dashboard view + with new_context(): + identify_context(user_id) + capture('dashboard_viewed', properties={ + 'is_staff': request.user.is_staff, + }) + + # PostHog: Check feature flag + show_new_feature = posthog.feature_enabled( + 'new-dashboard-feature', + distinct_id=user_id, + person_properties={ + 'email': request.user.email, + 'is_staff': request.user.is_staff, + } + ) + + # PostHog: Get feature flag payload + feature_config = posthog.get_feature_flag_payload( + 'new-dashboard-feature', + distinct_id=user_id, + ) + + context = { + 'show_new_feature': show_new_feature, + 'feature_config': feature_config, + } + + return render(request, 'core/dashboard.html', context) + + +@login_required +def burrito_view(request): + """Example page demonstrating event tracking""" + count = request.session.get('burrito_count', 0) + + context = { + 'burrito_count': count, + } + + return render(request, 'core/burrito.html', context) + + +@login_required +@require_POST +def consider_burrito_view(request): + """API endpoint for tracking burrito considerations""" + count = request.session.get('burrito_count', 0) + 1 + request.session['burrito_count'] = count + + user_id = str(request.user.id) + + # PostHog: Track custom event + with new_context(): + identify_context(user_id) + capture('burrito_considered', properties={ + 'total_considerations': count, + }) + + return JsonResponse({ + 'success': True, + 'count': count, + }) + + +@login_required +def profile_view(request): + """Profile page with error tracking demonstration""" + user_id = str(request.user.id) + + # PostHog: Track profile view + with new_context(): + identify_context(user_id) + capture('profile_viewed') + + context = { + 'user': request.user, + } + + return render(request, 'core/profile.html', context) + + +@login_required +@require_POST +def trigger_error_view(request): + """API endpoint that demonstrates error tracking""" + try: + error_type = request.POST.get('error_type', 'generic') + + if error_type == 'value': + raise ValueError("Invalid value provided by user") + elif error_type == 'key': + data = {} + _ = data['nonexistent_key'] + else: + raise Exception("Something went wrong!") + + except Exception as e: + # PostHog: Capture exception + posthog.capture_exception(e) + + # PostHog: Track error trigger event + with new_context(): + identify_context(str(request.user.id)) + capture('error_triggered', properties={ + 'error_type': error_type, + 'error_message': str(e), + }) + + return JsonResponse({ + 'success': False, + 'error': str(e), + 'message': 'Error has been captured by PostHog', + }, status=400) + + return JsonResponse({'success': True}) + + +@login_required +def group_analytics_view(request): + """Example demonstrating group analytics""" + user_id = str(request.user.id) + + # PostHog: Identify group + posthog.group_identify( + group_type='company', + group_key='acme-corp', + properties={ + 'name': 'Acme Corporation', + 'plan': 'enterprise', + 'employee_count': 150, + } + ) + + # PostHog: Capture event with group + with new_context(): + identify_context(user_id) + capture( + 'feature_used', + properties={ + 'feature_name': 'group_analytics', + }, + groups={ + 'company': 'acme-corp', + } + ) + + return JsonResponse({ + 'success': True, + 'message': 'Group analytics event captured', + }) diff --git a/basics/django/manage.py b/basics/django/manage.py new file mode 100644 index 0000000..7fc4b11 --- /dev/null +++ b/basics/django/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'posthog_example.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main() diff --git a/basics/django/posthog_example/__init__.py b/basics/django/posthog_example/__init__.py new file mode 100644 index 0000000..7d233d7 --- /dev/null +++ b/basics/django/posthog_example/__init__.py @@ -0,0 +1 @@ +# PostHog Django example project diff --git a/basics/django/posthog_example/asgi.py b/basics/django/posthog_example/asgi.py new file mode 100644 index 0000000..5232a47 --- /dev/null +++ b/basics/django/posthog_example/asgi.py @@ -0,0 +1,11 @@ +""" +ASGI config for PostHog example project +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'posthog_example.settings') + +application = get_asgi_application() diff --git a/basics/django/posthog_example/settings.py b/basics/django/posthog_example/settings.py new file mode 100644 index 0000000..167c4b3 --- /dev/null +++ b/basics/django/posthog_example/settings.py @@ -0,0 +1,89 @@ +"""Django settings for PostHog example project""" + +import os +from pathlib import Path + +try: + from dotenv import load_dotenv + load_dotenv() +except ImportError: + pass + +BASE_DIR = Path(__file__).resolve().parent.parent + +SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY', 'django-insecure-example-key-change-in-production') + +DEBUG = os.environ.get('DEBUG', 'True').lower() == 'true' + +ALLOWED_HOSTS = ['localhost', '127.0.0.1'] + + +# PostHog configuration +POSTHOG_API_KEY = os.environ.get('POSTHOG_API_KEY', '') +POSTHOG_HOST = os.environ.get('POSTHOG_HOST', 'https://us.i.posthog.com') +POSTHOG_DISABLED = os.environ.get('POSTHOG_DISABLED', 'False').lower() == 'true' + + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'core.apps.CoreConfig', +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', + 'posthog.integrations.django.PosthogContextMiddleware', +] + +ROOT_URLCONF = 'posthog_example.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'posthog_example.wsgi.application' + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': BASE_DIR / 'db.sqlite3', + } +} + +AUTH_PASSWORD_VALIDATORS = [ + {'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator'}, + {'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator'}, + {'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator'}, + {'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator'}, +] + +LANGUAGE_CODE = 'en-us' +TIME_ZONE = 'UTC' +USE_I18N = True +USE_TZ = True + +STATIC_URL = 'static/' + +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' diff --git a/basics/django/posthog_example/urls.py b/basics/django/posthog_example/urls.py new file mode 100644 index 0000000..f9bc6b4 --- /dev/null +++ b/basics/django/posthog_example/urls.py @@ -0,0 +1,12 @@ +""" +URL configuration for PostHog example project +""" + +from django.contrib import admin +from django.urls import path, include + +urlpatterns = [ + path('admin/', admin.site.urls), + # Include the core app URLs for PostHog examples + path('', include('core.urls')), +] diff --git a/basics/django/posthog_example/wsgi.py b/basics/django/posthog_example/wsgi.py new file mode 100644 index 0000000..b20deb9 --- /dev/null +++ b/basics/django/posthog_example/wsgi.py @@ -0,0 +1,11 @@ +""" +WSGI config for PostHog example project +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'posthog_example.settings') + +application = get_wsgi_application() diff --git a/basics/django/requirements.txt b/basics/django/requirements.txt new file mode 100644 index 0000000..d89fcae --- /dev/null +++ b/basics/django/requirements.txt @@ -0,0 +1,3 @@ +Django>=4.2,<5.0 +posthog # Always use latest version +python-dotenv>=1.0.0 diff --git a/package-lock.json b/package-lock.json index 2833cd6..a110bd3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@posthog/examples", - "version": "1.0.0", + "version": "1.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@posthog/examples", - "version": "1.0.0", + "version": "1.2.0", "dependencies": { "gray-matter": "^4.0.3", "js-yaml": "^4.1.1" @@ -2221,6 +2221,7 @@ "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", diff --git a/package.json b/package.json index 5880cf8..8cc8b06 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@posthog/examples", - "version": "1.1.0", + "version": "1.2.0", "private": true, "description": "PostHog example projects", "scripts": { diff --git a/scripts/build-examples-mcp-resources.js b/scripts/build-examples-mcp-resources.js index 5c6298a..5f9fef8 100644 --- a/scripts/build-examples-mcp-resources.js +++ b/scripts/build-examples-mcp-resources.js @@ -101,6 +101,12 @@ const DOCS_CONFIG = { name: 'PostHog React Router v7 Declarative mode integration documentation', description: 'PostHog integration documentation for React Router v7 Declarative mode', url: 'https://posthog.com/docs/libraries/react-router/react-router-v7-declarative-mode' + }, + 'django': { + id: 'django', + name: 'PostHog Django integration documentation', + description: 'PostHog integration documentation for Django', + url: 'https://posthog.com/docs/libraries/django' } } }; @@ -180,6 +186,24 @@ const defaultConfig = { regex: [], }, plugins: [], + }, + { + path: 'basics/django', + id: 'django', + displayName: 'Django', + tags: ['django', 'python', 'server-side'], + skipPatterns: { + includes: [ + '__pycache__', + '.pyc', + 'db.sqlite3', + '.venv', + 'venv', + 'env', + ], + regex: [], + }, + plugins: [], } ], globalSkipPatterns: { diff --git a/transformation-config/commandments.yaml b/transformation-config/commandments.yaml index 2ad7fc0..4b81f48 100644 --- a/transformation-config/commandments.yaml +++ b/transformation-config/commandments.yaml @@ -10,5 +10,17 @@ commandments: javascript: - Remember that source code is available in the node_modules directory + - Check package.json for type checking or build scripts to validate changes + - posthog-js is the JavaScript SDK package name + python: + - Remember that source code is available in the venv/site-packages directory + - posthog is the Python SDK package name + + django: + - Add 'posthog.integrations.django.PosthogContextMiddleware' to MIDDLEWARE it auto-extracts tracing headers and captures exceptions + - Initialize PostHog in AppConfig.ready() with api_key and host from environment variables + - Use the context API pattern with new_context(), identify_context(user_id), then capture() + - For login/logout views, create a new context since user state changes during the request + - Do NOT create custom middleware, distinct_id helpers, or conditional checks - the SDK handles these \ No newline at end of file diff --git a/transformation-config/skill-description.md b/transformation-config/skill-description.md index 3dec7cc..0681458 100644 --- a/transformation-config/skill-description.md +++ b/transformation-config/skill-description.md @@ -26,7 +26,7 @@ The example project shows the target implementation pattern. Consult the documen ## Identifying users -Call `posthog.identify()` on the client side during login and signup events. Use form contents to identify users on submit. If server-side code exists, pass the client-side session and distinct ID using `X-POSTHOG-DISTINCT-ID` and `X-POSTHOG-SESSION-ID` headers to maintain correlation. +Identify users during login and signup events. Refer to the example code and documentation for the correct identify pattern for this framework. If both frontend and backend code exist, pass the client-side session and distinct ID using `X-POSTHOG-DISTINCT-ID` and `X-POSTHOG-SESSION-ID` headers to maintain correlation. ## Error tracking diff --git a/transformation-config/skills.yaml b/transformation-config/skills.yaml index f7df8c8..1f6efe6 100644 --- a/transformation-config/skills.yaml +++ b/transformation-config/skills.yaml @@ -61,6 +61,15 @@ skills: docs_urls: - https://posthog.com/docs/libraries/react-router/react-router-v7-declarative-mode.md + - id: django + type: example + example_path: basics/django + display_name: Django + description: PostHog integration for Django applications + tags: [django, python] + docs_urls: + - https://posthog.com/docs/libraries/django.md + # Guide-only skills (docs without example code) # - id: identify-users # type: guide diff --git a/transformation-config/skip-patterns.yaml b/transformation-config/skip-patterns.yaml index f010faf..c3ca766 100644 --- a/transformation-config/skip-patterns.yaml +++ b/transformation-config/skip-patterns.yaml @@ -58,6 +58,23 @@ global: - eslint - repomix-output.xml + # Python + - venv + - .venv + - __pycache__ + - .pyc + - .egg-info + - .eggs + - .pytest_cache + - .mypy_cache + - .ruff_cache + - .tox + - .nox + - htmlcov + - .coverage + - pip-log.txt + - .Python + # Regex patterns - skip if path matches # Note: Patterns are JavaScript regex syntax regex: