Skip to content

Commit 325a3a5

Browse files
committed
feat: more django context
1 parent 5d58a53 commit 325a3a5

File tree

2 files changed

+97
-9
lines changed

2 files changed

+97
-9
lines changed

posthog/exception_integrations/django.py

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ class DjangoIntegration:
3232
identifier = "django"
3333

3434
def __init__(self, capture_exception_fn=None):
35-
if DJANGO_VERSION < (4, 2):
36-
raise IntegrationEnablingError("Django 4.2 or newer is required.")
35+
if DJANGO_VERSION < (1, 8):
36+
raise IntegrationEnablingError("Django 1.8 or newer is required.")
3737

3838
# TODO: Right now this seems too complicated / overkill for us, but seems like we can automatically plug in middlewares
3939
# which is great for users (they don't need to do this) and everything should just work.
@@ -58,6 +58,17 @@ def uninstall(self):
5858
pass
5959

6060

61+
if DJANGO_VERSION < (1, 10):
62+
63+
def is_authenticated(request_user):
64+
return request_user.is_authenticated()
65+
66+
else:
67+
68+
def is_authenticated(request_user):
69+
return request_user.is_authenticated
70+
71+
6172
class DjangoRequestExtractor:
6273
def __init__(self, request):
6374
# type: (Any) -> None
@@ -67,8 +78,8 @@ def extract_person_data(self):
6778
headers = self.headers()
6879

6980
# Extract traceparent and tracestate headers
70-
traceparent = headers.get("traceparent")
71-
tracestate = headers.get("tracestate")
81+
traceparent = headers.get("Traceparent")
82+
tracestate = headers.get("Tracestate")
7283

7384
# Extract the distinct_id from tracestate
7485
distinct_id = None
@@ -80,12 +91,38 @@ def extract_person_data(self):
8091
distinct_id = match.group(1)
8192

8293
return {
94+
**self.user(),
8395
"distinct_id": distinct_id,
8496
"ip": headers.get("X-Forwarded-For"),
8597
"user_agent": headers.get("User-Agent"),
8698
"traceparent": traceparent,
99+
"$request.path": self.request.path,
87100
}
88101

102+
def user(self):
103+
user_data: dict[str, str] = {}
104+
105+
user = getattr(self.request, "user", None)
106+
107+
if user is None or not is_authenticated(user):
108+
return user_data
109+
110+
try:
111+
user_id = str(user.pk)
112+
if user_id:
113+
user_data.setdefault("$user.id", user_id)
114+
except Exception:
115+
pass
116+
117+
try:
118+
email = str(user.email)
119+
if email:
120+
user_data.setdefault("$user.email", email)
121+
except Exception:
122+
pass
123+
124+
return user_data
125+
89126
def headers(self):
90127
# type: () -> Dict[str, str]
91128
return dict(self.request.headers)

posthog/test/exception_integrations/test_django.py

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,44 @@
11
from posthog.exception_integrations.django import DjangoRequestExtractor
2+
from django.test import RequestFactory
3+
from django.conf import settings
4+
from django.core.management import call_command
5+
import django
26

37
DEFAULT_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3"
48

9+
# setup a test app
10+
if not settings.configured:
11+
settings.configure(
12+
SECRET_KEY="test",
13+
DEFAULT_CHARSET="utf-8",
14+
INSTALLED_APPS=[
15+
"django.contrib.auth",
16+
"django.contrib.contenttypes",
17+
],
18+
DATABASES={
19+
"default": {
20+
"ENGINE": "django.db.backends.sqlite3",
21+
"NAME": ":memory:",
22+
}
23+
},
24+
)
25+
django.setup()
26+
27+
call_command("migrate", verbosity=0, interactive=False)
28+
529

630
def mock_request_factory(override_headers):
7-
class Request:
8-
META = {}
9-
# TRICKY: Actual django request dict object has case insensitive matching, and strips http from the names
10-
headers = {
31+
factory = RequestFactory(
32+
headers={
1133
"User-Agent": DEFAULT_USER_AGENT,
1234
"Referrer": "http://example.com",
1335
"X-Forwarded-For": "193.4.5.12",
1436
**(override_headers or {}),
1537
}
38+
)
1639

17-
return Request()
40+
request = factory.get("/api/endpoint")
41+
return request
1842

1943

2044
def test_request_extractor_with_no_trace():
@@ -25,19 +49,22 @@ def test_request_extractor_with_no_trace():
2549
"user_agent": DEFAULT_USER_AGENT,
2650
"traceparent": None,
2751
"distinct_id": None,
52+
"$request.path": "/api/endpoint",
2853
}
2954

3055

3156
def test_request_extractor_with_trace():
3257
request = mock_request_factory(
3358
{"traceparent": "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01"}
3459
)
60+
3561
extractor = DjangoRequestExtractor(request)
3662
assert extractor.extract_person_data() == {
3763
"ip": "193.4.5.12",
3864
"user_agent": DEFAULT_USER_AGENT,
3965
"traceparent": "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01",
4066
"distinct_id": None,
67+
"$request.path": "/api/endpoint",
4168
}
4269

4370

@@ -54,6 +81,7 @@ def test_request_extractor_with_tracestate():
5481
"user_agent": DEFAULT_USER_AGENT,
5582
"traceparent": "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01",
5683
"distinct_id": "1234",
84+
"$request.path": "/api/endpoint",
5785
}
5886

5987

@@ -67,4 +95,27 @@ def test_request_extractor_with_complicated_tracestate():
6795
"user_agent": DEFAULT_USER_AGENT,
6896
"traceparent": None,
6997
"distinct_id": "alohaMountainsXUYZ",
98+
"$request.path": "/api/endpoint",
99+
}
100+
101+
102+
def test_request_extractor_with_request_user():
103+
from django.contrib.auth.models import User
104+
105+
user = User.objects.create_user(
106+
username="test", email="[email protected]", password="top_secret"
107+
)
108+
109+
request = mock_request_factory(None)
110+
request.user = user
111+
112+
extractor = DjangoRequestExtractor(request)
113+
assert extractor.extract_person_data() == {
114+
"ip": "193.4.5.12",
115+
"user_agent": DEFAULT_USER_AGENT,
116+
"traceparent": None,
117+
"distinct_id": None,
118+
"$request.path": "/api/endpoint",
119+
"$user.email": "[email protected]",
120+
"$user.id": "1",
70121
}

0 commit comments

Comments
 (0)