Push verbose_name to the model-level

at least for those fields where it is currently used.
this necessitates a patch to the migration machinery, which this commit adds

See #161
This commit is contained in:
Klaas van Schelven
2025-08-28 16:03:27 +02:00
parent f38112f3df
commit a0dc91c8c5
7 changed files with 43 additions and 29 deletions

View File

@@ -0,0 +1,20 @@
from django.db import models
IGNORED_ATTRS = ['verbose_name', 'help_text']
original_deconstruct = models.Field.deconstruct
def new_deconstruct(self):
# works around the non-fix of https://code.djangoproject.com/ticket/21498 (I don't agree with the reasoning that
# "in principle any field could influence the database schema"; you must be _insane_ if verbose_name or help_text
# actually do, and the cost of the migrations is real)
# solution from https://stackoverflow.com/a/39801321/339144
name, path, args, kwargs = original_deconstruct(self)
for attr in IGNORED_ATTRS:
kwargs.pop(attr, None)
return name, path, args, kwargs
def monkey_patch_deconstruct():
models.Field.deconstruct = new_deconstruct

View File

@@ -0,0 +1,8 @@
from django.core.management.commands.makemigrations import Command as OriginalCommand
from . import monkey_patch_deconstruct
monkey_patch_deconstruct()
class Command(OriginalCommand):
pass # no changes, except the monkey patch above

View File

@@ -1,6 +1,9 @@
import time
from django.core.management.commands.migrate import Command as DjangoMigrateCommand
from . import monkey_patch_deconstruct
monkey_patch_deconstruct() # needed for migrate.py to avoid the warning about non-reflected changes
class Command(DjangoMigrateCommand):
# We override the default Django migrate command to add the elapsed time for each migration. (This could in theory
@@ -10,8 +13,7 @@ class Command(DjangoMigrateCommand):
# We care more about the elapsed time for each migration than the average Django user because sqlite takes such a
# prominent role in our architecture, and because migrations are run out of our direct control ("self hosted").
#
# AFAIU, "just dropping a file called migrate.py in one of our apps" is good enough to be the override (and if it
# isn't, it's not critical, since all we do is add a bit more info to the output).
# AFAIU, "just dropping a file called migrate.py in one of our apps" is good enough to be the override.
def migration_progress_callback(self, action, migration=None, fake=False):
# Django 4.2's method, with a single change

View File

@@ -3,7 +3,6 @@ from django.contrib.auth import get_user_model
from django.template.defaultfilters import yesno
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from django.utils.translation import pgettext_lazy
from django.utils.html import format_html
from bugsink.utils import assert_
@@ -47,8 +46,6 @@ class MyProjectMembershipForm(forms.ModelForm):
super().__init__(*args, **kwargs)
assert_(self.instance is not None, "This form is only implemented for editing")
self.fields['role'].label = _("Role")
if not edit_role:
del self.fields['role']
@@ -66,7 +63,6 @@ class MyProjectMembershipForm(forms.ModelForm):
sea_default = self.instance.user.send_email_alerts
empty_label = _('Default (%s, as per %s settings)') % (yesno(sea_default).capitalize(), sea_defined_at)
self.fields['send_email_alerts'].label = _("Send email alerts")
self.fields['send_email_alerts'].empty_label = empty_label
self.fields['send_email_alerts'].widget.choices[0] = ("unknown", empty_label)
@@ -87,10 +83,6 @@ class ProjectForm(forms.ModelForm):
team_qs = kwargs.pop("team_qs", None)
super().__init__(*args, **kwargs)
self.fields["name"].label = pgettext_lazy("Project", "Name")
self.fields["visibility"].label = _("Visibility")
self.fields["retention_max_event_count"].label = _("Retention max event count")
self.fields["retention_max_event_count"].help_text = _("The maximum number of events to store before evicting.")
if self.instance is not None and self.instance.pk is not None:
# for editing, we disallow changing the team. consideration: it's somewhat hard to see what the consequences

View File

@@ -3,7 +3,7 @@ import uuid
from django.db import models
from django.conf import settings
from django.utils.text import slugify
from django.utils.translation import gettext_lazy as _
from django.utils.translation import gettext_lazy as _, pgettext_lazy
from bugsink.app_settings import get_settings
from bugsink.transaction import delay_on_commit
@@ -76,7 +76,7 @@ class Project(models.Model):
team = models.ForeignKey("teams.Team", blank=False, null=True, on_delete=models.SET_NULL)
name = models.CharField(max_length=255, blank=False, null=False, unique=True)
name = models.CharField(pgettext_lazy("Project", "Name"), max_length=255, blank=False, null=False, unique=True)
slug = models.SlugField(max_length=50, blank=False, null=False, unique=True)
is_deleted = models.BooleanField(default=False)
@@ -109,7 +109,7 @@ class Project(models.Model):
# visibility
visibility = models.IntegerField(
choices=ProjectVisibility.choices, default=ProjectVisibility.TEAM_MEMBERS,
_("Visibility"), choices=ProjectVisibility.choices, default=ProjectVisibility.TEAM_MEMBERS,
help_text=_("Which users can see this project and its issues?"))
# ingestion/digestion quota
@@ -117,7 +117,7 @@ class Project(models.Model):
next_quota_check = models.PositiveIntegerField(null=False, default=0)
# retention
retention_max_event_count = models.PositiveIntegerField(default=10_000)
retention_max_event_count = models.PositiveIntegerField(_("Retention max event count"), default=10_000)
def __str__(self):
return self.name
@@ -179,9 +179,9 @@ class ProjectMembership(models.Model):
project = models.ForeignKey(Project, on_delete=models.DO_NOTHING)
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
send_email_alerts = models.BooleanField(default=None, null=True)
send_email_alerts = models.BooleanField(_("Send email alerts"), default=None, null=True)
role = models.IntegerField(choices=ProjectRole.choices, default=ProjectRole.MEMBER)
role = models.IntegerField(_("Role"), choices=ProjectRole.choices, default=ProjectRole.MEMBER)
accepted = models.BooleanField(default=False)
def __str__(self):

View File

@@ -2,7 +2,6 @@ from django import forms
from django.contrib.auth import get_user_model
from django.template.defaultfilters import yesno
from django.utils.translation import gettext_lazy as _
from django.utils.translation import pgettext_lazy
from bugsink.utils import assert_
from .models import TeamRole, TeamMembership, Team
@@ -43,8 +42,6 @@ class MyTeamMembershipForm(forms.ModelForm):
super().__init__(*args, **kwargs)
assert_(self.instance is not None, "This form is only implemented for editing")
self.fields['role'].label = _("Role")
if not edit_role:
del self.fields['role']
@@ -52,7 +49,6 @@ class MyTeamMembershipForm(forms.ModelForm):
global_send_email_alerts_text = yesno(global_send_email_alerts).capitalize()
empty_label = _("User-default (%s)") % global_send_email_alerts_text
self.fields['send_email_alerts'].label = _("Send email alerts")
self.fields['send_email_alerts'].empty_label = empty_label
self.fields['send_email_alerts'].widget.choices[0] = ("unknown", empty_label)
@@ -69,8 +65,3 @@ class TeamForm(forms.ModelForm):
class Meta:
model = Team
fields = ["name", "visibility"]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['name'].label = pgettext_lazy("Team", "Name")
self.fields['visibility'].label = _("Visibility")

View File

@@ -3,7 +3,7 @@ import uuid
from django.db import models
from django.conf import settings
from django.utils.translation import gettext_lazy as _
from django.utils.translation import gettext_lazy as _, pgettext_lazy
class TeamRole(models.IntegerChoices):
@@ -27,9 +27,10 @@ class TeamVisibility(models.IntegerChoices):
class Team(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
name = models.CharField(max_length=255, blank=False, null=False, unique=True)
name = models.CharField(pgettext_lazy("Team", "Name"), max_length=255, blank=False, null=False, unique=True)
visibility = models.IntegerField(
_("Visibility"),
choices=TeamVisibility.choices, default=TeamVisibility.DISCOVERABLE,
help_text=_("Which users can see this team and its issues?"))
@@ -49,8 +50,8 @@ class TeamMembership(models.Model):
team = models.ForeignKey(Team, on_delete=models.CASCADE)
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
send_email_alerts = models.BooleanField(default=None, null=True, blank=True)
role = models.IntegerField(choices=TeamRole.choices, default=TeamRole.MEMBER)
send_email_alerts = models.BooleanField(_("Send email alerts"), default=None, null=True, blank=True)
role = models.IntegerField(_("Role"), choices=TeamRole.choices, default=TeamRole.MEMBER)
accepted = models.BooleanField(default=False)
def __str__(self):