(backend) implement recording expiration mechanism

Add expiration system for recordings.

Include option for users to set recordings as permanent (no expiration)
which is the default behavior.

System only calculates expiration dates and tracks status - actual deletion
is handled by Minio bucket lifecycle policies, not by application code.
This commit is contained in:
lebaudantoine
2025-04-23 15:36:29 +02:00
committed by aleb_the_flash
parent af21478143
commit 1a0051a90b
9 changed files with 373 additions and 213 deletions

View File

@@ -40,6 +40,7 @@ def get_frontend_configuration(request):
"recording": {
"is_enabled": settings.RECORDING_ENABLE,
"available_modes": settings.RECORDING_WORKER_CLASSES.keys(),
"expiration_days": settings.RECORDING_EXPIRATION_DAYS,
},
}
frontend_configuration.update(settings.FRONTEND_CONFIGURATION)

View File

@@ -159,7 +159,17 @@ class RecordingSerializer(serializers.ModelSerializer):
class Meta:
model = models.Recording
fields = ["id", "room", "created_at", "updated_at", "status", "mode", "key"]
fields = [
"id",
"room",
"created_at",
"updated_at",
"status",
"mode",
"key",
"is_expired",
"expired_at",
]
read_only_fields = fields

View File

@@ -3,8 +3,9 @@ Declare and configure the models for the Meet core application
"""
import uuid
from datetime import datetime, timedelta
from logging import getLogger
from typing import List
from typing import List, Optional
from django.conf import settings
from django.contrib.auth import models as auth_models
@@ -12,6 +13,7 @@ from django.contrib.auth.base_user import AbstractBaseUser
from django.core import mail, validators
from django.core.exceptions import PermissionDenied, ValidationError
from django.db import models
from django.utils import timezone
from django.utils.functional import lazy
from django.utils.text import capfirst, slugify
from django.utils.translation import gettext_lazy as _
@@ -601,6 +603,34 @@ class Recording(BaseModel):
return f"{settings.RECORDING_OUTPUT_FOLDER}/{self.id}.{self.extension}"
@property
def expired_at(self) -> Optional[datetime]:
"""
Calculate the expiration date based on created_at and RECORDING_EXPIRATION_DAYS.
Returns None if no expiration is configured.
Note: This is a naive and imperfect implementation since recordings are actually
saved to the bucket after created_at timestamp is set. The actual expiration
will be determined by the bucket lifecycle policy which operates on the object's
timestamp in the storage system, not this value.
"""
if not settings.RECORDING_EXPIRATION_DAYS:
return None
return self.created_at + timedelta(days=settings.RECORDING_EXPIRATION_DAYS)
@property
def is_expired(self) -> bool:
"""
Determine if the recording has expired by comparing expired_at with current UTC time.
Returns False if no expiration is configured or if expiration date is in the future.
"""
if not self.expired_at:
return False
return self.expired_at < timezone.now()
class RecordingAccess(BaseAccess):
"""Relation model to give access to a recording for a user or a team with a role."""

View File

@@ -47,11 +47,12 @@ def test_api_recordings_list_authenticated_no_recording():
"role",
["administrator", "member", "owner"],
)
def test_api_recordings_list_authenticated_direct(role):
def test_api_recordings_list_authenticated_direct(role, settings):
"""
Authenticated users listing recordings, should only see the recordings
to which they are related.
"""
settings.RECORDING_EXPIRATIONS_DAYS = None
user = factories.UserFactory()
client = APIClient()
client.force_login(user)
@@ -89,6 +90,8 @@ def test_api_recordings_list_authenticated_direct(role):
},
"status": "initiated",
"updated_at": recording.updated_at.isoformat().replace("+00:00", "Z"),
"expired_at": None,
"is_expired": False,
}

View File

@@ -2,7 +2,10 @@
Test recordings API endpoints in the Meet core app: retrieve.
"""
import random
import pytest
from freezegun import freeze_time
from rest_framework.test import APIClient
from ...factories import RecordingFactory, UserFactory, UserRecordingAccessFactory
@@ -61,8 +64,9 @@ def test_api_recording_retrieve_members():
}
def test_api_recording_retrieve_administrators():
def test_api_recording_retrieve_administrators(settings):
"""A user who is an administrator of a recording should be able to retrieve it."""
settings.RECORDING_EXPIRATION_DAYS = None
user = UserFactory()
recording = RecordingFactory()
@@ -91,11 +95,14 @@ def test_api_recording_retrieve_administrators():
"updated_at": recording.updated_at.isoformat().replace("+00:00", "Z"),
"status": str(recording.status),
"mode": str(recording.mode),
"expired_at": None,
"is_expired": False,
}
def test_api_recording_retrieve_owners():
def test_api_recording_retrieve_owners(settings):
"""A user who is an owner of a recording should be able to retrieve it."""
settings.RECORDING_EXPIRATION_DAYS = None
user = UserFactory()
recording = RecordingFactory()
@@ -123,6 +130,87 @@ def test_api_recording_retrieve_owners():
"updated_at": recording.updated_at.isoformat().replace("+00:00", "Z"),
"status": str(recording.status),
"mode": str(recording.mode),
"expired_at": None,
"is_expired": False,
}
@freeze_time("2023-01-15 12:00:00")
def test_api_recording_retrieve_compute_expiration_date_correctly(settings):
"""Test that the API returns the correct expiration date for a non-expired recording."""
settings.RECORDING_EXPIRATION_DAYS = 1
user = UserFactory()
recording = RecordingFactory()
UserRecordingAccessFactory(
recording=recording, user=user, role=random.choice(["administrator", "owner"])
)
client = APIClient()
client.force_login(user)
response = client.get(f"/api/v1.0/recordings/{recording.id!s}/")
assert response.status_code == 200
content = response.json()
room = recording.room
assert content == {
"id": str(recording.id),
"key": recording.key,
"room": {
"access_level": str(room.access_level),
"id": str(room.id),
"name": room.name,
"slug": room.slug,
},
"created_at": "2023-01-15T12:00:00Z",
"updated_at": "2023-01-15T12:00:00Z",
"status": str(recording.status),
"mode": str(recording.mode),
"expired_at": "2023-01-16T12:00:00Z",
"is_expired": False, # Ensure the recording is still valid and hasn't expired
}
def test_api_recording_retrieve_expired(settings):
"""Test that the API returns the correct expiration date and flag for an expired recording."""
settings.RECORDING_EXPIRATION_DAYS = 2
user = UserFactory()
with freeze_time("2023-01-15 12:00:00"):
recording = RecordingFactory()
UserRecordingAccessFactory(
recording=recording, user=user, role=random.choice(["administrator", "owner"])
)
client = APIClient()
client.force_login(user)
response = client.get(f"/api/v1.0/recordings/{recording.id!s}/")
assert response.status_code == 200
content = response.json()
room = recording.room
assert content == {
"id": str(recording.id),
"key": recording.key,
"room": {
"access_level": str(room.access_level),
"id": str(room.id),
"name": room.name,
"slug": room.slug,
},
"created_at": "2023-01-15T12:00:00Z",
"updated_at": "2023-01-15T12:00:00Z",
"status": str(recording.status),
"mode": str(recording.mode),
"expired_at": "2023-01-17T12:00:00Z",
"is_expired": True, # Ensure the recording has expired
}

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-04-22 15:15+0000\n"
"POT-Creation-Date: 2025-04-23 13:47+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -29,7 +29,7 @@ msgstr "Permissions"
msgid "Important dates"
msgstr "Important dates"
#: core/api/serializers.py:60
#: core/api/serializers.py:63
msgid "You must be administrator or owner of a room to add accesses to it."
msgstr "You must be administrator or owner of a room to add accesses to it."
@@ -45,106 +45,107 @@ msgstr "User account is disabled"
msgid "Multiple user accounts share a common email."
msgstr "Multiple user accounts share a common email."
#: core/models.py:29
#: core/models.py:30
msgid "Member"
msgstr "Member"
#: core/models.py:30
#: core/models.py:31
msgid "Administrator"
msgstr "Administrator"
#: core/models.py:31
#: core/models.py:32
msgid "Owner"
msgstr "Owner"
#: core/models.py:47
#: core/models.py:48
msgid "Initiated"
msgstr "Initiated"
#: core/models.py:48
#: core/models.py:49
msgid "Active"
msgstr "Active"
#: core/models.py:49
#: core/models.py:50
msgid "Stopped"
msgstr "Stopped"
#: core/models.py:50
#: core/models.py:51
msgid "Saved"
msgstr "Saved"
#: core/models.py:51
#: core/models.py:52
msgid "Aborted"
msgstr "Aborted"
#: core/models.py:52
#: core/models.py:53
msgid "Failed to Start"
msgstr "Failed to Start"
#: core/models.py:53
#: core/models.py:54
msgid "Failed to Stop"
msgstr "Failed to Stop"
#: core/models.py:54
#: core/models.py:55
msgid "Notification succeeded"
msgstr "Notification succeeded"
#: core/models.py:81
#: core/models.py:82
msgid "SCREEN_RECORDING"
msgstr "SCREEN_RECORDING"
#: core/models.py:82
#: core/models.py:83
msgid "TRANSCRIPT"
msgstr "TRANSCRIPT"
#: core/models.py:88
#: core/models.py:89
msgid "Public Access"
msgstr "Public Access"
#: core/models.py:89
#: core/models.py:90
msgid "Trusted Access"
msgstr "Trusted Access"
#: core/models.py:90
#: core/models.py:91
msgid "Restricted Access"
msgstr "Restricted Access"
#: core/models.py:102
#: core/models.py:103
msgid "id"
msgstr "id"
#: core/models.py:103
#: core/models.py:104
msgid "primary key for the record as UUID"
msgstr "primary key for the record as UUID"
#: core/models.py:109
#: core/models.py:110
msgid "created on"
msgstr "created on"
#: core/models.py:110
#: core/models.py:111
msgid "date and time at which a record was created"
msgstr "date and time at which a record was created"
#: core/models.py:115
#: core/models.py:116
msgid "updated on"
msgstr "updated on"
#: core/models.py:116
#: core/models.py:117
msgid "date and time at which a record was last updated"
msgstr "date and time at which a record was last updated"
#: core/models.py:136
#: core/models.py:137
msgid ""
"Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/"
"_ characters."
msgstr "Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/"
msgstr ""
"Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/"
"_ characters."
#: core/models.py:142
#: core/models.py:143
msgid "sub"
msgstr "sub"
#: core/models.py:144
#: core/models.py:145
msgid ""
"Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_ "
"characters only."
@@ -152,55 +153,55 @@ msgstr ""
"Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_ "
"characters only."
#: core/models.py:152
#: core/models.py:153
msgid "identity email address"
msgstr "identity email address"
#: core/models.py:157
#: core/models.py:158
msgid "admin email address"
msgstr "admin email address"
#: core/models.py:159
#: core/models.py:160
msgid "full name"
msgstr "full name"
#: core/models.py:161
#: core/models.py:162
msgid "short name"
msgstr "short name"
#: core/models.py:167
#: core/models.py:168
msgid "language"
msgstr "language"
#: core/models.py:168
#: core/models.py:169
msgid "The language in which the user wants to see the interface."
msgstr "The language in which the user wants to see the interface."
#: core/models.py:174
#: core/models.py:175
msgid "The timezone in which the user wants to see times."
msgstr "The timezone in which the user wants to see times."
#: core/models.py:177
#: core/models.py:178
msgid "device"
msgstr "device"
#: core/models.py:179
#: core/models.py:180
msgid "Whether the user is a device or a real user."
msgstr "Whether the user is a device or a real user."
#: core/models.py:182
#: core/models.py:183
msgid "staff status"
msgstr "staff status"
#: core/models.py:184
#: core/models.py:185
msgid "Whether the user can log into this admin site."
msgstr "Whether the user can log into this admin site."
#: core/models.py:187
#: core/models.py:188
msgid "active"
msgstr "active"
#: core/models.py:190
#: core/models.py:191
msgid ""
"Whether this user should be treated as active. Unselect this instead of "
"deleting accounts."
@@ -208,55 +209,55 @@ msgstr ""
"Whether this user should be treated as active. Unselect this instead of "
"deleting accounts."
#: core/models.py:203
#: core/models.py:204
msgid "user"
msgstr "user"
#: core/models.py:204
#: core/models.py:205
msgid "users"
msgstr "users"
#: core/models.py:263
#: core/models.py:264
msgid "Resource"
msgstr "Resource"
#: core/models.py:264
#: core/models.py:265
msgid "Resources"
msgstr "Resources"
#: core/models.py:318
#: core/models.py:319
msgid "Resource access"
msgstr "Resource access"
#: core/models.py:319
#: core/models.py:320
msgid "Resource accesses"
msgstr "Resource accesses"
#: core/models.py:325
#: core/models.py:326
msgid "Resource access with this User and Resource already exists."
msgstr "Resource access with this User and Resource already exists."
#: core/models.py:381
#: core/models.py:382
msgid "Visio room configuration"
msgstr "Visio room configuration"
#: core/models.py:382
#: core/models.py:383
msgid "Values for Visio parameters to configure the room."
msgstr "Values for Visio parameters to configure the room."
#: core/models.py:388 core/models.py:508
#: core/models.py:389 core/models.py:509
msgid "Room"
msgstr "Room"
#: core/models.py:389
#: core/models.py:390
msgid "Rooms"
msgstr "Rooms"
#: core/models.py:519
#: core/models.py:520
msgid "Worker ID"
msgstr "Worker ID"
#: core/models.py:521
#: core/models.py:522
msgid ""
"Enter an identifier for the worker recording.This ID is retained even when "
"the worker stops, allowing for easy tracking."
@@ -264,39 +265,39 @@ msgstr ""
"Enter an identifier for the worker recording.This ID is retained even when "
"the worker stops, allowing for easy tracking."
#: core/models.py:529
#: core/models.py:530
msgid "Recording mode"
msgstr "Recording mode"
#: core/models.py:530
#: core/models.py:531
msgid "Defines the mode of recording being called."
msgstr "Defines the mode of recording being called."
#: core/models.py:536
#: core/models.py:537
msgid "Recording"
msgstr "Recording"
#: core/models.py:537
#: core/models.py:538
msgid "Recordings"
msgstr "Recordings"
#: core/models.py:617
#: core/models.py:646
msgid "Recording/user relation"
msgstr "Recording/user relation"
#: core/models.py:618
#: core/models.py:647
msgid "Recording/user relations"
msgstr "Recording/user relations"
#: core/models.py:624
#: core/models.py:653
msgid "This user is already in this recording."
msgstr "This user is already in this recording."
#: core/models.py:630
#: core/models.py:659
msgid "This team is already in this recording."
msgstr "This team is already in this recording."
#: core/models.py:636
#: core/models.py:665
msgid "Either user or team must be set, not both."
msgstr "Either user or team must be set, not both."
@@ -347,7 +348,8 @@ msgstr "JOIN THE CALL"
msgid ""
"If you can't click the button, copy and paste the URL into your browser to "
"join the call."
msgstr "If you can't click the button, copy and paste the URL into your browser to "
msgstr ""
"If you can't click the button, copy and paste the URL into your browser to "
"join the call."
#: core/templates/mail/html/invitation.html:235
@@ -423,7 +425,8 @@ msgstr "Open"
msgid ""
" If you have any questions or need assistance, please contact our support "
"team at %(support_email)s. "
msgstr " If you have any questions or need assistance, please contact our support "
msgstr ""
" If you have any questions or need assistance, please contact our support "
"team at %(support_email)s. "
#: meet/settings.py:162

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-04-22 15:15+0000\n"
"POT-Creation-Date: 2025-04-23 13:47+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: antoine.lebaud@mail.numerique.gouv.fr\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -29,13 +29,17 @@ msgstr "Permissions"
msgid "Important dates"
msgstr "Dates importantes"
#: core/api/serializers.py:60
#: core/api/serializers.py:63
msgid "You must be administrator or owner of a room to add accesses to it."
msgstr "Vous devez être administrateur ou propriétaire d'une salle pour y ajouter des accès."
msgstr ""
"Vous devez être administrateur ou propriétaire d'une salle pour y ajouter "
"des accès."
#: core/authentication/backends.py:77
msgid "User info contained no recognizable user identification"
msgstr "Les informations utilisateur ne contiennent aucune identification utilisateur reconnaissable"
msgstr ""
"Les informations utilisateur ne contiennent aucune identification "
"utilisateur reconnaissable"
#: core/authentication/backends.py:102
msgid "User account is disabled"
@@ -45,251 +49,263 @@ msgstr "Le compte utilisateur est désactivé"
msgid "Multiple user accounts share a common email."
msgstr "Plusieurs comptes utilisateur partagent une adresse e-mail commune."
#: core/models.py:29
#: core/models.py:30
msgid "Member"
msgstr "Membre"
#: core/models.py:30
#: core/models.py:31
msgid "Administrator"
msgstr "Administrateur"
#: core/models.py:31
#: core/models.py:32
msgid "Owner"
msgstr "Propriétaire"
#: core/models.py:47
#: core/models.py:48
msgid "Initiated"
msgstr "Initié"
#: core/models.py:48
#: core/models.py:49
msgid "Active"
msgstr "Actif"
#: core/models.py:49
#: core/models.py:50
msgid "Stopped"
msgstr "Arrêté"
#: core/models.py:50
#: core/models.py:51
msgid "Saved"
msgstr "Enregistré"
#: core/models.py:51
#: core/models.py:52
msgid "Aborted"
msgstr "Abandonné"
#: core/models.py:52
#: core/models.py:53
msgid "Failed to Start"
msgstr "Échec au démarrage"
#: core/models.py:53
#: core/models.py:54
msgid "Failed to Stop"
msgstr "Échec à l'arrêt"
#: core/models.py:54
#: core/models.py:55
msgid "Notification succeeded"
msgstr "Notification réussie"
#: core/models.py:81
#: core/models.py:82
msgid "SCREEN_RECORDING"
msgstr "ENREGISTREMENT_ÉCRAN"
#: core/models.py:82
#: core/models.py:83
msgid "TRANSCRIPT"
msgstr "TRANSCRIPTION"
#: core/models.py:88
#: core/models.py:89
msgid "Public Access"
msgstr "Accès public"
#: core/models.py:89
#: core/models.py:90
msgid "Trusted Access"
msgstr "Accès de confiance"
#: core/models.py:90
#: core/models.py:91
msgid "Restricted Access"
msgstr "Accès restreint"
#: core/models.py:102
#: core/models.py:103
msgid "id"
msgstr "id"
#: core/models.py:103
#: core/models.py:104
msgid "primary key for the record as UUID"
msgstr "clé primaire pour l'enregistrement sous forme d'UUID"
#: core/models.py:109
#: core/models.py:110
msgid "created on"
msgstr "créé le"
#: core/models.py:110
#: core/models.py:111
msgid "date and time at which a record was created"
msgstr "date et heure auxquelles un enregistrement a été créé"
#: core/models.py:115
#: core/models.py:116
msgid "updated on"
msgstr "mis à jour le"
#: core/models.py:116
#: core/models.py:117
msgid "date and time at which a record was last updated"
msgstr "date et heure auxquelles un enregistrement a été mis à jour pour la dernière fois"
msgstr ""
"date et heure auxquelles un enregistrement a été mis à jour pour la dernière "
"fois"
#: core/models.py:136
#: core/models.py:137
msgid ""
"Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/"
"_ characters."
msgstr "Entrez un identifiant valide. Cette valeur ne peut contenir que des lettres, des chiffres et les caractères @/./+/-/_."
msgstr ""
"Entrez un identifiant valide. Cette valeur ne peut contenir que des lettres, "
"des chiffres et les caractères @/./+/-/_."
#: core/models.py:142
#: core/models.py:143
msgid "sub"
msgstr "identifiant"
#: core/models.py:144
#: core/models.py:145
msgid ""
"Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_ "
"characters only."
msgstr "Obligatoire. 255 caractères ou moins. Lettres, chiffres et caractères @/./+/-/_ uniquement."
msgstr ""
"Obligatoire. 255 caractères ou moins. Lettres, chiffres et caractères @/./"
"+/-/_ uniquement."
#: core/models.py:152
#: core/models.py:153
msgid "identity email address"
msgstr "adresse e-mail d'identité"
#: core/models.py:157
#: core/models.py:158
msgid "admin email address"
msgstr "adresse e-mail d'administrateur"
#: core/models.py:159
#: core/models.py:160
msgid "full name"
msgstr "nom complet"
#: core/models.py:161
#: core/models.py:162
msgid "short name"
msgstr "nom court"
#: core/models.py:167
#: core/models.py:168
msgid "language"
msgstr "langue"
#: core/models.py:168
#: core/models.py:169
msgid "The language in which the user wants to see the interface."
msgstr "La langue dans laquelle l'utilisateur souhaite voir l'interface."
#: core/models.py:174
#: core/models.py:175
msgid "The timezone in which the user wants to see times."
msgstr "Le fuseau horaire dans lequel l'utilisateur souhaite voir les heures."
#: core/models.py:177
#: core/models.py:178
msgid "device"
msgstr "appareil"
#: core/models.py:179
#: core/models.py:180
msgid "Whether the user is a device or a real user."
msgstr "Si l'utilisateur est un appareil ou un utilisateur réel."
#: core/models.py:182
#: core/models.py:183
msgid "staff status"
msgstr "statut du personnel"
#: core/models.py:184
#: core/models.py:185
msgid "Whether the user can log into this admin site."
msgstr "Si l'utilisateur peut se connecter à ce site d'administration."
#: core/models.py:187
#: core/models.py:188
msgid "active"
msgstr "actif"
#: core/models.py:190
#: core/models.py:191
msgid ""
"Whether this user should be treated as active. Unselect this instead of "
"deleting accounts."
msgstr "Si cet utilisateur doit être traité comme actif. Désélectionnez cette option au lieu de supprimer des comptes."
msgstr ""
"Si cet utilisateur doit être traité comme actif. Désélectionnez cette option "
"au lieu de supprimer des comptes."
#: core/models.py:203
#: core/models.py:204
msgid "user"
msgstr "utilisateur"
#: core/models.py:204
#: core/models.py:205
msgid "users"
msgstr "utilisateurs"
#: core/models.py:263
#: core/models.py:264
msgid "Resource"
msgstr "Ressource"
#: core/models.py:264
#: core/models.py:265
msgid "Resources"
msgstr "Ressources"
#: core/models.py:318
#: core/models.py:319
msgid "Resource access"
msgstr "Accès aux ressources"
#: core/models.py:319
#: core/models.py:320
msgid "Resource accesses"
msgstr "Accès aux ressources"
#: core/models.py:325
#: core/models.py:326
msgid "Resource access with this User and Resource already exists."
msgstr "L'accès à la ressource avec cet utilisateur et cette ressource existe déjà."
msgstr ""
"L'accès à la ressource avec cet utilisateur et cette ressource existe déjà."
#: core/models.py:381
#: core/models.py:382
msgid "Visio room configuration"
msgstr "Configuration de la salle de visioconférence"
#: core/models.py:382
#: core/models.py:383
msgid "Values for Visio parameters to configure the room."
msgstr "Valeurs des paramètres de visioconférence pour configurer la salle."
#: core/models.py:388 core/models.py:508
#: core/models.py:389 core/models.py:509
msgid "Room"
msgstr "Salle"
#: core/models.py:389
#: core/models.py:390
msgid "Rooms"
msgstr "Salles"
#: core/models.py:519
#: core/models.py:520
msgid "Worker ID"
msgstr "ID du Worker"
#: core/models.py:521
#: core/models.py:522
msgid ""
"Enter an identifier for the worker recording.This ID is retained even when "
"the worker stops, allowing for easy tracking."
msgstr "Entrez un identifiant pour l'enregistrement du travailleur. Cet identifiant est conservé même lorsque le travailleur s'arrête, permettant un suivi facile."
msgstr ""
"Entrez un identifiant pour l'enregistrement du travailleur. Cet identifiant "
"est conservé même lorsque le travailleur s'arrête, permettant un suivi "
"facile."
#: core/models.py:529
#: core/models.py:530
msgid "Recording mode"
msgstr "Mode d'enregistrement"
#: core/models.py:530
#: core/models.py:531
msgid "Defines the mode of recording being called."
msgstr "Définit le mode d'enregistrement appelé."
#: core/models.py:536
#: core/models.py:537
msgid "Recording"
msgstr "Enregistrement"
#: core/models.py:537
#: core/models.py:538
msgid "Recordings"
msgstr "Enregistrements"
#: core/models.py:617
#: core/models.py:646
msgid "Recording/user relation"
msgstr "Relation enregistrement/utilisateur"
#: core/models.py:618
#: core/models.py:647
msgid "Recording/user relations"
msgstr "Relations enregistrement/utilisateur"
#: core/models.py:624
#: core/models.py:653
msgid "This user is already in this recording."
msgstr "Cet utilisateur est déjà dans cet enregistrement."
#: core/models.py:630
#: core/models.py:659
msgid "This team is already in this recording."
msgstr "Cette équipe est déjà dans cet enregistrement."
#: core/models.py:636
#: core/models.py:665
msgid "Either user or team must be set, not both."
msgstr "Soit l'utilisateur, soit l'équipe doit être défini, pas les deux."
@@ -340,7 +356,9 @@ msgstr "REJOINDRE L'APPEL"
msgid ""
"If you can't click the button, copy and paste the URL into your browser to "
"join the call."
msgstr "Si vous ne pouvez pas cliquer sur le bouton, copiez et collez l'URL dans votre navigateur pour rejoindre l'appel."
msgstr ""
"Si vous ne pouvez pas cliquer sur le bouton, copiez et collez l'URL dans "
"votre navigateur pour rejoindre l'appel."
#: core/templates/mail/html/invitation.html:235
#: core/templates/mail/text/invitation.txt:15
@@ -416,7 +434,9 @@ msgstr "Ouvrir"
msgid ""
" If you have any questions or need assistance, please contact our support "
"team at %(support_email)s. "
msgstr " Si vous avez des questions ou besoin d'assistance, veuillez contacter notre équipe d'assistance à %(support_email)s. "
msgstr ""
" Si vous avez des questions ou besoin d'assistance, veuillez contacter notre "
"équipe d'assistance à %(support_email)s. "
#: meet/settings.py:162
msgid "English"

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-04-22 15:15+0000\n"
"POT-Creation-Date: 2025-04-23 13:47+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -29,9 +29,10 @@ msgstr "Rechten"
msgid "Important dates"
msgstr "Belangrijke datums"
#: core/api/serializers.py:60
#: core/api/serializers.py:63
msgid "You must be administrator or owner of a room to add accesses to it."
msgstr "Je moet beheerder of eigenaar van een ruimte zijn om toegang toe te voegen."
msgstr ""
"Je moet beheerder of eigenaar van een ruimte zijn om toegang toe te voegen."
#: core/authentication/backends.py:77
msgid "User info contained no recognizable user identification"
@@ -45,95 +46,95 @@ msgstr "Gebruikersaccount is uitgeschakeld"
msgid "Multiple user accounts share a common email."
msgstr "Meerdere gebruikersaccounts delen een gemeenschappelijk e-mailadres."
#: core/models.py:29
#: core/models.py:30
msgid "Member"
msgstr "Lid"
#: core/models.py:30
#: core/models.py:31
msgid "Administrator"
msgstr "Beheerder"
#: core/models.py:31
#: core/models.py:32
msgid "Owner"
msgstr "Eigenaar"
#: core/models.py:47
#: core/models.py:48
msgid "Initiated"
msgstr "Gestart"
#: core/models.py:48
#: core/models.py:49
msgid "Active"
msgstr "Actief"
#: core/models.py:49
#: core/models.py:50
msgid "Stopped"
msgstr "Gestopt"
#: core/models.py:50
#: core/models.py:51
msgid "Saved"
msgstr "Opgeslagen"
#: core/models.py:51
#: core/models.py:52
msgid "Aborted"
msgstr "Afgebroken"
#: core/models.py:52
#: core/models.py:53
msgid "Failed to Start"
msgstr "Starten mislukt"
#: core/models.py:53
#: core/models.py:54
msgid "Failed to Stop"
msgstr "Stoppen mislukt"
#: core/models.py:54
#: core/models.py:55
msgid "Notification succeeded"
msgstr "Notificatie geslaagd"
#: core/models.py:81
#: core/models.py:82
msgid "SCREEN_RECORDING"
msgstr "SCHERM_OPNAME"
#: core/models.py:82
#: core/models.py:83
msgid "TRANSCRIPT"
msgstr "TRANSCRIPT"
#: core/models.py:88
#: core/models.py:89
msgid "Public Access"
msgstr "Openbare toegang"
#: core/models.py:89
#: core/models.py:90
msgid "Trusted Access"
msgstr "Vertrouwde toegang"
#: core/models.py:90
#: core/models.py:91
msgid "Restricted Access"
msgstr "Beperkte toegang"
#: core/models.py:102
#: core/models.py:103
msgid "id"
msgstr "id"
#: core/models.py:103
#: core/models.py:104
msgid "primary key for the record as UUID"
msgstr "primaire sleutel voor het record als UUID"
#: core/models.py:109
#: core/models.py:110
msgid "created on"
msgstr "aangemaakt op"
#: core/models.py:110
#: core/models.py:111
msgid "date and time at which a record was created"
msgstr "datum en tijd waarop een record werd aangemaakt"
#: core/models.py:115
#: core/models.py:116
msgid "updated on"
msgstr "bijgewerkt op"
#: core/models.py:116
#: core/models.py:117
msgid "date and time at which a record was last updated"
msgstr "datum en tijd waarop een record voor het laatst werd bijgewerkt"
#: core/models.py:136
#: core/models.py:137
msgid ""
"Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/"
"_ characters."
@@ -141,163 +142,162 @@ msgstr ""
"Voer een geldige sub in. Deze waarde mag alleen letters, cijfers en @/./+/-/"
"_ tekens bevatten."
#: core/models.py:142
#: core/models.py:143
msgid "sub"
msgstr "sub"
#: core/models.py:144
#: core/models.py:145
msgid ""
"Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_ "
"characters only."
msgstr ""
"Vereist. 255 tekens of minder. Alleen letters, cijfers en @/./+/-/_ "
"tekens."
"Vereist. 255 tekens of minder. Alleen letters, cijfers en @/./+/-/_ tekens."
#: core/models.py:152
#: core/models.py:153
msgid "identity email address"
msgstr "identiteit e-mailadres"
#: core/models.py:157
#: core/models.py:158
msgid "admin email address"
msgstr "beheerder e-mailadres"
#: core/models.py:159
#: core/models.py:160
msgid "full name"
msgstr "volledige naam"
#: core/models.py:161
#: core/models.py:162
msgid "short name"
msgstr "korte naam"
#: core/models.py:167
#: core/models.py:168
msgid "language"
msgstr "taal"
#: core/models.py:168
#: core/models.py:169
msgid "The language in which the user wants to see the interface."
msgstr "De taal waarin de gebruiker de interface wil zien."
#: core/models.py:174
#: core/models.py:175
msgid "The timezone in which the user wants to see times."
msgstr "De tijdzone waarin de gebruiker tijden wil zien."
#: core/models.py:177
#: core/models.py:178
msgid "device"
msgstr "apparaat"
#: core/models.py:179
#: core/models.py:180
msgid "Whether the user is a device or a real user."
msgstr "Of de gebruiker een apparaat is of een echte gebruiker."
#: core/models.py:182
#: core/models.py:183
msgid "staff status"
msgstr "personeelsstatus"
#: core/models.py:184
#: core/models.py:185
msgid "Whether the user can log into this admin site."
msgstr "Of de gebruiker kan inloggen op deze beheersite."
#: core/models.py:187
#: core/models.py:188
msgid "active"
msgstr "actief"
#: core/models.py:190
#: core/models.py:191
msgid ""
"Whether this user should be treated as active. Unselect this instead of "
"deleting accounts."
msgstr ""
"Of deze gebruiker als actief moet worden behandeld. Deselecteer dit in plaats van "
"accounts te verwijderen."
"Of deze gebruiker als actief moet worden behandeld. Deselecteer dit in "
"plaats van accounts te verwijderen."
#: core/models.py:203
#: core/models.py:204
msgid "user"
msgstr "gebruiker"
#: core/models.py:204
#: core/models.py:205
msgid "users"
msgstr "gebruikers"
#: core/models.py:263
#: core/models.py:264
msgid "Resource"
msgstr "Bron"
#: core/models.py:264
#: core/models.py:265
msgid "Resources"
msgstr "Bronnen"
#: core/models.py:318
#: core/models.py:319
msgid "Resource access"
msgstr "Brontoegang"
#: core/models.py:319
#: core/models.py:320
msgid "Resource accesses"
msgstr "Brontoegangsrechten"
#: core/models.py:325
#: core/models.py:326
msgid "Resource access with this User and Resource already exists."
msgstr "Brontoegang met deze gebruiker en bron bestaat al."
#: core/models.py:381
#: core/models.py:382
msgid "Visio room configuration"
msgstr "Visio-ruimteconfiguratie"
#: core/models.py:382
#: core/models.py:383
msgid "Values for Visio parameters to configure the room."
msgstr "Waarden voor Visio-parameters om de ruimte te configureren."
#: core/models.py:388 core/models.py:508
#: core/models.py:389 core/models.py:509
msgid "Room"
msgstr "Ruimte"
#: core/models.py:389
#: core/models.py:390
msgid "Rooms"
msgstr "Ruimtes"
#: core/models.py:519
#: core/models.py:520
msgid "Worker ID"
msgstr "Worker ID"
#: core/models.py:521
#: core/models.py:522
msgid ""
"Enter an identifier for the worker recording.This ID is retained even when "
"the worker stops, allowing for easy tracking."
msgstr ""
"Voer een identificatie in voor de worker-opname. Deze ID blijft behouden, zelfs wanneer "
"de worker stopt, waardoor eenvoudige tracking mogelijk is."
"Voer een identificatie in voor de worker-opname. Deze ID blijft behouden, "
"zelfs wanneer de worker stopt, waardoor eenvoudige tracking mogelijk is."
#: core/models.py:529
#: core/models.py:530
msgid "Recording mode"
msgstr "Opnamemodus"
#: core/models.py:530
#: core/models.py:531
msgid "Defines the mode of recording being called."
msgstr "Definieert de modus van opname die wordt aangeroepen."
#: core/models.py:536
#: core/models.py:537
msgid "Recording"
msgstr "Opname"
#: core/models.py:537
#: core/models.py:538
msgid "Recordings"
msgstr "Opnames"
#: core/models.py:617
#: core/models.py:646
msgid "Recording/user relation"
msgstr "Opname/gebruiker-relatie"
#: core/models.py:618
#: core/models.py:647
msgid "Recording/user relations"
msgstr "Opname/gebruiker-relaties"
#: core/models.py:624
#: core/models.py:653
msgid "This user is already in this recording."
msgstr "Deze gebruiker is al in deze opname."
#: core/models.py:630
#: core/models.py:659
msgid "This team is already in this recording."
msgstr "Dit team is al in deze opname."
#: core/models.py:636
#: core/models.py:665
msgid "Either user or team must be set, not both."
msgstr "Ofwel gebruiker of team moet worden ingesteld, niet beide."
@@ -349,8 +349,8 @@ msgid ""
"If you can't click the button, copy and paste the URL into your browser to "
"join the call."
msgstr ""
"Als je niet op de knop kunt klikken, kopieer en plak dan de URL in je browser om "
"deel te nemen aan de oproep."
"Als je niet op de knop kunt klikken, kopieer en plak dan de URL in je "
"browser om deel te nemen aan de oproep."
#: core/templates/mail/html/invitation.html:235
#: core/templates/mail/text/invitation.txt:15

View File

@@ -485,6 +485,11 @@ class Base(Configuration):
RECORDING_STORAGE_EVENT_TOKEN = values.Value(
None, environ_name="RECORDING_STORAGE_EVENT_TOKEN", environ_prefix=None
)
# Number of days before recordings expire - must be synced with bucket lifecycle policy
# Set to None for no expiration
RECORDING_EXPIRATION_DAYS = values.IntegerValue(
None, environ_name="RECORDING_EXPIRATION_DAYS", environ_prefix=None
)
SUMMARY_SERVICE_ENDPOINT = values.Value(
None, environ_name="SUMMARY_SERVICE_ENDPOINT", environ_prefix=None
)