Skip to content

Commit 02ebae2

Browse files
authored
HistoricalRecords: add additional fields (ip and browser) (#8490)
1 parent db11521 commit 02ebae2

File tree

6 files changed

+153
-2
lines changed

6 files changed

+153
-2
lines changed

readthedocs/core/history.py

+14-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ class ExtraFieldsHistoricalModel(models.Model):
2929
3030
Extra data includes:
3131
32-
- Users after they have been deleted.
32+
- User information to retain after they have been deleted
33+
- IP & browser
3334
"""
3435

3536
extra_history_user_id = models.IntegerField(
@@ -44,6 +45,18 @@ class ExtraFieldsHistoricalModel(models.Model):
4445
null=True,
4546
db_index=True,
4647
)
48+
extra_history_ip = models.CharField(
49+
_('IP address'),
50+
blank=True,
51+
null=True,
52+
max_length=250,
53+
)
54+
extra_history_browser = models.CharField(
55+
_('Browser user-agent'),
56+
max_length=250,
57+
blank=True,
58+
null=True,
59+
)
4760

4861
class Meta:
4962
abstract = True
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Generated by Django 2.2.24 on 2021-09-29 19:27
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('core', '0007_historicaluser'),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name='historicaluser',
15+
name='extra_history_browser',
16+
field=models.CharField(blank=True, max_length=250, null=True, verbose_name='Browser user-agent'),
17+
),
18+
migrations.AddField(
19+
model_name='historicaluser',
20+
name='extra_history_ip',
21+
field=models.CharField(blank=True, max_length=250, null=True, verbose_name='IP address'),
22+
),
23+
]

readthedocs/core/signals.py

+11-1
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@
88
from django.db.models.signals import pre_delete
99
from django.dispatch import Signal, receiver
1010
from rest_framework.permissions import SAFE_METHODS
11+
from simple_history.models import HistoricalRecords
1112
from simple_history.signals import pre_create_historical_record
1213

14+
from readthedocs.analytics.utils import get_client_ip
1315
from readthedocs.builds.models import Version
1416
from readthedocs.core.unresolver import unresolve
1517
from readthedocs.projects.models import Project
@@ -122,10 +124,18 @@ def delete_projects(sender, instance, *args, **kwargs):
122124
@receiver(pre_create_historical_record)
123125
def add_extra_historical_fields(sender, **kwargs):
124126
history_instance = kwargs['history_instance']
127+
if not history_instance:
128+
return
129+
125130
history_user = kwargs['history_user']
126-
if history_instance and history_user:
131+
if history_user:
127132
history_instance.extra_history_user_id = history_user.id
128133
history_instance.extra_history_user_username = history_user.username
129134

135+
request = getattr(HistoricalRecords.context, 'request', None)
136+
if request:
137+
history_instance.extra_history_ip = get_client_ip(request)
138+
history_instance.extra_history_browser = request.headers.get('User-Agent')
139+
130140

131141
signals.check_request_enabled.connect(decide_if_cors)
+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
from django.contrib.auth.models import User
2+
from django.test import TestCase
3+
from django.urls import reverse
4+
from django_dynamic_fixture import get
5+
6+
from readthedocs.projects.models import Project
7+
8+
9+
class TestHistoricalModels(TestCase):
10+
11+
def setUp(self):
12+
self.user = get(User)
13+
self.project = get(Project, users=[self.user])
14+
self.client.force_login(self.user)
15+
16+
def test_extra_historical_fields_with_request(self):
17+
self.assertEqual(self.project.history.count(), 1)
18+
r = self.client.post(
19+
reverse('projects_edit', args=[self.project.slug]),
20+
data={
21+
'name': 'Changed!',
22+
'repo': 'https://github.com/readthedocs/readthedocs',
23+
'repo_type': self.project.repo_type,
24+
'language': self.project.language,
25+
},
26+
HTTP_USER_AGENT='Firefox',
27+
)
28+
self.assertEqual(r.status_code, 302)
29+
self.assertEqual(self.project.history.count(), 2)
30+
history = self.project.history.first()
31+
self.assertEqual(history.name, 'Changed!')
32+
self.assertEqual(history.history_user, self.user)
33+
self.assertEqual(history.extra_history_user_id, self.user.id)
34+
self.assertEqual(history.extra_history_user_username, self.user.username)
35+
self.assertEqual(history.extra_history_ip, '127.0.0.1')
36+
self.assertEqual(history.extra_history_browser, 'Firefox')
37+
38+
def test_extra_historical_fields_outside_request(self):
39+
self.assertEqual(self.project.history.count(), 1)
40+
self.project.name = 'Changed!'
41+
self.project.save()
42+
self.assertEqual(self.project.history.count(), 2)
43+
history = self.project.history.first()
44+
self.assertEqual(history.name, 'Changed!')
45+
self.assertIsNone(history.history_user)
46+
self.assertIsNone(history.extra_history_user_id)
47+
self.assertIsNone(history.extra_history_user_username)
48+
self.assertIsNone(history.extra_history_ip)
49+
self.assertIsNone(history.extra_history_browser)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Generated by Django 2.2.24 on 2021-09-29 19:27
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('organizations', '0006_add_assets_cleaned'),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name='historicalorganization',
15+
name='extra_history_browser',
16+
field=models.CharField(blank=True, max_length=250, null=True, verbose_name='Browser user-agent'),
17+
),
18+
migrations.AddField(
19+
model_name='historicalorganization',
20+
name='extra_history_ip',
21+
field=models.CharField(blank=True, max_length=250, null=True, verbose_name='IP address'),
22+
),
23+
migrations.AddField(
24+
model_name='historicalteam',
25+
name='extra_history_browser',
26+
field=models.CharField(blank=True, max_length=250, null=True, verbose_name='Browser user-agent'),
27+
),
28+
migrations.AddField(
29+
model_name='historicalteam',
30+
name='extra_history_ip',
31+
field=models.CharField(blank=True, max_length=250, null=True, verbose_name='IP address'),
32+
),
33+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Generated by Django 2.2.24 on 2021-09-29 19:27
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('projects', '0081_add_another_header'),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name='historicalproject',
15+
name='extra_history_browser',
16+
field=models.CharField(blank=True, max_length=250, null=True, verbose_name='Browser user-agent'),
17+
),
18+
migrations.AddField(
19+
model_name='historicalproject',
20+
name='extra_history_ip',
21+
field=models.CharField(blank=True, max_length=250, null=True, verbose_name='IP address'),
22+
),
23+
]

0 commit comments

Comments
 (0)