-
Notifications
You must be signed in to change notification settings - Fork 9
♻️(backend) introduce Service.slug as immutable index identifier #159
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
b596cf7
6b3bd0c
5b2a938
2b711f7
ac59ee6
33af6ed
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,13 +4,19 @@ | |
| import string | ||
|
|
||
| from django.contrib.auth.models import AbstractUser | ||
| from django.core.exceptions import ValidationError | ||
| from django.core.validators import RegexValidator | ||
| from django.db import models | ||
| from django.db.models.functions import Length | ||
| from django.utils.text import slugify | ||
| from django.utils.translation import gettext_lazy as _ | ||
|
|
||
| models.CharField.register_lookup(Length) | ||
| TOKEN_LENGTH = 50 | ||
| SLUG_REGEX = r"^[a-z0-9]+$" | ||
| SLUG_VALIDATOR = RegexValidator( | ||
| regex=SLUG_REGEX, | ||
| message=_("Slug must contain only lowercase letters and digits."), | ||
| ) | ||
|
|
||
|
|
||
| class User(AbstractUser): | ||
|
|
@@ -20,15 +26,22 @@ class User(AbstractUser): | |
| class Service(models.Model): | ||
| """Service registered to index its documents to our find""" | ||
|
|
||
| name = models.SlugField(max_length=20, unique=True) | ||
| name = models.CharField(max_length=255) | ||
| slug = models.SlugField( | ||
| max_length=20, | ||
| unique=True, | ||
| validators=[SLUG_VALIDATOR], | ||
|
StephanMeijer marked this conversation as resolved.
|
||
| help_text=_( | ||
| "Stable identifier used in the OpenSearch index name. " | ||
| "Lowercase alphanumeric only. Set on creation, immutable thereafter." | ||
| ), | ||
| ) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also you may want to modify the admin model and prepopulate this field using dedicated utility (see Django docs).
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point on the admin flow. I think we should make So I propose to expose There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perfect. |
||
| token = models.CharField(max_length=TOKEN_LENGTH) | ||
| created_at = models.DateTimeField(auto_now_add=True) | ||
| is_active = models.BooleanField(default=True) | ||
| client_id = models.CharField(blank=True, null=True) | ||
| services = models.ManyToManyField( | ||
| "self", | ||
| verbose_name=_("Allowed services for search"), | ||
| blank=True, | ||
| "self", blank=True, verbose_name=_("Allowed services for search") | ||
| ) | ||
|
|
||
| class Meta: | ||
|
|
@@ -41,14 +54,34 @@ class Meta: | |
| condition=models.Q(token__length=TOKEN_LENGTH), | ||
| name="token_length_exact_50", | ||
| ), | ||
| models.CheckConstraint( | ||
| condition=models.Q(slug__regex=SLUG_REGEX), | ||
| name="slug_alphanumeric_only", | ||
| ), | ||
| ] | ||
|
|
||
| def __str__(self): | ||
| return self.name | ||
|
|
||
| def save(self, *args, **kwargs): | ||
| """Automatically slugify the service name and generate a token on creation""" | ||
| self.name = slugify(self.name) | ||
| """Generate token and enforce slug immutability. | ||
|
|
||
| - ``slug`` must be provided explicitly on creation and is immutable | ||
| thereafter. The DB check constraint enforces the allowed character | ||
| set; no auto-derivation is performed. | ||
| - ``name`` is a free-form display field and can be edited. | ||
| - ``token`` is generated once on creation if missing. | ||
| """ | ||
| if not self._state.adding: | ||
| stored_slug = ( | ||
| Service.objects.filter(pk=self.pk) | ||
| .values_list("slug", flat=True) | ||
| .first() | ||
| ) | ||
| if self.slug != stored_slug: | ||
| raise ValidationError( | ||
| {"slug": _("Service.slug is immutable after creation.")} | ||
| ) | ||
| if not self.token: | ||
| self.token = self.generate_secure_token() | ||
| super().save(*args, **kwargs) | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.