export-all-verif
This commit is contained in:
@@ -20,6 +20,7 @@ EXPORTS_LOCAL_PATH=/home/rpi4/exports
|
|||||||
EXPORT_REMOTE_PATH=/mnt/exports
|
EXPORT_REMOTE_PATH=/mnt/exports
|
||||||
REDUCTSTORE_PATH=/home/rpi4/medias
|
REDUCTSTORE_PATH=/home/rpi4/medias
|
||||||
CSV_EXPORT_DIR=/home/rpi4/exports/csv
|
CSV_EXPORT_DIR=/home/rpi4/exports/csv
|
||||||
|
BACKUP_DIR=/home/rpi4/backup/mariadb
|
||||||
|
|
||||||
####
|
####
|
||||||
# django email
|
# django email
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
'''
|
||||||
|
Created on 18 mai 2026
|
||||||
|
|
||||||
|
@author: denis
|
||||||
|
'''
|
||||||
|
from pathlib import Path
|
||||||
|
from datetime import datetime
|
||||||
|
import subprocess
|
||||||
|
import gzip
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
BACKUP_DIR = Path(settings.BACKUP_DIR)
|
||||||
|
|
||||||
|
|
||||||
|
def mariadb_backup():
|
||||||
|
BACKUP_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
db = settings.DATABASES["default"]
|
||||||
|
|
||||||
|
db_name = db["NAME"]
|
||||||
|
db_user = db["USER"]
|
||||||
|
db_password = db["PASSWORD"]
|
||||||
|
db_host = db.get("HOST", "localhost")
|
||||||
|
db_port = str(db.get("PORT", 3306))
|
||||||
|
|
||||||
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||||
|
|
||||||
|
sql_file = BACKUP_DIR / f"{db_name}_{timestamp}.sql"
|
||||||
|
gz_file = BACKUP_DIR / f"{db_name}_{timestamp}.sql.gz"
|
||||||
|
|
||||||
|
cmd = [
|
||||||
|
"mariadb-dump",
|
||||||
|
"--single-transaction",
|
||||||
|
"--quick",
|
||||||
|
"--routines",
|
||||||
|
"--triggers",
|
||||||
|
"-h", db_host,
|
||||||
|
"-P", db_port,
|
||||||
|
"-u", db_user,
|
||||||
|
f"-p{db_password}",
|
||||||
|
db_name,
|
||||||
|
]
|
||||||
|
|
||||||
|
with open(sql_file, "wb") as f:
|
||||||
|
result = subprocess.run(
|
||||||
|
cmd,
|
||||||
|
stdout=f,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
check=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
with open(sql_file, "rb") as f_in:
|
||||||
|
with gzip.open(gz_file, "wb") as f_out:
|
||||||
|
shutil.copyfileobj(f_in, f_out)
|
||||||
|
|
||||||
|
sql_file.unlink()
|
||||||
|
|
||||||
|
return str(gz_file)
|
||||||
|
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
'''
|
||||||
|
Created on 18 mai 2026
|
||||||
|
|
||||||
|
@author: denis
|
||||||
|
'''
|
||||||
|
from celery import shared_task
|
||||||
|
|
||||||
|
from .services import mariadb_backup
|
||||||
|
|
||||||
|
|
||||||
|
@shared_task
|
||||||
|
def backup_mariadb_task():
|
||||||
|
path = mariadb_backup()
|
||||||
|
|
||||||
|
return {
|
||||||
|
"status": "ok",
|
||||||
|
"backup": path,
|
||||||
|
}
|
||||||
@@ -8,3 +8,6 @@ os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'home.settings')
|
|||||||
appc = Celery('home')
|
appc = Celery('home')
|
||||||
appc.config_from_object('django.conf:settings', namespace='CELERY')
|
appc.config_from_object('django.conf:settings', namespace='CELERY')
|
||||||
appc.autodiscover_tasks()
|
appc.autodiscover_tasks()
|
||||||
|
|
||||||
|
worker_prefetch_multiplier = 1
|
||||||
|
task_acks_late = True
|
||||||
|
|||||||
@@ -345,6 +345,7 @@ DJANGO_CELERY_BEAT_TZ_AWARE = False
|
|||||||
CELERY_ENABLE_UTC = True
|
CELERY_ENABLE_UTC = True
|
||||||
CELERY_TASK_TRACK_STARTED = True
|
CELERY_TASK_TRACK_STARTED = True
|
||||||
CELERY_TASK_TIME_LIMIT = 30 * 60
|
CELERY_TASK_TIME_LIMIT = 30 * 60
|
||||||
|
CELERY_TASK_SOFT_TIME_LIMIT = 20 * 60
|
||||||
CELERY_BEAT_SCHEDULER = 'django_celery_beat.schedulers:DatabaseScheduler'
|
CELERY_BEAT_SCHEDULER = 'django_celery_beat.schedulers:DatabaseScheduler'
|
||||||
CELERY_ACCEPT_CONTENT = ['application/json']
|
CELERY_ACCEPT_CONTENT = ['application/json']
|
||||||
CELERY_RESULT_SERIALIZER = 'json'
|
CELERY_RESULT_SERIALIZER = 'json'
|
||||||
@@ -362,6 +363,8 @@ REDUCTSTORE_PORT = config('REDUCTSTORE_PORT', cast=int)
|
|||||||
REDUCTSTORE_URL = f'http://{REDUCTSTORE_HOST}:{REDUCTSTORE_PORT}'
|
REDUCTSTORE_URL = f'http://{REDUCTSTORE_HOST}:{REDUCTSTORE_PORT}'
|
||||||
REDUCTSTORE_PATH = config("REDUCTSTORE_PATH")
|
REDUCTSTORE_PATH = config("REDUCTSTORE_PATH")
|
||||||
|
|
||||||
|
BACKUP_DIR = config('BACKUP_DIR')
|
||||||
|
|
||||||
## servers app
|
## servers app
|
||||||
#
|
#
|
||||||
USER = config('USER')
|
USER = config('USER')
|
||||||
|
|||||||
@@ -687,16 +687,8 @@ class ReductStoreClient:
|
|||||||
logger.info(f"ReductStore connecté : {self.url} / {self.bucket_name}")
|
logger.info(f"ReductStore connecté : {self.url} / {self.bucket_name}")
|
||||||
'''
|
'''
|
||||||
|
|
||||||
async def store_metric(
|
async def store_metric(self, record: dict, experiment: str, well: str, uuid: str,
|
||||||
self,
|
planarian: int = 0, record_type: str = "frame", ts_us: Optional[int] = None, ):
|
||||||
record: dict,
|
|
||||||
experiment: str,
|
|
||||||
well: str,
|
|
||||||
planarian: int = 0,
|
|
||||||
record_type: str = "frame",
|
|
||||||
uuid: str = "",
|
|
||||||
ts_us: Optional[int] = None,
|
|
||||||
):
|
|
||||||
"""
|
"""
|
||||||
Stocke un enregistrement dans ReductStore.
|
Stocke un enregistrement dans ReductStore.
|
||||||
|
|
||||||
@@ -705,17 +697,18 @@ class ReductStoreClient:
|
|||||||
quand plusieurs planaires du même puits écrivent dans la même frame.
|
quand plusieurs planaires du même puits écrivent dans la même frame.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
uuid : identifiant unique de session (permet de filtrer
|
||||||
|
plusieurs sessions d'un même puits/expérience)
|
||||||
record : dict de métriques (issu de EthoVisionMetrics.update())
|
record : dict de métriques (issu de EthoVisionMetrics.update())
|
||||||
experiment : identifiant de l'expérience
|
experiment : identifiant de l'expérience
|
||||||
well : identifiant du puits
|
well : identifiant du puits
|
||||||
planarian : index du planaire (défaut 0)
|
planarian : index du planaire (défaut 0)
|
||||||
record_type : "frame" ou "summary"
|
record_type : "frame" ou "summary"
|
||||||
uuid : identifiant unique de session (permet de filtrer
|
|
||||||
plusieurs sessions d'un même puits/expérience)
|
|
||||||
ts_us : timestamp en microsecondes (défaut : maintenant)
|
ts_us : timestamp en microsecondes (défaut : maintenant)
|
||||||
"""
|
"""
|
||||||
if self._bucket is None:
|
if self._bucket is None:
|
||||||
await self.connect()
|
await self.connect()
|
||||||
|
|
||||||
# ts_us de base + offset planaire (0, 1, 2…) pour unicité garantie
|
# ts_us de base + offset planaire (0, 1, 2…) pour unicité garantie
|
||||||
base_ts = ts_us or int(time.time() * 1_000_000)
|
base_ts = ts_us or int(time.time() * 1_000_000)
|
||||||
unique_ts = base_ts + planarian
|
unique_ts = base_ts + planarian
|
||||||
@@ -725,10 +718,13 @@ class ReductStoreClient:
|
|||||||
"planarian": str(planarian),
|
"planarian": str(planarian),
|
||||||
"record_type": record_type,
|
"record_type": record_type,
|
||||||
}
|
}
|
||||||
|
"""
|
||||||
if uuid:
|
if uuid:
|
||||||
labels["uuid"] = uuid
|
labels["uuid"] = uuid"""
|
||||||
|
|
||||||
await self._bucket.write(
|
await self._bucket.write(
|
||||||
entry_name = "metrics",
|
#entry_name = "metrics",
|
||||||
|
entry_name = uuid,
|
||||||
data = json.dumps(record).encode("utf-8"),
|
data = json.dumps(record).encode("utf-8"),
|
||||||
timestamp = unique_ts,
|
timestamp = unique_ts,
|
||||||
labels = labels,
|
labels = labels,
|
||||||
@@ -740,8 +736,9 @@ class ReductStoreClient:
|
|||||||
summary: dict,
|
summary: dict,
|
||||||
experiment: str,
|
experiment: str,
|
||||||
well: str,
|
well: str,
|
||||||
|
uuid: str,
|
||||||
planarian: int = 0,
|
planarian: int = 0,
|
||||||
uuid: str = "",
|
record_type: str = "summary",
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Stocke le résumé de fin de session dans ReductStore.
|
Stocke le résumé de fin de session dans ReductStore.
|
||||||
@@ -750,26 +747,20 @@ class ReductStoreClient:
|
|||||||
summary : dict issu de EthoVisionMetrics.summary()
|
summary : dict issu de EthoVisionMetrics.summary()
|
||||||
experiment : identifiant de l'expérience
|
experiment : identifiant de l'expérience
|
||||||
well : identifiant du puits
|
well : identifiant du puits
|
||||||
planarian : index du planaire
|
|
||||||
uuid : identifiant unique de session (même valeur que
|
uuid : identifiant unique de session (même valeur que
|
||||||
celle utilisée dans store_metric pour cette session)
|
celle utilisée dans store_metric pour cette session)
|
||||||
|
planarian : index du planaire
|
||||||
"""
|
"""
|
||||||
await self.store_metric(
|
await self.store_metric(record=summary,experiment=experiment,
|
||||||
record = summary,
|
well=well, uuid=uuid, planarian=planarian, record_type=record_type)
|
||||||
experiment = experiment,
|
|
||||||
well = well,
|
|
||||||
planarian = planarian,
|
|
||||||
record_type = "summary",
|
|
||||||
uuid = uuid,
|
|
||||||
)
|
|
||||||
|
|
||||||
async def get_tracking_data(
|
async def get_tracking_data(
|
||||||
self,
|
self,
|
||||||
experiment: str,
|
experiment: str,
|
||||||
well: str,
|
well: str,
|
||||||
|
uuid: str,
|
||||||
planarian: int = 0,
|
planarian: int = 0,
|
||||||
record_type: str = "frame",
|
record_type: str = "frame",
|
||||||
uuid: str = "",
|
|
||||||
start: Optional[datetime] = None,
|
start: Optional[datetime] = None,
|
||||||
stop: Optional[datetime] = None,
|
stop: Optional[datetime] = None,
|
||||||
) -> list:
|
) -> list:
|
||||||
@@ -779,29 +770,31 @@ class ReductStoreClient:
|
|||||||
Args:
|
Args:
|
||||||
experiment : identifiant de l'expérience
|
experiment : identifiant de l'expérience
|
||||||
well : identifiant du puits
|
well : identifiant du puits
|
||||||
planarian : index du planaire
|
|
||||||
record_type : "frame" | "summary"
|
|
||||||
uuid : filtre sur une session spécifique (optionnel —
|
uuid : filtre sur une session spécifique (optionnel —
|
||||||
si vide, retourne toutes les sessions)
|
si vide, retourne toutes les sessions)
|
||||||
|
planarian : index du planaire
|
||||||
|
record_type : "frame" | "summary"
|
||||||
start, stop : plage temporelle (datetime UTC, optionnel)
|
start, stop : plage temporelle (datetime UTC, optionnel)
|
||||||
"""
|
"""
|
||||||
if self._bucket is None:
|
if self._bucket is None:
|
||||||
await self.connect()
|
await self.connect()
|
||||||
labels = {
|
|
||||||
"experiment": experiment,
|
when = {
|
||||||
"well": well,
|
"$and": [
|
||||||
"planarian": str(planarian),
|
#{"&experiment": {"$contains": experiment}},
|
||||||
"record_type": record_type,
|
#{"&well": {"$contains": well}},
|
||||||
|
{"&planarian": {"$contains": str(planarian)}},
|
||||||
|
{"&record_type": {"$contains": record_type}},
|
||||||
|
]
|
||||||
}
|
}
|
||||||
if uuid:
|
kwargs = {'when': when}
|
||||||
labels["uuid"] = uuid
|
|
||||||
kwargs = {"include": labels}
|
|
||||||
if start:
|
if start:
|
||||||
kwargs["start"] = int(start.timestamp() * 1_000_000)
|
kwargs["start"] = int(start.timestamp() * 1_000_000)
|
||||||
if stop:
|
if stop:
|
||||||
kwargs["stop"] = int(stop.timestamp() * 1_000_000)
|
kwargs["stop"] = int(stop.timestamp() * 1_000_000)
|
||||||
|
|
||||||
records = []
|
records = []
|
||||||
async for rec in self._bucket.query("metrics", **kwargs):
|
async for rec in self._bucket.query(uuid, **kwargs):
|
||||||
try:
|
try:
|
||||||
records.append(json.loads(await rec.read_all()))
|
records.append(json.loads(await rec.read_all()))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -854,16 +847,16 @@ class ReductStoreClient:
|
|||||||
"""
|
"""
|
||||||
dirpath = os.path.abspath(output_dir)
|
dirpath = os.path.abspath(output_dir)
|
||||||
os.makedirs(dirpath, exist_ok=True)
|
os.makedirs(dirpath, exist_ok=True)
|
||||||
filename = f"{experiment}_{well}_planaire{planarian:02d}_{record_type}.csv"
|
filename = f"{experiment}_{well}_planaire-{planarian}-{record_type}.csv"
|
||||||
return os.path.join(dirpath, filename)
|
return os.path.join(dirpath, filename)
|
||||||
|
|
||||||
async def export_csv(
|
async def export_csv(
|
||||||
self,
|
self,
|
||||||
experiment: str,
|
experiment: str,
|
||||||
well: str,
|
well: str,
|
||||||
|
uuid: str,
|
||||||
planarian: int = 0,
|
planarian: int = 0,
|
||||||
record_type: str = "frame",
|
record_type: str = "frame",
|
||||||
uuid: str = "",
|
|
||||||
output_dir: str = ".",
|
output_dir: str = ".",
|
||||||
start: Optional[datetime] = None,
|
start: Optional[datetime] = None,
|
||||||
stop: Optional[datetime] = None,
|
stop: Optional[datetime] = None,
|
||||||
@@ -876,25 +869,34 @@ class ReductStoreClient:
|
|||||||
Args:
|
Args:
|
||||||
experiment : identifiant de l'expérience
|
experiment : identifiant de l'expérience
|
||||||
well : identifiant du puits
|
well : identifiant du puits
|
||||||
|
uuid : filtre sur une session spécifique (optionnel —
|
||||||
|
si vide, retourne toutes les sessions)
|
||||||
planarian : index du planaire
|
planarian : index du planaire
|
||||||
record_type : "frame" | "summary"
|
record_type : "frame" | "summary"
|
||||||
output_dir : répertoire de sortie (défaut : répertoire courant)
|
output_dir : répertoire de sortie (défaut : répertoire courant)
|
||||||
uuid : filtre sur une session spécifique (optionnel —
|
|
||||||
si vide, retourne toutes les sessions)
|
|
||||||
start, stop : plage temporelle (datetime UTC, optionnel)
|
start, stop : plage temporelle (datetime UTC, optionnel)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
tuple (filepath, nb_lignes)
|
tuple (filepath, nb_lignes)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# record, experiment, well, uuid,
|
||||||
records = await self.get_tracking_data(
|
records = await self.get_tracking_data(
|
||||||
experiment, well, planarian, record_type, uuid, start, stop)
|
experiment,
|
||||||
|
well,
|
||||||
|
uuid,
|
||||||
|
planarian=planarian,
|
||||||
|
record_type=record_type,
|
||||||
|
start=start,
|
||||||
|
stop=stop,
|
||||||
|
)
|
||||||
|
|
||||||
if not records:
|
if not records:
|
||||||
logger.warning(f"Aucune donnée pour {experiment}/{well}/{planarian}")
|
logger.warning(f"Aucune donnée pour {experiment}/{well}/{planarian}")
|
||||||
return "", 0
|
return "", 0
|
||||||
|
|
||||||
records = self._convert_timestamps(records)
|
records = self._convert_timestamps(records)
|
||||||
filepath = self._build_filepath(output_dir, experiment, well,
|
filepath = self._build_filepath(output_dir, experiment, well, planarian, record_type)
|
||||||
planarian, record_type)
|
|
||||||
fieldnames = list(dict.fromkeys(k for r in records for k in r.keys()))
|
fieldnames = list(dict.fromkeys(k for r in records for k in r.keys()))
|
||||||
|
|
||||||
with open(filepath, "w", newline="", encoding="utf-8") as f:
|
with open(filepath, "w", newline="", encoding="utf-8") as f:
|
||||||
@@ -909,9 +911,9 @@ class ReductStoreClient:
|
|||||||
self,
|
self,
|
||||||
experiment: str,
|
experiment: str,
|
||||||
well: str,
|
well: str,
|
||||||
|
uuid: str,
|
||||||
planarian: int = 0,
|
planarian: int = 0,
|
||||||
record_type: str = "frame",
|
record_type: str = "frame",
|
||||||
uuid: str = "",
|
|
||||||
start: Optional[datetime] = None,
|
start: Optional[datetime] = None,
|
||||||
stop: Optional[datetime] = None,
|
stop: Optional[datetime] = None,
|
||||||
) -> tuple:
|
) -> tuple:
|
||||||
@@ -923,7 +925,15 @@ class ReductStoreClient:
|
|||||||
tuple (contenu_csv_str, nb_lignes)
|
tuple (contenu_csv_str, nb_lignes)
|
||||||
"""
|
"""
|
||||||
records = await self.get_tracking_data(
|
records = await self.get_tracking_data(
|
||||||
experiment, well, planarian, record_type, uuid, start, stop)
|
experiment,
|
||||||
|
well,
|
||||||
|
uuid,
|
||||||
|
planarian=planarian,
|
||||||
|
record_type=record_type,
|
||||||
|
start=start,
|
||||||
|
stop=stop,
|
||||||
|
)
|
||||||
|
|
||||||
if not records:
|
if not records:
|
||||||
return "", 0
|
return "", 0
|
||||||
records = self._convert_timestamps(records)
|
records = self._convert_timestamps(records)
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
|
||||||
|
import logging
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
from asgiref.sync import async_to_sync
|
||||||
|
from django.conf import settings
|
||||||
|
from modules.planarian_metrics import ReductStoreClient
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_reduct_client() -> ReductStoreClient:
|
||||||
|
"""Instancie le client ReductStore depuis les settings Django."""
|
||||||
|
return ReductStoreClient(url=settings.REDUCTSTORE_URL, token=settings.REDUCTSTORE_TOKEN)
|
||||||
|
|
||||||
|
|
||||||
|
def export_csv_sync(
|
||||||
|
*,
|
||||||
|
experiment,
|
||||||
|
well,
|
||||||
|
uuid,
|
||||||
|
planarian,
|
||||||
|
record_type,
|
||||||
|
start=None,
|
||||||
|
stop=None,
|
||||||
|
):
|
||||||
|
#@async_to_sync
|
||||||
|
async def _run():
|
||||||
|
client = _get_reduct_client()
|
||||||
|
|
||||||
|
await client.connect()
|
||||||
|
return await client.export_csv_response(
|
||||||
|
experiment=experiment,
|
||||||
|
well=well,
|
||||||
|
uuid=uuid,
|
||||||
|
planarian=planarian,
|
||||||
|
record_type=record_type,
|
||||||
|
start=start,
|
||||||
|
stop=stop,
|
||||||
|
)
|
||||||
|
#return _run()
|
||||||
|
return asyncio.run(_run())
|
||||||
|
|
||||||
|
def export_csv_file_sync(
|
||||||
|
*,
|
||||||
|
experiment,
|
||||||
|
well,
|
||||||
|
uuid,
|
||||||
|
planarian,
|
||||||
|
record_type,
|
||||||
|
):
|
||||||
|
async def _run():
|
||||||
|
client = _get_reduct_client()
|
||||||
|
|
||||||
|
await client.connect()
|
||||||
|
try:
|
||||||
|
return await client.export_csv(
|
||||||
|
experiment=experiment,
|
||||||
|
well=well,
|
||||||
|
uuid=uuid,
|
||||||
|
planarian=planarian,
|
||||||
|
record_type=record_type,
|
||||||
|
output_dir=settings.CSV_EXPORT_DIR,
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
await client.close()
|
||||||
|
return asyncio.run(_run())
|
||||||
|
|
||||||
@@ -143,7 +143,8 @@ class ExperimentConfig(models.Model):
|
|||||||
|
|
||||||
|
|
||||||
def get_session(self):
|
def get_session(self):
|
||||||
return self.experiment_key.session_experiments.first() if self.experiment_key else None
|
se = self.experiment_key.session_experiments.first() if self.experiment_key else None
|
||||||
|
return se.session
|
||||||
|
|
||||||
|
|
||||||
def to_params_dict(self) -> dict:
|
def to_params_dict(self) -> dict:
|
||||||
|
|||||||
@@ -1,54 +1,80 @@
|
|||||||
# tasks.py
|
# tasks.py
|
||||||
from celery import shared_task
|
from celery import shared_task, group
|
||||||
from celery.utils.log import get_task_logger
|
from celery.utils.log import get_task_logger
|
||||||
|
|
||||||
from django.conf import settings
|
from scanner.models import SessionExperiment, get_uuid_from_session, Experiment
|
||||||
from asgiref.sync import async_to_sync
|
|
||||||
from modules.planarian_metrics import ReductStoreClient
|
|
||||||
from scanner.models import SessionExperiment
|
|
||||||
from .models import ExperimentConfig
|
from .models import ExperimentConfig
|
||||||
|
from .export_service import export_csv_file_sync
|
||||||
|
|
||||||
logger = get_task_logger(__name__)
|
logger = get_task_logger(__name__)
|
||||||
|
|
||||||
def _get_reduct_client() -> ReductStoreClient:
|
|
||||||
"""Instancie le client ReductStore depuis les settings Django."""
|
@shared_task(bind=True, autoretry_for=(Exception,), retry_backoff=True, retry_kwargs={"max_retries": 3},)
|
||||||
return ReductStoreClient(url=settings.REDUCTSTORE_URL, token=settings.REDUCTSTORE_TOKEN)
|
def export_single_metric(self, *, experiment, well, uuid, planarian, record_type,):
|
||||||
|
"""
|
||||||
|
Exporte UN fichier CSV.
|
||||||
|
"""
|
||||||
|
logger.info( f"Export start {experiment} {well} {planarian} {record_type}")
|
||||||
|
f, n = export_csv_file_sync(
|
||||||
|
experiment=experiment,
|
||||||
|
well=well,
|
||||||
|
uuid=uuid,
|
||||||
|
planarian=planarian,
|
||||||
|
record_type=record_type,
|
||||||
|
)
|
||||||
|
logger.info( f"Export done {f} ({n} rows)" )
|
||||||
|
return {"file": f, "rows": n, }
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task
|
||||||
def export_experiment_metrics(experiment):
|
def export_experiment_metrics_task(experiment_id):
|
||||||
|
experiment = Experiment.objects.filter(pk=experiment_id).first()
|
||||||
|
if not experiment:
|
||||||
|
return
|
||||||
|
|
||||||
@async_to_sync
|
jobs = []
|
||||||
async def _do_export(well, planarian, record_type):
|
configs = (ExperimentConfig.objects.filter(experiment_key_id=experiment.id).order_by("well"))
|
||||||
client = _get_reduct_client()
|
for conf in configs:
|
||||||
|
|
||||||
await client.connect()
|
|
||||||
try:
|
|
||||||
f, n = await client.export_csv(
|
|
||||||
experiment = experiment.identifier,
|
|
||||||
well = well,
|
|
||||||
planarian = planarian,
|
|
||||||
record_type = record_type,
|
|
||||||
output_dir = settings.CSV_EXPORT_DIR,
|
|
||||||
)
|
|
||||||
logger.warning(f"Export CSV {settings.CSV_EXPORT_DIR}/{f} done, {n} lignes")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Erreur export CSV: {e}")
|
|
||||||
|
|
||||||
experiment_configs = ExperimentConfig.objects.filter(experiment_key_id=experiment.id).order_by('well').all()
|
|
||||||
for conf in experiment_configs:
|
|
||||||
well = conf.well
|
well = conf.well
|
||||||
count = conf.planarian_count
|
count = conf.planarian_count
|
||||||
|
session = conf.get_session()
|
||||||
|
uuid = get_uuid_from_session(session.id, conf.experiment_key.multiwell.position, well, )
|
||||||
|
|
||||||
for record_type in ["frame", "summary"]:
|
for record_type in ["frame", "summary"]:
|
||||||
for planarian in range(count):
|
for planarian in range(count):
|
||||||
_do_export(well, planarian, record_type)
|
'''
|
||||||
|
jobs.append(
|
||||||
|
export_single_metric.s(
|
||||||
|
experiment=experiment.identifier,
|
||||||
|
well=well,
|
||||||
|
uuid=uuid,
|
||||||
|
planarian=planarian,
|
||||||
|
record_type=record_type,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
'''
|
||||||
|
try:
|
||||||
|
f, n = export_csv_file_sync(
|
||||||
|
experiment=experiment.identifier,
|
||||||
|
well=well,
|
||||||
|
uuid=uuid,
|
||||||
|
planarian=planarian,
|
||||||
|
record_type=record_type,
|
||||||
|
)
|
||||||
|
logger.info( f"Export done {f} ({n} rows)" )
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception(e)
|
||||||
|
'''
|
||||||
|
result = group(jobs).apply_async()
|
||||||
|
logger.info(f"{len(jobs)} exports launched group_id={result.id}")
|
||||||
|
return {"group_id": result.id,"jobs": len(jobs), }'''
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task
|
||||||
def export_session_metrics(session):
|
def export_session_metrics_task(session_id):
|
||||||
experiments = SessionExperiment.experiment_by_session(session.id)
|
experiments = SessionExperiment.experiment_by_session(session_id, active=False)
|
||||||
for experiment in experiments:
|
for experiment in experiments:
|
||||||
export_experiment_metrics(experiment)
|
export_experiment_metrics_task.delay(experiment.id)
|
||||||
|
|
||||||
|
|
||||||
@@ -27,6 +27,12 @@
|
|||||||
<i class="fa-solid fa-file-export w3-text-red w3-xlarge"></i> {% trans "Exporter les metrics de l'expérience" %}<br>
|
<i class="fa-solid fa-file-export w3-text-red w3-xlarge"></i> {% trans "Exporter les metrics de l'expérience" %}<br>
|
||||||
<span class="w3-margin-left-5"> $> {{ export_csv_destination }}
|
<span class="w3-margin-left-5"> $> {{ export_csv_destination }}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
<a href="#" class="w3-bar-item w3-btn w3-hover-opacity" onclick="export_csv('{{ url_export }}', {{ current_session.id }}, 'session_csv')">
|
||||||
|
<i class="fa-solid fa-file-export w3-text-pink w3-xxlarge"></i> {% trans "Exporter les metrics de la session" %}<br>
|
||||||
|
<span class="w3-margin-left-5"> $> {{ export_csv_destination }}
|
||||||
|
</a>
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -34,6 +40,7 @@
|
|||||||
{% block js_footer %}
|
{% block js_footer %}
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
<script>
|
<script>
|
||||||
|
const export_msg = "{% trans "Confirmer l'export des vidéos. ATTENTION le processus peut être long!" %}";
|
||||||
// Auto-dismiss messages after 5 seconds
|
// Auto-dismiss messages after 5 seconds
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
const messages = document.querySelectorAll('.alert');
|
const messages = document.querySelectorAll('.alert');
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
<input type="hidden" id="_sid" name="_sid" value="{{ current_session.id }}">
|
<input type="hidden" id="_sid" name="_sid" value="{{ current_session.id }}">
|
||||||
<input type="hidden" id="_expid" name="_expid" value="{{ current_experiment.id }}">
|
<input type="hidden" id="_expid" name="_expid" value="{{ current_experiment.id }}">
|
||||||
|
|
||||||
|
|
||||||
<div class="w3-card-4 w3-border w3-round w3-round-large">
|
<div class="w3-card-4 w3-border w3-round w3-round-large">
|
||||||
<header class="w3-container w3-blue w3-round w3-round-top-large">
|
<header class="w3-container w3-blue w3-round w3-round-top-large">
|
||||||
<h3 class="w3-text-white">{% trans "Paramètres d'export" %}</h3>
|
<h3 class="w3-text-white">{% trans "Paramètres d'export" %}</h3>
|
||||||
|
|||||||
@@ -20,7 +20,9 @@ from django.views import View
|
|||||||
from modules.planarian_metrics import ExperimentParams, ReductStoreClient
|
from modules.planarian_metrics import ExperimentParams, ReductStoreClient
|
||||||
from modules.system_stats import get_cached_stats, start_background_updater
|
from modules.system_stats import get_cached_stats, start_background_updater
|
||||||
from scanner.constants import ScannerConstants
|
from scanner.constants import ScannerConstants
|
||||||
from .tasks import export_experiment_metrics, export_session_metrics
|
|
||||||
|
from .tasks import export_experiment_metrics_task, export_session_metrics_task
|
||||||
|
from .export_service import export_csv_sync
|
||||||
from scanner import models, views as scanner_views
|
from scanner import models, views as scanner_views
|
||||||
from .models import ExperimentConfig
|
from .models import ExperimentConfig
|
||||||
|
|
||||||
@@ -82,7 +84,6 @@ def get_active_session(request, session_id=None, experiment_id=None):
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Vue :Export CSV depuis ReductStore
|
# Vue :Export CSV depuis ReductStore
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@csrf_exempt
|
@csrf_exempt
|
||||||
def export_metrics(request):
|
def export_metrics(request):
|
||||||
@@ -92,48 +93,13 @@ def export_metrics(request):
|
|||||||
if action=='experiment_csv':
|
if action=='experiment_csv':
|
||||||
experiment = models.Experiment.objects.filter(pk=pid).first()
|
experiment = models.Experiment.objects.filter(pk=pid).first()
|
||||||
if experiment:
|
if experiment:
|
||||||
export_experiment_metrics(experiment)
|
export_experiment_metrics_task.delay(experiment.id) # @UndefinedVariable
|
||||||
return JsonResponse({"state": True})
|
return JsonResponse({"success": True, "msg": str(_("Métrics en cours de téléchargement"))})
|
||||||
if action=='session_csv':
|
if action=='session_csv':
|
||||||
session = models.Session.objects.filter(pk=pid).first()
|
if pid:
|
||||||
if session:
|
export_session_metrics_task(pid) # @UndefinedVariable
|
||||||
export_session_metrics(session)
|
return JsonResponse({"success": True, "msg": str(_("Métrics en cours de téléchargement"))})
|
||||||
return JsonResponse({"state": True})
|
return JsonResponse({"success": False, "msg": str(_("Erreur: les métrics non pas été téléchargés"))})
|
||||||
return JsonResponse({"state": False})
|
|
||||||
|
|
||||||
|
|
||||||
def export_csv(request):
|
|
||||||
d = request.POST
|
|
||||||
|
|
||||||
print("export_csv===========", d)
|
|
||||||
|
|
||||||
@async_to_sync
|
|
||||||
async def _do_export():
|
|
||||||
client = _get_reduct_client()
|
|
||||||
await client.connect()
|
|
||||||
try:
|
|
||||||
csv_content, n = await client.export_csv_response(
|
|
||||||
experiment = d.get("experiment"),
|
|
||||||
well = d.get("well"),
|
|
||||||
planarian = d.get("planarian"),
|
|
||||||
record_type = d.get("record_type"),
|
|
||||||
start = d.get("start_dt"),
|
|
||||||
stop = d.get("stop_dt"),
|
|
||||||
)
|
|
||||||
print(f"Export CSV: export_csv_response done, {n} lignes, content size={len(csv_content)}")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Erreur export CSV: {e}")
|
|
||||||
messages.error(request, _("Erreur lors de l'export CSV: %(error)s") % {"error": str(e)})
|
|
||||||
return None, 0
|
|
||||||
return csv_content, n
|
|
||||||
|
|
||||||
csv_content, n = _do_export()
|
|
||||||
logger.info(f"Export CSV: {n} lignes, content size={len(csv_content)}")
|
|
||||||
filename = (
|
|
||||||
f"{d['experiment']}_{d['well']}-planaire-{d['planarian']}"
|
|
||||||
f"_{d['record_type']}.csv"
|
|
||||||
)
|
|
||||||
return csv_content, filename
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@@ -148,11 +114,34 @@ def export_csv_view(request):
|
|||||||
}
|
}
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
valid = request.POST.get('valid')
|
valid = request.POST.get('valid')
|
||||||
if valid == 'ok':
|
session = session_context['current_session']
|
||||||
csv_content, filename = export_csv(request)
|
experiment = session_context['current_experiment']
|
||||||
|
if valid == 'ok' and session and experiment:
|
||||||
|
well_name = request.POST.get('well')
|
||||||
|
uuid = models.get_uuid_from_session(session.id, experiment.multiwell.position, well_name)
|
||||||
|
'''
|
||||||
|
csv_content, filename = export_csv(request, uuid)
|
||||||
if csv_content:
|
if csv_content:
|
||||||
response = FileResponse(csv_content, content_type="text/csv; charset=utf-8")
|
response = FileResponse(csv_content, content_type="text/csv; charset=utf-8")
|
||||||
response["Content-Disposition"] = f'attachment; filename="{filename}"'
|
response["Content-Disposition"] = f'attachment; filename="{filename}"'
|
||||||
|
return response'''
|
||||||
|
csv_content, n = export_csv_sync(
|
||||||
|
experiment=experiment.identifier,
|
||||||
|
well=well_name,
|
||||||
|
uuid=uuid,
|
||||||
|
planarian=request.POST.get("planarian"),
|
||||||
|
record_type=request.POST.get("record_type"),
|
||||||
|
start=request.POST.get("start_dt"),
|
||||||
|
stop=request.POST.get("stop_dt"),
|
||||||
|
)
|
||||||
|
if csv_content:
|
||||||
|
filename = (
|
||||||
|
f"{experiment.identifier}_{well_name}-{request.POST.get('planarian')}_"
|
||||||
|
f"{request.POST.get('record_type')}.csv"
|
||||||
|
)
|
||||||
|
logger.info(f"Export CSV: {n} lignes, content size={len(csv_content)}")
|
||||||
|
response = FileResponse(csv_content, content_type="text/csv; charset=utf-8")
|
||||||
|
response["Content-Disposition"] = (f'attachment; filename="{filename}"')
|
||||||
return response
|
return response
|
||||||
|
|
||||||
messages.warning(request, _("Aucune donnée trouvée."))
|
messages.warning(request, _("Aucune donnée trouvée."))
|
||||||
|
|||||||
@@ -258,7 +258,6 @@ class Experiment(models.Model):
|
|||||||
def by_identifier(cls, identifier):
|
def by_identifier(cls, identifier):
|
||||||
return Experiment.objects.filter(identifier__exact=identifier).first()
|
return Experiment.objects.filter(identifier__exact=identifier).first()
|
||||||
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['-created', ]
|
ordering = ['-created', ]
|
||||||
verbose_name = _("Expérience")
|
verbose_name = _("Expérience")
|
||||||
@@ -376,15 +375,21 @@ def delete_periodic_task(sender, instance, **kwargs):
|
|||||||
instance.scanning_task.delete()
|
instance.scanning_task.delete()
|
||||||
|
|
||||||
|
|
||||||
|
def get_uuid_from_session(session_id, multiwel_position, well_name):
|
||||||
|
return f'{session_id}-{multiwel_position}-{well_name}'
|
||||||
|
|
||||||
|
|
||||||
class SessionExperiment(models.Model):
|
class SessionExperiment(models.Model):
|
||||||
author = models.ForeignKey(User, on_delete=models.SET_NULL, verbose_name="Auteur", null=True, blank=True)
|
author = models.ForeignKey(User, on_delete=models.SET_NULL, verbose_name="Auteur", null=True, blank=True)
|
||||||
session = models.ForeignKey(Session, verbose_name=_("Session"), on_delete=models.CASCADE, null=True, blank=True)
|
session = models.ForeignKey(Session, verbose_name=_("Session"), on_delete=models.CASCADE, null=True, blank=True)
|
||||||
experiment = models.ForeignKey(Experiment, verbose_name=_("Expérience"), on_delete=models.CASCADE, null=True, blank=True, related_name="session_experiments")
|
experiment = models.ForeignKey(Experiment, verbose_name=_("Expérience"), on_delete=models.CASCADE, null=True, blank=True, related_name="session_experiments")
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def experiment_by_session(cls, session_id, active=True):
|
def experiment_by_session(cls, session_id, active=True):
|
||||||
return [ ss.experiment for ss in SessionExperiment.objects.filter(session__id=session_id, session__active=active).order_by('experiment__multiwell__order') ]
|
return [ ss.experiment for ss in SessionExperiment.objects.filter(session__id=session_id, session__active=active).order_by('experiment__multiwell__order') ]
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def uuid_from_session(cls, sid):
|
def uuid_from_session(cls, sid):
|
||||||
experiments = [ss.experiment for ss in SessionExperiment.objects.filter(session__id=sid, session__active=False)]
|
experiments = [ss.experiment for ss in SessionExperiment.objects.filter(session__id=sid, session__active=False)]
|
||||||
@@ -397,6 +402,15 @@ class SessionExperiment(models.Model):
|
|||||||
uuid_list.append(uuid)
|
uuid_list.append(uuid)
|
||||||
return uuid_list
|
return uuid_list
|
||||||
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_uuid(cls, session_id, experiment_id, well_name):
|
||||||
|
ss = SessionExperiment.objects.filter(session__id=session_id, experiment_id=experiment_id).first()
|
||||||
|
if ss:
|
||||||
|
return get_uuid_from_session(session_id, ss.experiment.multiwel.position, well_name)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['session',]
|
ordering = ['session',]
|
||||||
unique_together = ["session", "experiment"]
|
unique_together = ["session", "experiment"]
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ def stats_view(request):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
return JsonResponse({"error": str(e)}, status=500)
|
return JsonResponse({"error": str(e)}, status=500)
|
||||||
|
|
||||||
|
|
||||||
def global_context(request, **ctx):
|
def global_context(request, **ctx):
|
||||||
default_multiwell = models.MultiWell.objects.filter(default=True).first()
|
default_multiwell = models.MultiWell.objects.filter(default=True).first()
|
||||||
conf = ScannerConstants().get()
|
conf = ScannerConstants().get()
|
||||||
@@ -60,11 +61,12 @@ def global_context(request, **ctx):
|
|||||||
def admin_view(request):
|
def admin_view(request):
|
||||||
return render(request, "scanner/iframe.html", context=global_context(request, link='/admin/'))
|
return render(request, "scanner/iframe.html", context=global_context(request, link='/admin/'))
|
||||||
|
|
||||||
@login_required
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
def admin_session_view(request):
|
def admin_session_view(request):
|
||||||
return render(request, "scanner/iframe.html", context=global_context(request, link='/admin/scanner/session/'))
|
return render(request, "scanner/iframe.html", context=global_context(request, link='/admin/scanner/session/'))
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def admin_experiment_view(request):
|
def admin_experiment_view(request):
|
||||||
return render(request, "scanner/iframe.html", context=global_context(request, link='/admin/scanner/experiment/'))
|
return render(request, "scanner/iframe.html", context=global_context(request, link='/admin/scanner/experiment/'))
|
||||||
@@ -73,15 +75,18 @@ def admin_experiment_view(request):
|
|||||||
def admin_experimentconfig_view(request):
|
def admin_experimentconfig_view(request):
|
||||||
return render(request, "scanner/iframe.html", context=global_context(request, link='/admin/planarian/experimentconfig/'))
|
return render(request, "scanner/iframe.html", context=global_context(request, link='/admin/planarian/experimentconfig/'))
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def admin_periodictask_view(request):
|
def admin_periodictask_view(request):
|
||||||
return render(request, "scanner/iframe.html", context=global_context(request, link='/admin/django_celery_beat/periodictask/'))
|
return render(request, "scanner/iframe.html", context=global_context(request, link='/admin/django_celery_beat/periodictask/'))
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@user_passes_test(is_staff_or_admin)
|
@user_passes_test(is_staff_or_admin)
|
||||||
def reductstore_view(request):
|
def reductstore_view(request):
|
||||||
return render(request, "scanner/redirection.html", context=global_context(request, link=f'http://{settings.LOCAL_IP_SERVER}:8383/'))
|
return render(request, "scanner/redirection.html", context=global_context(request, link=f'http://{settings.LOCAL_IP_SERVER}:8383/'))
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@user_passes_test(is_staff_or_admin)
|
@user_passes_test(is_staff_or_admin)
|
||||||
def adminer_view(request):
|
def adminer_view(request):
|
||||||
@@ -92,21 +97,25 @@ def adminer_view(request):
|
|||||||
def portainer_view(request):
|
def portainer_view(request):
|
||||||
return render(request, "scanner/redirection.html", context=global_context(request, link=f'http://{settings.LOCAL_IP_SERVER}:9000/'))
|
return render(request, "scanner/redirection.html", context=global_context(request, link=f'http://{settings.LOCAL_IP_SERVER}:9000/'))
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@user_passes_test(is_staff_or_admin)
|
@user_passes_test(is_staff_or_admin)
|
||||||
def supervisor_view(request):
|
def supervisor_view(request):
|
||||||
return render(request, "scanner/redirection.html", context=global_context(request, link=f'http://{settings.LOCAL_IP_SERVER}:9001/'))
|
return render(request, "scanner/redirection.html", context=global_context(request, link=f'http://{settings.LOCAL_IP_SERVER}:9001/'))
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@user_passes_test(is_staff_or_admin)
|
@user_passes_test(is_staff_or_admin)
|
||||||
def supervisor_worker(request):
|
def supervisor_worker(request):
|
||||||
return render(request, "scanner/redirection.html", context=global_context(request, link=f'http://{settings.LOCAL_IP_SERVER}:9001/logtail/test_tube:services'))
|
return render(request, "scanner/redirection.html", context=global_context(request, link=f'http://{settings.LOCAL_IP_SERVER}:9001/logtail/test_tube:services'))
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@user_passes_test(is_staff_or_admin)
|
@user_passes_test(is_staff_or_admin)
|
||||||
def supervisor_scheduler(request):
|
def supervisor_scheduler(request):
|
||||||
return render(request, "scanner/redirection.html", context=global_context(request, link=f'http://{settings.LOCAL_IP_SERVER}:9001/logtail/test_tube:planification'))
|
return render(request, "scanner/redirection.html", context=global_context(request, link=f'http://{settings.LOCAL_IP_SERVER}:9001/logtail/test_tube:planification'))
|
||||||
|
|
||||||
|
|
||||||
def documentation(request, template=None):
|
def documentation(request, template=None):
|
||||||
ctx = dict(
|
ctx = dict(
|
||||||
choice_title=_("Documentation")
|
choice_title=_("Documentation")
|
||||||
@@ -225,6 +234,7 @@ def export_medias(request):
|
|||||||
)
|
)
|
||||||
return render(request, "scanner/export_medias.html", context=global_context(request, **ctx))
|
return render(request, "scanner/export_medias.html", context=global_context(request, **ctx))
|
||||||
|
|
||||||
|
|
||||||
@require_POST
|
@require_POST
|
||||||
@csrf_exempt
|
@csrf_exempt
|
||||||
def download_api(request):
|
def download_api(request):
|
||||||
@@ -286,6 +296,7 @@ def replay_view(request):
|
|||||||
)
|
)
|
||||||
return render(request, "scanner/replay.html", context=global_context(request, **ctx))
|
return render(request, "scanner/replay.html", context=global_context(request, **ctx))
|
||||||
|
|
||||||
|
|
||||||
@require_POST
|
@require_POST
|
||||||
@csrf_exempt
|
@csrf_exempt
|
||||||
def export_api(request):
|
def export_api(request):
|
||||||
@@ -295,10 +306,10 @@ def export_api(request):
|
|||||||
try:
|
try:
|
||||||
if action == 'export_images':
|
if action == 'export_images':
|
||||||
export_all_images(session_id)
|
export_all_images(session_id)
|
||||||
return JsonResponse({"success": True, "msg": str(_("Images téléchargées"))})
|
return JsonResponse({"success": True, "msg": str(_("Images en cours de téléchargement"))})
|
||||||
elif action == 'export_videos':
|
elif action == 'export_videos':
|
||||||
export_all_videos(session_id)
|
export_all_videos(session_id)
|
||||||
return JsonResponse({"success": True, "msg": str(_("Vidéos téléchargées"))})
|
return JsonResponse({"success": True, "msg": str(_("Vidéos en cours de téléchargement"))})
|
||||||
except:
|
except:
|
||||||
return JsonResponse({"state": False, "success": False, "msg": str(_("Erreur d'exportation")), })
|
return JsonResponse({"state": False, "success": False, "msg": str(_("Erreur d'exportation")), })
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user