Track users submitting events
authorMagnus Hagander <magnus@hagander.net>
Fri, 21 Nov 2025 08:55:46 +0000 (09:55 +0100)
committerMagnus Hagander <magnus@hagander.net>
Wed, 26 Nov 2025 11:09:43 +0000 (12:09 +0100)
Track when users submit bugs, news, events, comments etc in a simple
table. The idea is we will then be able to use this for rate limiting,
and also for tracking dosn some level of abuse.

pgweb/core/migrations/0007_usersubmissions.py [new file with mode: 0644]
pgweb/core/models.py
pgweb/docs/views.py
pgweb/misc/views.py
pgweb/util/helpers.py

diff --git a/pgweb/core/migrations/0007_usersubmissions.py b/pgweb/core/migrations/0007_usersubmissions.py
new file mode 100644 (file)
index 0000000..c80fcc9
--- /dev/null
@@ -0,0 +1,28 @@
+# Generated by Django 4.2.11 on 2025-11-21 08:23
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+        ('core', '0006_version_docsgit'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='UserSubmission',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('when', models.DateTimeField(auto_now_add=True)),
+                ('what', models.CharField(max_length=100)),
+                ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+            ],
+            options={
+                'indexes': [models.Index(fields=['user', 'when'], name='core_usersubm_userwhen_idx')],
+            },
+        ),
+    ]
index 98716d7606d4ffb971f96c2501a3fdad1f247f09..54185917ece2003f56ed58c0a3a79472174d56e5 100644 (file)
@@ -274,6 +274,20 @@ class UserProfile(models.Model):
                                       help_text="Disallow login to this account using OAuth providers like Google or Microsoft.")
 
 
+class UserSubmission(models.Model):
+    user = models.ForeignKey(User, null=False, blank=False, on_delete=models.CASCADE)
+    when = models.DateTimeField(null=False, blank=False, auto_now_add=True)
+    what = models.CharField(null=False, blank=False, max_length=100)
+
+    class Meta:
+        indexes = [
+            models.Index(
+                name='core_usersubm_userwhen_idx',
+                fields=('user', 'when'),
+            ),
+        ]
+
+
 # Notifications sent for any moderated content.
 # Yes, we uglify it by storing the type of object as a string, so we don't
 # end up with a bazillion fields being foreign keys. Ugly, but works.
index b37895edbce2bcd9f2a7319d0a52cb9faac2f4e4..c40748a27d1e6bb33484bb44f237d13e82b02d34 100644 (file)
@@ -14,7 +14,7 @@ from pgweb.util.contexts import render_pgweb
 from pgweb.util.helpers import template_to_string
 from pgweb.util.misc import send_template_mail
 
-from pgweb.core.models import Version
+from pgweb.core.models import Version, UserSubmission
 from pgweb.util.db import exec_to_dict
 
 from .models import DocPage, DocPageRedirect
@@ -420,6 +420,7 @@ def commentform(request, itemid, version, filename):
                 replyto='%s, %s' % (form.cleaned_data['email'], settings.DOCSREPORT_EMAIL),
                 sendername='PG Doc comments form'
             )
+            UserSubmission(user=request.user, what='Added comment to {}/{}'.format(version, filename)).save()
             return HttpResponseRedirect("done/")
     else:
         form = DocCommentForm(initial={
index 10356e67be1c1f81e17bd9cee821ac89f0e7db09..01a89856a3dc531fa515804d4e484e9829197015 100644 (file)
@@ -12,7 +12,7 @@ from pgweb.util.contexts import render_pgweb
 from pgweb.util.helpers import template_to_string
 from pgweb.util.misc import send_template_mail
 
-from pgweb.core.models import Version
+from pgweb.core.models import Version, UserSubmission
 from pgweb.misc.models import BugIdMap
 
 from .forms import SubmitBugForm
@@ -54,6 +54,8 @@ def submitbug(request):
                     messageid=messageid,
                 )
 
+                UserSubmission(user=request.user, what='Submitted bug {}'.format(bugid)).save()
+
                 return HttpResponseRedirect("/account/submitbug/{0}/".format(bugid))
     else:
         form = SubmitBugForm(initial={
index f68534f40e4714cc2c49e92cd9fc7494d47ea4ce..93896c686b58c58e41842f5c36b0e6d3a3e0651f 100644 (file)
@@ -45,6 +45,8 @@ def simple_form(instancetype, itemid, request, formclass, formtemplate='base/for
             raise PermissionDenied("You cannot edit this item")
 
     if request.method == 'POST':
+        from pgweb.core.models import UserSubmission
+
         if 'modstate' in (f.name for f in instance._meta.get_fields()) and instance.modstate == ModerationState.CREATED and request.POST.get('delete', '') == 'delete':
             # Don't care to validate, just delete.
             instance.delete()
@@ -111,6 +113,7 @@ def simple_form(instancetype, itemid, request, formclass, formtemplate='base/for
                     else:
                         notify.write("{}\n".format(str(form.cleaned_data[f])))
                     notify.write("\n")
+                UserSubmission(user=request.user, what='Added {} {}'.format(instance._meta.verbose_name, instance.id)).save()
             else:
                 subj = '{0} id {1} ({2}) has been modified'.format(instance._meta.verbose_name, instance.id, str(instance))
 
@@ -150,6 +153,11 @@ def simple_form(instancetype, itemid, request, formclass, formtemplate='base/for
                         if diffrows:
                             notify.write("\n".join(diffrows))
                             notify.write("\n\n")
+                if do_notify:
+                    # We only store modification events if it's a change that would've been notified about (meaning users can edit
+                    # a pending entry an unlimited number of times without storing modification events, but once it has been approved,
+                    # any further edits are logged)
+                    UserSubmission(user=request.user, what='Modified {} {}'.format(instance._meta.verbose_name, instance.id)).save()
 
             if do_notify and notify.tell():
                 send_simple_mail(