Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
e6f9f30
refator: combine 2 html
SbirLobo Jul 3, 2026
580eeac
feat: allow owner to transfer meeting ownership to a delegate
SbirLobo Jul 3, 2026
7f6b506
refactor: simplify transfert_meeting_ownership validation
SbirLobo Jul 3, 2026
83b362d
test: adds test for meeting ownership transfer
SbirLobo Jul 3, 2026
bda0d8a
feat: adds modal before trasfert meeting ownership
SbirLobo Jul 9, 2026
ffaa9e9
feat: add migration to replace integer meeting.id with uuid
SbirLobo Jul 10, 2026
79a0b4c
Merge branch 'main' into 254-remove-meetingID-and-fake_id
SbirLobo Jul 13, 2026
530208d
fix: change down revision ref after merge main
SbirLobo Jul 13, 2026
5a541d5
refactor: remove the fake_id concept, unify quick and persisted meeti…
SbirLobo Jul 17, 2026
704d6fb
test: remove test_create_favorite_table, no longer reachable
SbirLobo Jul 17, 2026
32428d7
refactor: remove useless quick_meeting param
SbirLobo Jul 20, 2026
19e583b
test: add test for migration and join quick meeting
SbirLobo Jul 20, 2026
5f69374
doc: adds information about non regressive migrations
SbirLobo Jul 21, 2026
646762b
Merge branch 'main' into 254-remove-meetingID-and-fake_id
SbirLobo Jul 21, 2026
2a01ed0
test: add test for join quick meeting as moderator
SbirLobo Jul 21, 2026
928fab0
feat: save meeting url in db
SbirLobo Jul 22, 2026
e7c67c2
feat: add used_at column to follow usage of meeting urls
SbirLobo Jul 22, 2026
3fd08c8
test: adds tests for meeting_urls used_at row
SbirLobo Jul 23, 2026
ff3a258
test: import groups model in template db fixtures
SbirLobo Jul 23, 2026
5bb7271
Merge branch 'main' into 254-remove-meetingID-and-fake_id
SbirLobo Jul 23, 2026
af76d01
Merge branch 'main' into 254-remove-meetingID-and-fake_id
SbirLobo Jul 23, 2026
e51ed81
refactor: avoid repetitive fixtures
SbirLobo Jul 23, 2026
6db85c1
refactor: harmonize last-used timestamps for a link and a meeting
SbirLobo Jul 23, 2026
07e9016
refactor: prepare meeting deletion for deletion with celery cron
SbirLobo Jul 24, 2026
4e561a2
refactor: use UUID v6 for meeting identifiers
SbirLobo Jul 24, 2026
6045da7
refactor: update migration name
SbirLobo Jul 24, 2026
3ef3b4c
fix: fix migration import in test
SbirLobo Jul 24, 2026
9e1c625
feat: replace uuid6 package by standart uuid7 and remove meeting.id …
SbirLobo Jul 27, 2026
0f7eb76
chore: use UTC alias instead of timezone.utc
SbirLobo Jul 27, 2026
e2bb218
fix: remove useless print in tests
SbirLobo Jul 28, 2026
6bea1f9
Merge branch '254-remove-meetingID-and-fake_id' into 329-transfer-mee…
SbirLobo Jul 29, 2026
364f4f3
fix: cancel previous modification
SbirLobo Jul 29, 2026
0773ef6
refactor: define bbb_meeting_id by default in meeting model
SbirLobo Jul 30, 2026
aee0c5d
fix: fix error of meeting access FK
SbirLobo Jul 30, 2026
7b5f713
refactor: move flash messages out of meetings model
SbirLobo Jul 30, 2026
7ccaccc
feat: remove used_at column in MeetingUrls table
SbirLobo Jul 30, 2026
ba5e115
feat: make bbb_meeting_id unique
SbirLobo Jul 31, 2026
74ecec9
refactor: replace MeetingUrls with MeetingSecretKey, storing secrets …
SbirLobo Jul 31, 2026
eabad4b
test: add test for meeting link retrocompatibility
SbirLobo Jul 31, 2026
0941d57
refactor: replace hash by secret_key
SbirLobo Jul 31, 2026
f4d9d1f
feat: renew moderator/attendee secret keys when update PW
SbirLobo Jul 31, 2026
3da3054
refactor: change logs to identify persistent meeting and quick meetin…
SbirLobo Jul 31, 2026
8023f50
test: add test for get role from url and quick meeting get secret key
SbirLobo Jul 31, 2026
83a59ed
Merge branch '254-remove-meetingID-and-fake_id' into 329-transfer-mee…
SbirLobo Jul 31, 2026
b5711ef
Merge branch 'main' into 329-transfer-meeting-ownership
SbirLobo Aug 17, 2026
1d1db1c
feat: change redirection for admin
SbirLobo Aug 17, 2026
e27a45f
Merge branch 'main' into 329-transfer-meeting-ownership
SbirLobo Aug 17, 2026
737c616
Merge branch 'main' into 329-transfer-meeting-ownership
azmeuk Aug 31, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
240 changes: 120 additions & 120 deletions uv.lock

Large diffs are not rendered by default.

66 changes: 66 additions & 0 deletions web/b3desk/endpoints/meetings.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
from ..session import is_admin_mode
from ..session import meeting_access_required
from ..utils import send_delegation_mail
from ..utils import send_new_owner_mail

bp = Blueprint("meetings", __name__)

Expand Down Expand Up @@ -479,3 +480,68 @@ def remove_delegate(meeting: Meeting, user: User, delegate: User):
admin_mode=is_admin_mode() or None,
)
)


@bp.route("/meeting/transfert-meeting-ownership/<meeting:meeting>/<user:delegate>")
@check_oidc_connection(auth)
@auth.oidc_auth("default")
@meeting_access_required()
def transfert_meeting_ownership(meeting: Meeting, user: User, delegate: User):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

À la réflexion, les changements d'état devraient plutôt être derrière des requêtes POST.

"""Display the page for manage meeting delegation."""

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ça ne semble pas être la bonne docstring :)

new_owner = delegate
if new_owner is None or new_owner not in meeting.get_all_delegates:
abort(404)

previous_owner = meeting.owner
meeting.owner = new_owner
meeting.owner_id = new_owner.id
new_access = MeetingAccess(
meeting_id=meeting.id,
user_id=previous_owner.id,
level=AccessLevel.DELEGATE,
)
removed_access = MeetingAccess.query.filter_by(
user_id=new_owner.id, meeting_id=meeting.id
).one()
db.session.add(new_access)
db.session.delete(removed_access)
db.session.commit()
current_app.logger.info(
"Meeting %s %s have a new owner : %s %s",
meeting.id,
meeting.name,
new_owner.id,
new_owner.fullname,
)
current_app.logger.info(
"%s became delegate of meeting %s %s",
previous_owner.email,
meeting.id,
meeting.name,
)
current_app.logger.info(
"%s removed from delegates of meeting %s %s",
new_owner.email,
meeting.id,
meeting.name,
)
Comment on lines +491 to +527

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

J'extrairais ça dans une méthode dédiée pour alléger la vue.

flash(
_(
"%(owner_name)s est le nouveau propriétaire de %(meeting_name)s",
owner_name=new_owner.fullname,
meeting_name=meeting.name,
),
"success",
)
send_delegation_mail(meeting, previous_owner, new_delegation=True)
send_new_owner_mail(meeting, new_owner, previous_owner)
return (
redirect(url_for("public.welcome"))
if not is_admin_mode()
else redirect(
url_for(
"admin.meeting_infos",
meeting=meeting,
)
)
)
11 changes: 11 additions & 0 deletions web/b3desk/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,17 @@ class MeetingSearchForm(FlaskForm):
)


class TransferMeetingOwnership(FlaskForm):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cette classe n'est jamais appelée non ?

select = SelectField(
label=_("Sélectionner un délégataire"),
description=_(
"Transférer la propriété d'une réunion est action définitive. Vous deviendrez délégataire de cette réunion."

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

est une action définitive

),
validators=[validators.DataRequired()],
choices=[],
)


class GroupSearchForm(FlaskForm):
search = StringField(
label=_("Rechercher un groupe"),
Expand Down
1 change: 1 addition & 0 deletions web/b3desk/models/meetings.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ def get_all_delegates(self):
MeetingAccess.meeting_id == self.id,
MeetingAccess.level == AccessLevel.DELEGATE,
)
.order_by(User.family_name, User.given_name)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

C'est un peu hors PR, tu peux pousser des choses comme ça directement sur main :) (mais ce n'est pas grave)

).all()

def url_for_role(self, role):
Expand Down
11 changes: 9 additions & 2 deletions web/b3desk/templates/meeting/delegation-management.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@ <h6>{% trans %} Ajouter des délégataires {% endtrans %}</h6>
<table id="delegation">
<thead class="">
<tr class="">
<th class="fr-col--lg fr-cell--center"> Liste des délégataires </th>
<th class="fr-col--xs fr-cell--center"> {{ meeting.get_all_delegates|length }}/{{ config.get("MAXIMUM_MEETING_DELEGATES") }} </th>
<th class="fr-col--lg fr-cell--center">{% trans %}Liste des délégataires{% endtrans %} ({{ meeting.get_all_delegates|length }}/{{ config.get("MAXIMUM_MEETING_DELEGATES") }})</th>
<th class="fr-col--xs fr-cell--center">{% trans %}Retirer{% endtrans %}</th>
<th class="fr-col--xs fr-cell--center">{% trans %}Transférer{% endtrans %}</th>
</tr>
</thead>
<tbody class="">
Expand All @@ -42,6 +43,9 @@ <h6>{% trans %} Ajouter des délégataires {% endtrans %}</h6>
<td class="fr-col--xs fr-cell--center">
<a class="fr-btn fr-btn--secondary fr-fi-delete-line" title="{% trans %}Retirer le délégataire{% endtrans %}" href="{{ url_for("meetings.remove_delegate", meeting=meeting, delegate=delegate, admin_mode=admin_mode or None) }}"></a>
</td>
<td class="fr-col--xs fr-cell--center">
<button class="fr-btn fr-icon-share-forward-line fr-btn--delete" data-fr-opened="false" aria-controls="transfert-ownership-confirmation-{{ delegate.id }}" title="{% trans meeting_name=meeting.name %}Transférer {{ meeting_name }}{% endtrans %}"></button>
</td>
</tr>
{% endfor %}
</tbody>
Expand All @@ -53,3 +57,6 @@ <h6>{% trans %} Ajouter des délégataires {% endtrans %}</h6>
</div>
</div>
</div>
{% for delegate in meeting.get_all_delegates %}
{% include 'meeting/modals/transfert-ownership-confirmation.html' %}
{% endfor %}
5 changes: 5 additions & 0 deletions web/b3desk/templates/meeting/delegation.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,8 @@
{% endif %}
{% include 'meeting/delegation-management.html' %}
{% endblock %}

{#

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

C'est du code commenté.

fr-icon-arrow-right-s-line-double
fr-icon-share-forward-line
#}
12 changes: 12 additions & 0 deletions web/b3desk/templates/meeting/mailto/mail_new_owner_body.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<html>
{% trans %}Bonjour{% endtrans %} {{ new_owner.given_name }} {{ new_owner.family_name }},
<br/>
<br/>
{{ previous_owner.fullname }} {% trans %}vous a transféré la propriété de la réunion{% endtrans %} {{ meeting.name }}.
<br/>
<br/>
{% trans %}Vous pouvez dès à présent consulter et gérer les délégataires, l'édition, les enregistrements et les fichiers associés à cette réunion.{% endtrans %}
<br/>
<br/>
{% trans %}Rendez vous simplement sur <a href="{{ welcome_url }}">votre espace</a>.{% endtrans %}
</html>
7 changes: 7 additions & 0 deletions web/b3desk/templates/meeting/mailto/mail_new_owner_body.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{% trans %}Bonjour{% endtrans %} {{ new_owner.given_name }} {{ new_owner.family_name }},

{{ previous_owner.fullname }} {% trans %}vous a transféré la propriété de la réunion{% endtrans %} {{ meeting.name }}.

{% trans %}Vous pouvez dès à présent consulter et gérer les délégataires, l'édition, les enregistrements et les fichiers associés à cette réunion.{% endtrans %}

{% trans %}Rendez-vous simplement sur votre espace :{% endtrans %} {{ welcome_url }}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<dialog id="transfert-ownership-confirmation-{{ delegate.id }}" class="fr-modal" role="dialog" aria-labelledby="transfert-ownership-confirmation-{{ delegate.id }}-title">
<div class="fr-container fr-container--fluid fr-container-md">
<div class="fr-grid-row fr-grid-row--center">
<div class="fr-col-12 fr-col-md-8 fr-col-lg-6">
<div class="fr-modal__body">
<div class="fr-modal__header">
<button class="fr-btn--close fr-btn" aria-controls="transfert-ownership-confirmation-{{ delegate.id }}" title="{% trans %}Fermer{% endtrans %}">
{% trans %}Fermer{% endtrans %}
</button>
</div>
<div class="fr-modal__content">
<h1 id="transfert-ownership-confirmation-{{ delegate.id }}-title" class="fr-modal__title">
{% trans meeting_name=meeting.name %}Transférer "{{ meeting_name }}" ?{% endtrans %}
</h1>
<p>{% trans meeting_name=meeting.name, delegate_name=delegate.fullname %}Voulez-vous vraiment transférer la propriété de "{{ meeting_name }}" à {{ delegate_name }} ?{% endtrans %}</p>
<div class="fr-notice fr-notice--info">
<div class="fr-container">
<div class="fr-notice__body">
<p class="fr-notice__title">{% trans %}La confirmation est définitive.{% endtrans %}</p>
</div>
<p>{% trans %}Vous deviendrez délégataire de cette réunion.{% endtrans %}</p>
</div>
</div>
</div>
<div class="fr-modal__footer">
<ul class="fr-btns-group fr-btns-group--inline-reverse fr-btns-group--inline-lg fr-btns-group--right">
<li>
<a class="fr-btn fr-btn--delete" href="{{ url_for("meetings.transfert_meeting_ownership", meeting=meeting, delegate=delegate, admin_mode=admin_mode or None) }}">
{% trans %}Confirmer{% endtrans %}
</a>
</li>
<li>
<button type="button" class="fr-btn fr-btn--secondary" aria-controls="transfert-ownership-confirmation-{{ delegate.id }}" title="{% trans %}Annuler{% endtrans %}">{% trans %}Annuler{% endtrans %}</button>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</dialog>
20 changes: 20 additions & 0 deletions web/b3desk/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,26 @@ def send_delegation_mail(meeting, delegate, new_delegation: bool):
send_email(msg, text, html, smtp)


def send_new_owner_mail(meeting, new_owner, previous_owner):
"""Send email to inform the new meeting owner."""
smtp = make_smtp()
msg = EmailMessage()
body_file = "mail_new_owner_body"
context = {
"meeting": meeting,
"new_owner": new_owner,
"previous_owner": previous_owner,
"welcome_url": url_for("public.welcome", _external=True),
}
text = render_template(f"meeting/mailto/{body_file}.txt", **context)
html = render_template(f"meeting/mailto/{body_file}.html", **context)
msg["Subject"] = str(_(f"Transfert de {meeting.name}"))

@azmeuk azmeuk Sep 1, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pas de f-string dans _(...), il faut utiliser _(...).format(...)

msg["From"] = smtp["from_email"]
msg["To"] = new_owner.email

send_email(msg, text, html, smtp)


def _build_recording_links(playbacks):
"""Return an ordered list of {label, url} entries from a BBB playbacks dict."""
links = []
Expand Down
34 changes: 34 additions & 0 deletions web/tests/meeting/test_transfert_ownership.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from b3desk.models import db
from b3desk.models.meetings import AccessLevel
from b3desk.models.meetings import MeetingAccess


def test_owner_can_transfer_ownership_to_delegate(
client_app, authenticated_user, user_2, meeting, user, smtpd, caplog
):
"""Test owner can transfer ownership to delegate."""
new_access = MeetingAccess(
meeting_id=meeting.id,
user_id=user_2.id,
level=AccessLevel.DELEGATE,
)
db.session.add(new_access)
db.session.commit()
client_app.get("/meeting/transfert-meeting-ownership/1/2", status=302)
assert meeting.owner is user_2
assert user in meeting.get_all_delegates
assert len(smtpd.messages) == 2
assert "Meeting 1 meeting have a new owner : 2 Berenice Cooler" in caplog.text
assert "alice@domain.tld became delegate of meeting 1 meeting" in caplog.text
assert (
"berenice@domain.tld removed from delegates of meeting 1 meeting" in caplog.text
)
assert "Email sent to alice@domain.tld" in caplog.text
assert "Email sent to berenice@domain.tld" in caplog.text


def test_new_owner_is_not_delegate_display_404(
client_app, authenticated_user, meeting, user_2
):
"""Test form displays message if form is empty."""
client_app.get("/meeting/transfert-meeting-ownership/1/2", status=404)
Loading
Loading