FIX(notifications): action_code accepts dots, PushMessage.stats actually updates #4

Merged
Ghasemi merged 1 commit from feature/notification-actions into master 2026-09-01 08:38:17 -04:00
3 changed files with 38 additions and 4 deletions

View file

@ -0,0 +1,24 @@
# Generated by Django 5.2.6 on 2026-09-01 12:23
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('push_notifications', '0006_rename_code_notificationlink_action_code_and_more'),
]
operations = [
migrations.AlterField(
model_name='notificationlink',
name='action_code',
field=models.CharField(db_index=True, max_length=100, unique=True, validators=[django.core.validators.RegexValidator(message='Enter a valid action code: letters, numbers, underscores, hyphens, or dots.', regex='^[-a-zA-Z0-9_.]+\\Z')]),
),
migrations.AlterField(
model_name='pushmessage',
name='action_code',
field=models.CharField(blank=True, db_index=True, max_length=100, null=True, validators=[django.core.validators.RegexValidator(message='Enter a valid action code: letters, numbers, underscores, hyphens, or dots.', regex='^[-a-zA-Z0-9_.]+\\Z')]),
),
]

View file

@ -1,3 +1,4 @@
from django.core.validators import RegexValidator
from django.db import models
from oauth2_provider.settings import oauth2_settings
@ -6,6 +7,15 @@ from utils.models import BaseModel
from utils.clients import gotify
from django.utils.translation import gettext_lazy as _
# action_code values are dotted, e.g. "billboard.approved", "escrow.timeout"
# (see docs/frontend-notification-click-actions.md) — a plain SlugField
# rejects the dot, so every real call using the documented catalog gets a
# 400 from the serializer. This mirrors slug's allowed charset but adds ".".
validate_action_code = RegexValidator(
regex=r'^[-a-zA-Z0-9_.]+\Z',
message="Enter a valid action code: letters, numbers, underscores, hyphens, or dots.",
)
class PushUserQuerySet(models.QuerySet):
def submit(self, user):
@ -51,7 +61,7 @@ class NotificationLink(BaseModel):
type) instead of a raw URL, so where a tap should navigate is a config
change here, not a code deploy across every producer service.
"""
action_code = models.SlugField(max_length=100, unique=True, db_index=True)
action_code = models.CharField(max_length=100, unique=True, db_index=True, validators=[validate_action_code])
description = models.CharField(max_length=255, blank=True)
url_template = models.CharField(
max_length=1000,
@ -93,7 +103,7 @@ class PushMessage(BaseModel):
# action_code, resolved against NotificationLink at send time (see
# get_gotify_extras). click_object_id_value is substituted into that
# action_code's {object_id} placeholder.
action_code = models.SlugField(max_length=100, null=True, blank=True, db_index=True)
action_code = models.CharField(max_length=100, null=True, blank=True, db_index=True, validators=[validate_action_code])
click_object_id_value = models.CharField(max_length=255, null=True, blank=True)
stats = models.IntegerField(default=StateChoices.INIT, choices=StateChoices.choices)

View file

@ -11,8 +11,8 @@ def send_push_notification(push_message_uuid):
message=push_message.message,
priority=push_message.priority,
extras=push_message.get_gotify_extras())
push_message.state = push_message.StateChoices.DONE
push_message.save()
push_message.stats = push_message.StateChoices.DONE
push_message.save(update_fields=["stats"])
@celery_app.task