export-all-verif
This commit is contained in:
@@ -20,6 +20,7 @@ EXPORTS_LOCAL_PATH=/home/rpi4/exports
|
||||
EXPORT_REMOTE_PATH=/mnt/exports
|
||||
REDUCTSTORE_PATH=/home/rpi4/medias
|
||||
CSV_EXPORT_DIR=/home/rpi4/exports/csv
|
||||
BACKUP_DIR=/home/rpi4/backup/mariadb
|
||||
|
||||
####
|
||||
# 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.config_from_object('django.conf:settings', namespace='CELERY')
|
||||
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_TASK_TRACK_STARTED = True
|
||||
CELERY_TASK_TIME_LIMIT = 30 * 60
|
||||
CELERY_TASK_SOFT_TIME_LIMIT = 20 * 60
|
||||
CELERY_BEAT_SCHEDULER = 'django_celery_beat.schedulers:DatabaseScheduler'
|
||||
CELERY_ACCEPT_CONTENT = ['application/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_PATH = config("REDUCTSTORE_PATH")
|
||||
|
||||
BACKUP_DIR = config('BACKUP_DIR')
|
||||
|
||||
## servers app
|
||||
#
|
||||
USER = config('USER')
|
||||
|
||||
@@ -687,16 +687,8 @@ class ReductStoreClient:
|
||||
logger.info(f"ReductStore connecté : {self.url} / {self.bucket_name}")
|
||||
'''
|
||||
|
||||
async def store_metric(
|
||||
self,
|
||||
record: dict,
|
||||
experiment: str,
|
||||
well: str,
|
||||
planarian: int = 0,
|
||||
record_type: str = "frame",
|
||||
uuid: str = "",
|
||||
ts_us: Optional[int] = None,
|
||||
):
|
||||
async def store_metric(self, record: dict, experiment: str, well: str, uuid: str,
|
||||
planarian: int = 0, record_type: str = "frame", ts_us: Optional[int] = None, ):
|
||||
"""
|
||||
Stocke un enregistrement dans ReductStore.
|
||||
|
||||
@@ -705,17 +697,18 @@ class ReductStoreClient:
|
||||
quand plusieurs planaires du même puits écrivent dans la même frame.
|
||||
|
||||
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())
|
||||
experiment : identifiant de l'expérience
|
||||
well : identifiant du puits
|
||||
planarian : index du planaire (défaut 0)
|
||||
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)
|
||||
"""
|
||||
if self._bucket is None:
|
||||
await self.connect()
|
||||
|
||||
# ts_us de base + offset planaire (0, 1, 2…) pour unicité garantie
|
||||
base_ts = ts_us or int(time.time() * 1_000_000)
|
||||
unique_ts = base_ts + planarian
|
||||
@@ -725,10 +718,13 @@ class ReductStoreClient:
|
||||
"planarian": str(planarian),
|
||||
"record_type": record_type,
|
||||
}
|
||||
"""
|
||||
if uuid:
|
||||
labels["uuid"] = uuid
|
||||
labels["uuid"] = uuid"""
|
||||
|
||||
await self._bucket.write(
|
||||
entry_name = "metrics",
|
||||
#entry_name = "metrics",
|
||||
entry_name = uuid,
|
||||
data = json.dumps(record).encode("utf-8"),
|
||||
timestamp = unique_ts,
|
||||
labels = labels,
|
||||
@@ -740,8 +736,9 @@ class ReductStoreClient:
|
||||
summary: dict,
|
||||
experiment: str,
|
||||
well: str,
|
||||
uuid: str,
|
||||
planarian: int = 0,
|
||||
uuid: str = "",
|
||||
record_type: str = "summary",
|
||||
):
|
||||
"""
|
||||
Stocke le résumé de fin de session dans ReductStore.
|
||||
@@ -750,26 +747,20 @@ class ReductStoreClient:
|
||||
summary : dict issu de EthoVisionMetrics.summary()
|
||||
experiment : identifiant de l'expérience
|
||||
well : identifiant du puits
|
||||
planarian : index du planaire
|
||||
uuid : identifiant unique de session (même valeur que
|
||||
celle utilisée dans store_metric pour cette session)
|
||||
planarian : index du planaire
|
||||
"""
|
||||
await self.store_metric(
|
||||
record = summary,
|
||||
experiment = experiment,
|
||||
well = well,
|
||||
planarian = planarian,
|
||||
record_type = "summary",
|
||||
uuid = uuid,
|
||||
)
|
||||
await self.store_metric(record=summary,experiment=experiment,
|
||||
well=well, uuid=uuid, planarian=planarian, record_type=record_type)
|
||||
|
||||
async def get_tracking_data(
|
||||
self,
|
||||
experiment: str,
|
||||
well: str,
|
||||
uuid: str,
|
||||
planarian: int = 0,
|
||||
record_type: str = "frame",
|
||||
uuid: str = "",
|
||||
start: Optional[datetime] = None,
|
||||
stop: Optional[datetime] = None,
|
||||
) -> list:
|
||||
@@ -779,29 +770,31 @@ class ReductStoreClient:
|
||||
Args:
|
||||
experiment : identifiant de l'expérience
|
||||
well : identifiant du puits
|
||||
planarian : index du planaire
|
||||
record_type : "frame" | "summary"
|
||||
uuid : filtre sur une session spécifique (optionnel —
|
||||
si vide, retourne toutes les sessions)
|
||||
planarian : index du planaire
|
||||
record_type : "frame" | "summary"
|
||||
start, stop : plage temporelle (datetime UTC, optionnel)
|
||||
"""
|
||||
if self._bucket is None:
|
||||
await self.connect()
|
||||
labels = {
|
||||
"experiment": experiment,
|
||||
"well": well,
|
||||
"planarian": str(planarian),
|
||||
"record_type": record_type,
|
||||
|
||||
when = {
|
||||
"$and": [
|
||||
#{"&experiment": {"$contains": experiment}},
|
||||
#{"&well": {"$contains": well}},
|
||||
{"&planarian": {"$contains": str(planarian)}},
|
||||
{"&record_type": {"$contains": record_type}},
|
||||
]
|
||||
}
|
||||
if uuid:
|
||||
labels["uuid"] = uuid
|
||||
kwargs = {"include": labels}
|
||||
kwargs = {'when': when}
|
||||
if start:
|
||||
kwargs["start"] = int(start.timestamp() * 1_000_000)
|
||||
if stop:
|
||||
kwargs["stop"] = int(stop.timestamp() * 1_000_000)
|
||||
|
||||
records = []
|
||||
async for rec in self._bucket.query("metrics", **kwargs):
|
||||
async for rec in self._bucket.query(uuid, **kwargs):
|
||||
try:
|
||||
records.append(json.loads(await rec.read_all()))
|
||||
except Exception as e:
|
||||
@@ -854,16 +847,16 @@ class ReductStoreClient:
|
||||
"""
|
||||
dirpath = os.path.abspath(output_dir)
|
||||
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)
|
||||
|
||||
async def export_csv(
|
||||
self,
|
||||
experiment: str,
|
||||
well: str,
|
||||
uuid: str,
|
||||
planarian: int = 0,
|
||||
record_type: str = "frame",
|
||||
uuid: str = "",
|
||||
output_dir: str = ".",
|
||||
start: Optional[datetime] = None,
|
||||
stop: Optional[datetime] = None,
|
||||
@@ -876,25 +869,34 @@ class ReductStoreClient:
|
||||
Args:
|
||||
experiment : identifiant de l'expérience
|
||||
well : identifiant du puits
|
||||
uuid : filtre sur une session spécifique (optionnel —
|
||||
si vide, retourne toutes les sessions)
|
||||
planarian : index du planaire
|
||||
record_type : "frame" | "summary"
|
||||
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)
|
||||
|
||||
Returns:
|
||||
tuple (filepath, nb_lignes)
|
||||
"""
|
||||
|
||||
# record, experiment, well, uuid,
|
||||
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:
|
||||
logger.warning(f"Aucune donnée pour {experiment}/{well}/{planarian}")
|
||||
return "", 0
|
||||
|
||||
records = self._convert_timestamps(records)
|
||||
filepath = self._build_filepath(output_dir, experiment, well,
|
||||
planarian, record_type)
|
||||
filepath = self._build_filepath(output_dir, experiment, well, planarian, record_type)
|
||||
fieldnames = list(dict.fromkeys(k for r in records for k in r.keys()))
|
||||
|
||||
with open(filepath, "w", newline="", encoding="utf-8") as f:
|
||||
@@ -909,9 +911,9 @@ class ReductStoreClient:
|
||||
self,
|
||||
experiment: str,
|
||||
well: str,
|
||||
uuid: str,
|
||||
planarian: int = 0,
|
||||
record_type: str = "frame",
|
||||
uuid: str = "",
|
||||
start: Optional[datetime] = None,
|
||||
stop: Optional[datetime] = None,
|
||||
) -> tuple:
|
||||
@@ -923,7 +925,15 @@ class ReductStoreClient:
|
||||
tuple (contenu_csv_str, nb_lignes)
|
||||
"""
|
||||
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:
|
||||
return "", 0
|
||||
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):
|
||||
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:
|
||||
|
||||
@@ -1,54 +1,80 @@
|
||||
# tasks.py
|
||||
from celery import shared_task
|
||||
from celery import shared_task, group
|
||||
from celery.utils.log import get_task_logger
|
||||
|
||||
from django.conf import settings
|
||||
from asgiref.sync import async_to_sync
|
||||
from modules.planarian_metrics import ReductStoreClient
|
||||
from scanner.models import SessionExperiment
|
||||
from scanner.models import SessionExperiment, get_uuid_from_session, Experiment
|
||||
from .models import ExperimentConfig
|
||||
|
||||
from .export_service import export_csv_file_sync
|
||||
|
||||
logger = get_task_logger(__name__)
|
||||
|
||||
def _get_reduct_client() -> ReductStoreClient:
|
||||
"""Instancie le client ReductStore depuis les settings Django."""
|
||||
return ReductStoreClient(url=settings.REDUCTSTORE_URL, token=settings.REDUCTSTORE_TOKEN)
|
||||
|
||||
@shared_task(bind=True, autoretry_for=(Exception,), retry_backoff=True, retry_kwargs={"max_retries": 3},)
|
||||
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
|
||||
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
|
||||
async def _do_export(well, planarian, record_type):
|
||||
client = _get_reduct_client()
|
||||
|
||||
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:
|
||||
jobs = []
|
||||
configs = (ExperimentConfig.objects.filter(experiment_key_id=experiment.id).order_by("well"))
|
||||
for conf in configs:
|
||||
well = conf.well
|
||||
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 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
|
||||
def export_session_metrics(session):
|
||||
experiments = SessionExperiment.experiment_by_session(session.id)
|
||||
def export_session_metrics_task(session_id):
|
||||
experiments = SessionExperiment.experiment_by_session(session_id, active=False)
|
||||
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>
|
||||
<span class="w3-margin-left-5"> $> {{ export_csv_destination }}
|
||||
</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 %}
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
@@ -34,6 +40,7 @@
|
||||
{% block js_footer %}
|
||||
{{ block.super }}
|
||||
<script>
|
||||
const export_msg = "{% trans "Confirmer l'export des vidéos. ATTENTION le processus peut être long!" %}";
|
||||
// Auto-dismiss messages after 5 seconds
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const messages = document.querySelectorAll('.alert');
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
<input type="hidden" id="_sid" name="_sid" value="{{ current_session.id }}">
|
||||
<input type="hidden" id="_expid" name="_expid" value="{{ current_experiment.id }}">
|
||||
|
||||
|
||||
<div class="w3-card-4 w3-border w3-round w3-round-large">
|
||||
<header class="w3-container w3-blue w3-round w3-round-top-large">
|
||||
<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.system_stats import get_cached_stats, start_background_updater
|
||||
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 .models import ExperimentConfig
|
||||
|
||||
@@ -82,7 +84,6 @@ def get_active_session(request, session_id=None, experiment_id=None):
|
||||
# ---------------------------------------------------------------------------
|
||||
# Vue :Export CSV depuis ReductStore
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@login_required
|
||||
@csrf_exempt
|
||||
def export_metrics(request):
|
||||
@@ -92,48 +93,13 @@ def export_metrics(request):
|
||||
if action=='experiment_csv':
|
||||
experiment = models.Experiment.objects.filter(pk=pid).first()
|
||||
if experiment:
|
||||
export_experiment_metrics(experiment)
|
||||
return JsonResponse({"state": True})
|
||||
export_experiment_metrics_task.delay(experiment.id) # @UndefinedVariable
|
||||
return JsonResponse({"success": True, "msg": str(_("Métrics en cours de téléchargement"))})
|
||||
if action=='session_csv':
|
||||
session = models.Session.objects.filter(pk=pid).first()
|
||||
if session:
|
||||
export_session_metrics(session)
|
||||
return JsonResponse({"state": True})
|
||||
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
|
||||
if pid:
|
||||
export_session_metrics_task(pid) # @UndefinedVariable
|
||||
return JsonResponse({"success": True, "msg": str(_("Métrics en cours de téléchargement"))})
|
||||
return JsonResponse({"success": False, "msg": str(_("Erreur: les métrics non pas été téléchargés"))})
|
||||
|
||||
|
||||
@login_required
|
||||
@@ -148,11 +114,34 @@ def export_csv_view(request):
|
||||
}
|
||||
if request.method == 'POST':
|
||||
valid = request.POST.get('valid')
|
||||
if valid == 'ok':
|
||||
csv_content, filename = export_csv(request)
|
||||
session = session_context['current_session']
|
||||
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:
|
||||
response = FileResponse(csv_content, content_type="text/csv; charset=utf-8")
|
||||
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
|
||||
|
||||
messages.warning(request, _("Aucune donnée trouvée."))
|
||||
|
||||
@@ -258,7 +258,6 @@ class Experiment(models.Model):
|
||||
def by_identifier(cls, identifier):
|
||||
return Experiment.objects.filter(identifier__exact=identifier).first()
|
||||
|
||||
|
||||
class Meta:
|
||||
ordering = ['-created', ]
|
||||
verbose_name = _("Expérience")
|
||||
@@ -376,15 +375,21 @@ def delete_periodic_task(sender, instance, **kwargs):
|
||||
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):
|
||||
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)
|
||||
experiment = models.ForeignKey(Experiment, verbose_name=_("Expérience"), on_delete=models.CASCADE, null=True, blank=True, related_name="session_experiments")
|
||||
|
||||
|
||||
@classmethod
|
||||
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') ]
|
||||
|
||||
|
||||
@classmethod
|
||||
def uuid_from_session(cls, sid):
|
||||
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)
|
||||
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:
|
||||
ordering = ['session',]
|
||||
unique_together = ["session", "experiment"]
|
||||
|
||||
@@ -39,6 +39,7 @@ def stats_view(request):
|
||||
except Exception as e:
|
||||
return JsonResponse({"error": str(e)}, status=500)
|
||||
|
||||
|
||||
def global_context(request, **ctx):
|
||||
default_multiwell = models.MultiWell.objects.filter(default=True).first()
|
||||
conf = ScannerConstants().get()
|
||||
@@ -60,11 +61,12 @@ def global_context(request, **ctx):
|
||||
def admin_view(request):
|
||||
return render(request, "scanner/iframe.html", context=global_context(request, link='/admin/'))
|
||||
|
||||
@login_required
|
||||
|
||||
@login_required
|
||||
def admin_session_view(request):
|
||||
return render(request, "scanner/iframe.html", context=global_context(request, link='/admin/scanner/session/'))
|
||||
|
||||
|
||||
@login_required
|
||||
def admin_experiment_view(request):
|
||||
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):
|
||||
return render(request, "scanner/iframe.html", context=global_context(request, link='/admin/planarian/experimentconfig/'))
|
||||
|
||||
|
||||
@login_required
|
||||
def admin_periodictask_view(request):
|
||||
return render(request, "scanner/iframe.html", context=global_context(request, link='/admin/django_celery_beat/periodictask/'))
|
||||
|
||||
|
||||
@login_required
|
||||
@user_passes_test(is_staff_or_admin)
|
||||
def reductstore_view(request):
|
||||
return render(request, "scanner/redirection.html", context=global_context(request, link=f'http://{settings.LOCAL_IP_SERVER}:8383/'))
|
||||
|
||||
|
||||
@login_required
|
||||
@user_passes_test(is_staff_or_admin)
|
||||
def adminer_view(request):
|
||||
@@ -92,21 +97,25 @@ def adminer_view(request):
|
||||
def portainer_view(request):
|
||||
return render(request, "scanner/redirection.html", context=global_context(request, link=f'http://{settings.LOCAL_IP_SERVER}:9000/'))
|
||||
|
||||
|
||||
@login_required
|
||||
@user_passes_test(is_staff_or_admin)
|
||||
def supervisor_view(request):
|
||||
return render(request, "scanner/redirection.html", context=global_context(request, link=f'http://{settings.LOCAL_IP_SERVER}:9001/'))
|
||||
|
||||
|
||||
@login_required
|
||||
@user_passes_test(is_staff_or_admin)
|
||||
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'))
|
||||
|
||||
|
||||
@login_required
|
||||
@user_passes_test(is_staff_or_admin)
|
||||
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'))
|
||||
|
||||
|
||||
def documentation(request, template=None):
|
||||
ctx = dict(
|
||||
choice_title=_("Documentation")
|
||||
@@ -225,6 +234,7 @@ def export_medias(request):
|
||||
)
|
||||
return render(request, "scanner/export_medias.html", context=global_context(request, **ctx))
|
||||
|
||||
|
||||
@require_POST
|
||||
@csrf_exempt
|
||||
def download_api(request):
|
||||
@@ -286,6 +296,7 @@ def replay_view(request):
|
||||
)
|
||||
return render(request, "scanner/replay.html", context=global_context(request, **ctx))
|
||||
|
||||
|
||||
@require_POST
|
||||
@csrf_exempt
|
||||
def export_api(request):
|
||||
@@ -295,10 +306,10 @@ def export_api(request):
|
||||
try:
|
||||
if action == 'export_images':
|
||||
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':
|
||||
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:
|
||||
return JsonResponse({"state": False, "success": False, "msg": str(_("Erreur d'exportation")), })
|
||||
|
||||
|
||||
Reference in New Issue
Block a user