Deleted transitions remain in history database table
La feat 79! en affichant l'historique des transitions de profile a révélé un bug introduit dans le refactor des transition en 2020: e41ff7fd
Trace:
[2024-09-30 14:57:01,543] ERROR in app: Exception on /moderation/ [GET]
Traceback (most recent call last):
File "/var/www/hiboo/.venv/lib/python3.9/site-packages/flask/app.py", line 1473, in wsgi_app
response = self.full_dispatch_request()
File "/var/www/hiboo/.venv/lib/python3.9/site-packages/flask/app.py", line 882, in full_dispatch_request
rv = self.handle_user_exception(e)
File "/var/www/hiboo/.venv/lib/python3.9/site-packages/flask/app.py", line 880, in full_dispatch_request
rv = self.dispatch_request()
File "/var/www/hiboo/.venv/lib/python3.9/site-packages/flask/app.py", line 865, in dispatch_request
return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args) # type: ignore[no-any-return]
File "/var/www/hiboo/.venv/lib/python3.9/site-packages/flask_login/utils.py", line 290, in decorated_view
return current_app.ensure_sync(func)(*args, **kwargs)
File "/var/www/hiboo/hiboo/security.py", line 25, in wrapper
return callback(
File "/var/www/hiboo/hiboo/security.py", line 19, in callback
return function(*args, **kwargs)
File "/var/www/hiboo/hiboo/moderation/views.py", line 17, in board
return flask.render_template("moderation_home.html", events=events,
File "/var/www/hiboo/.venv/lib/python3.9/site-packages/flask/templating.py", line 150, in render_template
return _render(app, template, context)
File "/var/www/hiboo/.venv/lib/python3.9/site-packages/flask/templating.py", line 131, in _render
rv = template.render(context)
File "/var/www/hiboo/.venv/lib/python3.9/site-packages/jinja2/environment.py", line 1304, in render
self.environment.handle_exception()
File "/var/www/hiboo/.venv/lib/python3.9/site-packages/jinja2/environment.py", line 939, in handle_exception
raise rewrite_traceback_stack(source=source)
File "/var/www/hiboo/hiboo/moderation/templates/moderation_home.html", line 1, in top-level template code
{% extends "base.html" %}
File "/var/www/hiboo/hiboo/templates/base.html", line 94, in top-level template code
{% block content %}{% endblock %}
File "/var/www/hiboo/hiboo/moderation/templates/moderation_home.html", line 47, in block 'content'
{{ macros.timeline(events, public_only=False, show_user=True) }}
File "/var/www/hiboo/.venv/lib/python3.9/site-packages/jinja2/runtime.py", line 782, in _invoke
rv = self._func(*arguments)
File "/var/www/hiboo/hiboo/templates/macros.html", line 37, in template
{{ event.description }}
File "/var/www/hiboo/.venv/lib/python3.9/site-packages/jinja2/environment.py", line 487, in getattr
return getattr(obj, attribute)
File "/var/www/hiboo/hiboo/models.py", line 408, in description
return History.DESCRIPTION.get(self.category, "").format(this=self)
File "/var/www/hiboo/hiboo/models.py", line 404, in transition
return Profile.TRANSITIONS[self.value]
KeyError: 'purge-blocked'
Diff
diff --git a/hiboo/models.py b/hiboo/models.py
index fc45946..518dec9 100644
--- a/hiboo/models.py
+++ b/hiboo/models.py
@@ -2,6 +2,7 @@ from passlib import context, hash
from flask import current_app as app
from sqlalchemy.ext import declarative, mutable
from flask_babel import lazy_gettext as _
+from hiboo import actions
import flask_sqlalchemy
import flask_babel
@@ -196,17 +197,32 @@ class Profile(db.Model):
}
TRANSITIONS = {
- # Assigning or claiming is not a generic transition
- # so it is not listed here (it requires chosing a user)
- "activate": (REQUEST, ACTIVE, 120, True, _("activate")),
- "reject": (REQUEST, DELETED, 120, True, _("reject")),
- "block": (ACTIVE, BLOCKED, 0, True, _("block")),
- "unblock": (BLOCKED, ACTIVE, 120, True, _("unblock")),
- "delete": (ACTIVE, DELETED, 120, False, _("delete")),
- "delete-blocked": (BLOCKED, DELETED, 120, True, _("delete")),
- "purge": (ACTIVE, PURGED, 120, True, _("purge")),
- "purge-blocked": (BLOCKED, PURGED, 120, True, _("purge")),
- "purge-deleted": (DELETED, PURGED, 120, True, _("purge"))
+ "activate": actions.Transition("activate",
+ label=_("activate"), description=_("activate this profile"),
+ from_=(REQUEST,), to=ACTIVE
+ ),
+ "reject": actions.Transition("reject",
+ label=_("reject"), description=_("reject this request"),
+ from_=(REQUEST,), to=PURGED
+ ),
+ "block": actions.Transition("block",
+ label=_("block"), description=_("block this profile"),
+ from_=(ACTIVE,), to=BLOCKED
+ ),
+ "unblock": actions.Transition("unblock",
+ label=_("unblock"), description=_("unblock this blocked profile"),
+ from_=(BLOCKED,), to=ACTIVE
+ ),
+ "delete": actions.Transition("delete",
+ label=_("delete"), description=_("delete this profile"),
+ from_=(ACTIVE, BLOCKED), to=DELETED,
+ admin_only=False, delay=120
+ ),
+ "purge": actions.Transition("purge",
+ label=_("purge"), description=_("delete and purge this profile"),
+ from_=(ACTIVE, BLOCKED, DELETED), to=PURGED,
+ delay=120
+ )
}
user_uuid = db.Column(db.String(36), db.ForeignKey(User.uuid))
@@ -245,34 +261,6 @@ class Profile(db.Model):
)
))
- def transitions(self, actor):
- return {
- name: transition for name, transition in Profile.TRANSITIONS.items()
- if transition[0] == self.status and not self.transition_step
- and (actor.is_admin or (self.user.uuid == actor.uuid and not transition[3]))
- }
-
- def transition_delta(self, formatted=False):
- delta = self.transition_time - datetime.datetime.now() if self.transition_time else 0
- return flask_babel.format_timedelta(delta) if formatted else delta
-
- def set_transition(self, transition, actor):
- """ Prepare the profile for transition
- """
- _, _, delta, _, _ = Profile.TRANSITIONS[transition]
- self.transition = transition
- self.transition_step = Profile.INIT
- self.transition_time = datetime.datetime.now() + datetime.timedelta(seconds=delta)
- log(
- category=History.TRANSITION,
- value=transition,
- user=self.user,
- service=self.service,
- profile=self,
- actor=actor,
- public=True
- )
-
class ClaimName(db.Model):
""" A profile might have multiple claimable names.
@@ -315,7 +303,7 @@ class History(db.Model):
CREATE: _("created the profile {this.profile.username} on {this.service.name}"),
PASSWORD: _("changed this account password"),
STATUS: _("set the {this.service.name} profile {this.profile.username} as {this.value}"),
- TRANSITION: _("did {this.transition[4]} the profile {this.profile.username} on {this.service.name}")
+ TRANSITION: _("did {this.transition.label} the profile {this.profile.username} on {this.service.name}")
diff --git a/hiboo/models.py b/hiboo/models.py
index fc45946..518dec9 100644
--- a/hiboo/models.py
+++ b/hiboo/models.py
@@ -2,6 +2,7 @@ from passlib import context, hash
from flask import current_app as app
from sqlalchemy.ext import declarative, mutable
from flask_babel import lazy_gettext as _
+from hiboo import actions
import flask_sqlalchemy
import flask_babel
@@ -196,17 +197,32 @@ class Profile(db.Model):
}
TRANSITIONS = {
- # Assigning or claiming is not a generic transition
- # so it is not listed here (it requires chosing a user)
- "activate": (REQUEST, ACTIVE, 120, True, _("activate")),
- "reject": (REQUEST, DELETED, 120, True, _("reject")),
- "block": (ACTIVE, BLOCKED, 0, True, _("block")),
- "unblock": (BLOCKED, ACTIVE, 120, True, _("unblock")),
- "delete": (ACTIVE, DELETED, 120, False, _("delete")),
- "delete-blocked": (BLOCKED, DELETED, 120, True, _("delete")),
- "purge": (ACTIVE, PURGED, 120, True, _("purge")),
- "purge-blocked": (BLOCKED, PURGED, 120, True, _("purge")),
- "purge-deleted": (DELETED, PURGED, 120, True, _("purge"))
+ "activate": actions.Transition("activate",
+ label=_("activate"), description=_("activate this profile"),
+ from_=(REQUEST,), to=ACTIVE
+ ),
+ "reject": actions.Transition("reject",
+ label=_("reject"), description=_("reject this request"),
+ from_=(REQUEST,), to=PURGED
+ ),
+ "block": actions.Transition("block",
+ label=_("block"), description=_("block this profile"),
+ from_=(ACTIVE,), to=BLOCKED
+ ),
+ "unblock": actions.Transition("unblock",
+ label=_("unblock"), description=_("unblock this blocked profile"),
+ from_=(BLOCKED,), to=ACTIVE
+ ),
+ "delete": actions.Transition("delete",
+ label=_("delete"), description=_("delete this profile"),
+ from_=(ACTIVE, BLOCKED), to=DELETED,
+ admin_only=False, delay=120
+ ),
+ "purge": actions.Transition("purge",
+ label=_("purge"), description=_("delete and purge this profile"),
+ from_=(ACTIVE, BLOCKED, DELETED), to=PURGED,
+ delay=120
+ )
}
user_uuid = db.Column(db.String(36), db.ForeignKey(User.uuid))
@@ -245,34 +261,6 @@ class Profile(db.Model):
)
))
- def transitions(self, actor):
- return {
- name: transition for name, transition in Profile.TRANSITIONS.items()
- if transition[0] == self.status and not self.transition_step
- and (actor.is_admin or (self.user.uuid == actor.uuid and not transition[3]))
- }
-
- def transition_delta(self, formatted=False):
- delta = self.transition_time - datetime.datetime.now() if self.transition_time else 0
- return flask_babel.format_timedelta(delta) if formatted else delta
-
- def set_transition(self, transition, actor):
- """ Prepare the profile for transition
- """
- _, _, delta, _, _ = Profile.TRANSITIONS[transition]
- self.transition = transition
- self.transition_step = Profile.INIT
- self.transition_time = datetime.datetime.now() + datetime.timedelta(seconds=delta)
- log(
- category=History.TRANSITION,
- value=transition,
- user=self.user,
- service=self.service,
- profile=self,
- actor=actor,
- public=True
- )
-
class ClaimName(db.Model):
""" A profile might have multiple claimable names.
@@ -315,7 +303,7 @@ class History(db.Model):
CREATE: _("created the profile {this.profile.username} on {this.service.name}"),
PASSWORD: _("changed this account password"),
STATUS: _("set the {this.service.name} profile {this.profile.username} as {this.value}"),
- TRANSITION: _("did {this.transition[4]} the profile {this.profile.username} on {this.service.name}")
+ TRANSITION: _("did {this.transition.label} the profile {this.profile.username} on {this.service.name}")
diff --git a/hiboo/models.py b/hiboo/models.py
index fc45946..518dec9 100644
--- a/hiboo/models.py
+++ b/hiboo/models.py
@@ -2,6 +2,7 @@ from passlib import context, hash
from flask import current_app as app
from sqlalchemy.ext import declarative, mutable
from flask_babel import lazy_gettext as _
+from hiboo import actions
import flask_sqlalchemy
import flask_babel
@@ -196,17 +197,32 @@ class Profile(db.Model):
}
TRANSITIONS = {
- # Assigning or claiming is not a generic transition
- # so it is not listed here (it requires chosing a user)
- "activate": (REQUEST, ACTIVE, 120, True, _("activate")),
- "reject": (REQUEST, DELETED, 120, True, _("reject")),
- "block": (ACTIVE, BLOCKED, 0, True, _("block")),
- "unblock": (BLOCKED, ACTIVE, 120, True, _("unblock")),
- "delete": (ACTIVE, DELETED, 120, False, _("delete")),
- "delete-blocked": (BLOCKED, DELETED, 120, True, _("delete")),
- "purge": (ACTIVE, PURGED, 120, True, _("purge")),
- "purge-blocked": (BLOCKED, PURGED, 120, True, _("purge")),
- "purge-deleted": (DELETED, PURGED, 120, True, _("purge"))
+ "activate": actions.Transition("activate",
+ label=_("activate"), description=_("activate this profile"),
+ from_=(REQUEST,), to=ACTIVE
+ ),
+ "reject": actions.Transition("reject",
+ label=_("reject"), description=_("reject this request"),
+ from_=(REQUEST,), to=PURGED
+ ),
+ "block": actions.Transition("block",
+ label=_("block"), description=_("block this profile"),
+ from_=(ACTIVE,), to=BLOCKED
+ ),
+ "unblock": actions.Transition("unblock",
+ label=_("unblock"), description=_("unblock this blocked profile"),
+ from_=(BLOCKED,), to=ACTIVE
+ ),
+ "delete": actions.Transition("delete",
+ label=_("delete"), description=_("delete this profile"),
+ from_=(ACTIVE, BLOCKED), to=DELETED,
+ admin_only=False, delay=120
+ ),
+ "purge": actions.Transition("purge",
+ label=_("purge"), description=_("delete and purge this profile"),
+ from_=(ACTIVE, BLOCKED, DELETED), to=PURGED,
+ delay=120
+ )
}
user_uuid = db.Column(db.String(36), db.ForeignKey(User.uuid))
@@ -245,34 +261,6 @@ class Profile(db.Model):
)
))
- def transitions(self, actor):
- return {
- name: transition for name, transition in Profile.TRANSITIONS.items()
- if transition[0] == self.status and not self.transition_step
- and (actor.is_admin or (self.user.uuid == actor.uuid and not transition[3]))
- }
-
- def transition_delta(self, formatted=False):
- delta = self.transition_time - datetime.datetime.now() if self.transition_time else 0
- return flask_babel.format_timedelta(delta) if formatted else delta
-
- def set_transition(self, transition, actor):
- """ Prepare the profile for transition
- """
- _, _, delta, _, _ = Profile.TRANSITIONS[transition]
- self.transition = transition
- self.transition_step = Profile.INIT
- self.transition_time = datetime.datetime.now() + datetime.timedelta(seconds=delta)
- log(
- category=History.TRANSITION,
- value=transition,
- user=self.user,
- service=self.service,
- profile=self,
- actor=actor,
- public=True
- )
-
class ClaimName(db.Model):
""" A profile might have multiple claimable names.
@@ -315,7 +303,7 @@ class History(db.Model):
CREATE: _("created the profile {this.profile.username} on {this.service.name}"),
PASSWORD: _("changed this account password"),
STATUS: _("set the {this.service.name} profile {this.profile.username} as {this.value}"),
- TRANSITION: _("did {this.transition[4]} the profile {this.profile.username} on {this.service.name}")
+ TRANSITION: _("did {this.transition.label} the profile {this.profile.username} on {this.service.name}")
}
user_uuid = db.Column(db.String(36), db.ForeignKey(User.uuid))
}
user_uuid = db.Column(db.String(36), db.ForeignKey(User.uuid))
}
user_uuid = db.Column(db.String(36), db.ForeignKey(User.uuid))
En supprimant les transitions purge-deleted
et purge-blocked
sans mettre a jour la base de donnée, les transitions sont restée dans la table history
mais ne sont plus présente dans modele.py
et donc err 500. Il faudrait écrire un migration pour réécrire l'historique de purge-deleted
et purge-blocked
en purge