diff --git a/etc/3-install-samba-client.sh b/etc/3-install-samba-client.sh index 5a935d0..6893cfb 100755 --- a/etc/3-install-samba-client.sh +++ b/etc/3-install-samba-client.sh @@ -50,6 +50,8 @@ sudo bash -c "echo '//$samba_server/$public_share /mnt/samba/public cifs guest,u echo "Montage des partages Samba..." sudo mount -a +sudo mkdir -p /mnt/samba/public/images /mnt/samba/public/videos + # Vérification du montage echo "Vérification des partages montés :" df -h | grep samba diff --git a/test_tube_scanner/home/settings.py b/test_tube_scanner/home/settings.py index 4ab3a33..a3ebb59 100644 --- a/test_tube_scanner/home/settings.py +++ b/test_tube_scanner/home/settings.py @@ -390,7 +390,9 @@ DATETIME_FORMAT = '%d-%m-%Y-%m %H:%M:%S' EXPORTS_LOCAL_PATH = config("EXPORTS_LOCAL_PATH") EXPORT_REMOTE_PATH = config("EXPORT_REMOTE_PATH") + EXPORT_DESTINATIONS = ["local", "remote"] +#EXPORT_DESTINATIONS = ["remote"] # only remote TEST_VIDEOFILE = False diff --git a/test_tube_scanner/scanner/export_tasks.py b/test_tube_scanner/scanner/export_tasks.py index 8af0bb9..734d02d 100644 --- a/test_tube_scanner/scanner/export_tasks.py +++ b/test_tube_scanner/scanner/export_tasks.py @@ -144,6 +144,10 @@ def _copy_to_destinations(source_path: str, filename: str) -> dict: Retourne un dict avec les chemins effectivement écrits. """ results = {"local": None, "remote": None} + + filename = filename.replace(':', '_') + + logger.info("%s %s", source_path, filename) for dest in settings.EXPORT_DESTINATIONS: @@ -151,10 +155,10 @@ def _copy_to_destinations(source_path: str, filename: str) -> dict: # Déjà sur place, rien à copier results["local"] = source_path - elif dest == "remote": - remote_path = os.path.join(settings.EXPORT_REMOTE_DIR, filename) + elif dest == "remote": + remote_path = os.path.join(settings.EXPORT_REMOTE_PATH, filename) try: - if not remote_mount_available(settings.EXPORT_REMOTE_DIR): + if not remote_mount_available(settings.EXPORT_REMOTE_PATH): logger.warning("Partage Samba non disponible, copie ignorée") results["remote_error"] = "Montage indisponible" continue @@ -248,6 +252,7 @@ async def export_images_zip( # --- Génération du ZIP --- max_zip_bytes = max_zip_size_mb * 1024 * 1024 if max_zip_size_mb > 0 else 0 ts_s = unix_timestamp_to_iso(start_ts) + zip_filename = f"{uuid}_{ts_s}.zip" zip_path = os.path.join(settings.EXPORTS_LOCAL_PATH, 'images', zip_filename) os.makedirs(os.path.dirname(zip_path), exist_ok=True) @@ -297,14 +302,16 @@ async def export_images_zip( current_offset += size ## Copie vers les destinations (local + Samba) - #destinations = _copy_to_destinations(zip_path, zip_filename) + filename = os.path.join('images', zip_filename) + + destinations = _copy_to_destinations(zip_path, filename) return { "status": "success", "zip_path": zip_path, "frames_written": written, "frames_skipped": skipped, "jpeg_quality": jpeg_quality, - #"destinations": destinations, + "destinations": destinations, } except Exception as exc: @@ -461,8 +468,10 @@ async def export_video_mp4( return {"status": "error", "message": f"Fichier {opencv_video_type} non créé"} ## Copie vers les destinations (local + Samba) - #filename = os.path.basename(video_path) - #destinations = _copy_to_destinations(video_path, filename) + filename = os.path.basename(video_path) + filename = os.path.join('videos', filename) + + destinations = _copy_to_destinations(video_path, filename) return { "status": "success", "video_path": video_path, @@ -470,7 +479,7 @@ async def export_video_mp4( "frames_skipped": skipped, "frame_rate": frame_rate, "file_size_mb": round(os.path.getsize(video_path) / 1024 / 1024, 2), - #"destinations": destinations, + "destinations": destinations, } except Exception as exc: diff --git a/test_tube_scanner/scanner/models.py b/test_tube_scanner/scanner/models.py index abaacd1..da3a86e 100644 --- a/test_tube_scanner/scanner/models.py +++ b/test_tube_scanner/scanner/models.py @@ -227,6 +227,7 @@ class Experiment(models.Model): created = models.DateTimeField(_("Date de création"), default=timezone.now) started = models.DateTimeField (_("Date de début"), null=True, blank=True) finished = models.DateTimeField (_("Date de fin"), null=True, blank=True) + class Meta: ordering = ['-created', ] @@ -270,7 +271,12 @@ class Session(models.Model): null=True, blank=True, on_delete=models.SET_NULL, related_name="scanning_session") scanning_finished_at = models.DateTimeField(_("Balayage terminé à"), null=True, blank=True) - + + @classmethod + def get_session(self, sid): + return Session.objects.filter(pk=sid).first() + + class Meta: ordering = ['-created', ] verbose_name = _("Session d'expérience") diff --git a/test_tube_scanner/scanner/templates/scanner/images.html b/test_tube_scanner/scanner/templates/scanner/images.html index d22862c..93851eb 100644 --- a/test_tube_scanner/scanner/templates/scanner/images.html +++ b/test_tube_scanner/scanner/templates/scanner/images.html @@ -9,19 +9,26 @@ {% trans "Retour accueil" %}
{% csrf_token %} - + {% for s in sessions %} - + {% endfor %} - {% if cursid %} - {% multiwell_cards cursid experiments %} +
+ {% for ss in experiments %} + + + {% endfor %} +
+ {% if current_session.id and current_experiment %} + {% multiwell_cards current_session.id current_experiment %} {% endif %}
- {% if cursid %} + {% if current_session.id %} - {% trans "Exporter l'ensemble des images" %} + {% trans "Exporter l'ensemble des images" %}
+ {{ export_destination }}
{% endif %} {% endblock %} @@ -95,14 +102,15 @@ action: 'export_images', sid: sid, }) - }).then(res => { - console.log(res); - - }).catch(error => { + }) + .then(response => response.json()) + .then(res => { + alert(res.msg); + }) + .catch(error => { console.error('Erreur:', error); }); } - setGridColumns(columns); if (uuid_btn) handleMultiwell(uuid_btn); diff --git a/test_tube_scanner/scanner/templates/scanner/replay.html b/test_tube_scanner/scanner/templates/scanner/replay.html index 6ef00e0..bd64415 100644 --- a/test_tube_scanner/scanner/templates/scanner/replay.html +++ b/test_tube_scanner/scanner/templates/scanner/replay.html @@ -11,18 +11,26 @@
{% csrf_token %} - {% if cursid %} - {% multiwell_cards cursid experiments %} +
+ {% for ss in experiments %} + + + {% endfor %} +
+ + {% if current_session.id and current_experiment %} + {% multiwell_cards current_session.id current_experiment %} {% endif %}
- {% if cursid %} + {% if current_session.id %} - {% trans "Exporter l'ensemble des vidéos" %} + {% trans "Exporter l'ensemble des vidéos" %}
+ {{ export_destination }}
{% endif %} {% endblock %} @@ -80,9 +88,12 @@ action: 'export_videos', sid: sid, }) - }).then(res => { - console.log(res); - }).catch(error => { + }) + .then(response => response.json()) + .then(res => { + alert(res.msg); + }) + .catch(error => { console.error('Erreur:', error); }); } diff --git a/test_tube_scanner/scanner/templatetags/scanner_tags.py b/test_tube_scanner/scanner/templatetags/scanner_tags.py index 9a66f63..71fc2de 100644 --- a/test_tube_scanner/scanner/templatetags/scanner_tags.py +++ b/test_tube_scanner/scanner/templatetags/scanner_tags.py @@ -5,22 +5,19 @@ from django.utils.html import mark_safe register = template.Library() @register.simple_tag -def multiwell_cards(sid, experiments): +def multiwell_cards(sid, experiment): multiwells = [] - for obs in experiments: - row_def = obs.multiwell.row_def.split(',') - multiwells.append( - f''' -
{obs.title}
-
- ''') - for row in range(obs.multiwell.rows): - for col in range(obs.multiwell.cols): - btn = f'{row_def[row]}{col+1}' - uuid = f'{sid}-{obs.multiwell.position}-{btn}' - multiwells.append(f"""""") - multiwells.append('''
''') + row_def = experiment.multiwell.row_def.split(',') + multiwells.append( + f''' +
{experiment.title}
+
+ ''') + for row in range(experiment.multiwell.rows): + for col in range(experiment.multiwell.cols): + btn = f'{row_def[row]}{col+1}' + uuid = f'{sid}-{experiment.multiwell.position}-{btn}' + multiwells.append(f"""""") + multiwells.append('''
''') return mark_safe("\n".join(multiwells)) - - diff --git a/test_tube_scanner/scanner/views.py b/test_tube_scanner/scanner/views.py index 7100e3a..10e0c55 100644 --- a/test_tube_scanner/scanner/views.py +++ b/test_tube_scanner/scanner/views.py @@ -35,14 +35,13 @@ def stats_view(request): def global_context(request, **ctx): default_multiwell = models.MultiWell.objects.filter(default=True).first() conf = ScannerConstants().get() - - print(conf, type(conf)) return dict( app_title=settings.APP_TITLE, app_sub_title=settings.APP_SUB_TITLE, domain_server=settings.DOMAIN_SERVER, conf=conf, default_position = default_multiwell.position or 'HD', + export_destination=settings.EXPORT_DESTINATIONS, **ctx ) @@ -97,6 +96,17 @@ def calibration_view(request): return render(request, "scanner/calibration.html", context=global_context(request, **ctx)) +def get_not_active_experiments(session, expid=None): + if session: + experiments = models.SessionExperiment.experiment_by_session(session.id, active=False) or [] + if experiments and not expid: + return experiments, experiments[0] + for e in experiments: + if expid == str(e.id): + return experiments, e + return [], None + + ## images def get_images(uuid): oldest, latest, n, images = 0, 0, 0, [] @@ -124,17 +134,21 @@ def get_images(uuid): @login_required def images_view(request): - cursid, duration, uuid, images = 0, 0, "", [] + cursid, expid, duration, uuid, images = 0, None, 0, "", [] if request.method == 'POST': cursid = request.POST.get('_sid') + expid = request.POST.get('_expid') uuid = request.POST.get('_multiwell') duration, images = get_images(uuid) + current_session = models.Session.get_session(cursid) + experiments, current_experiment = get_not_active_experiments(current_session, expid) ctx = dict( choice_title=_("Gestionnaire d'images"), sessions=models.Session.objects.filter(active=False).all(), - experiments=models.SessionExperiment.experiment_by_session(cursid, active=False), - cursid=int(cursid), + experiments=experiments or [], + current_session=current_session, + current_experiment=current_experiment, images=images, uuid=uuid, duration=duration, @@ -164,20 +178,24 @@ def get_video(uuid): @login_required def replay_view(request): - cursid, oldest, latest, uuid, image = 0, 0, 0, "", "" + cursid, expid, oldest, latest, uuid, image = 0, None, 0, 0, "", "" if request.method == 'POST': cursid = request.POST.get('_sid') + expid = request.POST.get('_expid') uuid = request.POST.get('_multiwell') if uuid: oldest, latest, image = get_video(uuid) - + + current_session = models.Session.get_session(cursid) + experiments, current_experiment = get_not_active_experiments(current_session, expid) ctx = dict( choice_title=_("Gestionnaire de vidéos"), ws_route=settings.REPLAY_WEBSOCKET_ROUTE, sessions=models.Session.objects.filter(active=False).all(), - experiments=models.SessionExperiment.experiment_by_session(cursid, active=False), - cursid=int(cursid), + experiments=experiments or [], + current_session=current_session, + current_experiment=current_experiment, image=image, uuid=uuid, oldest=oldest, @@ -193,12 +211,12 @@ def export_api(request): action = data.get("action") if action == 'export_images': - job_zip = export_all_images(session_id) - return JsonResponse({"state": True, "tasks": job_zip}) + export_all_images(session_id) + return JsonResponse({"success": True, "msg": str(_("Images téléchargées"))}) elif action == 'export_videos': - job_mp4 = export_all_videos(session_id) - return JsonResponse({"state": True, "tasks": job_mp4}) + export_all_videos(session_id) + return JsonResponse({"success": True, "msg": str(_("Vidéos téléchargées"))}) else: - return JsonResponse({"state": False}) + return JsonResponse({"success": False, "msg": str(_("Erreur d'exportation"))})