export csv
This commit is contained in:
@@ -9,5 +9,3 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -13,13 +13,14 @@ class ExperimentConfigAdmin(admin.ModelAdmin):
|
|||||||
list_display = ("experiment", "well", "active", "px_per_mm", "fps",
|
list_display = ("experiment", "well", "active", "px_per_mm", "fps",
|
||||||
"thresh_immobile", "thresh_mobile",
|
"thresh_immobile", "thresh_mobile",
|
||||||
"photo_mode", "chemo_strength", "created_at", )
|
"photo_mode", "chemo_strength", "created_at", )
|
||||||
|
|
||||||
list_filter = ("experiment", "photo_mode", "tube_axis")
|
list_filter = ("experiment", "photo_mode", "tube_axis")
|
||||||
search_fields = ("experiment", "well", "description")
|
search_fields = ("experiment", "well", "description")
|
||||||
ordering = ("-created_at",)
|
ordering = ("-created_at",)
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_("Identification"), {
|
(_("Identification"), {
|
||||||
"fields": ("experiment", "well", "description"),
|
"fields": ("experiment", "well", "active", "description"),
|
||||||
}),
|
}),
|
||||||
(_("Calibration optique: générée lors de la calibration"), {
|
(_("Calibration optique: générée lors de la calibration"), {
|
||||||
"fields": ("px_per_mm", "fps", "well_radius_mm"),
|
"fields": ("px_per_mm", "fps", "well_radius_mm"),
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ class CsvImportForm(forms.Form):
|
|||||||
label=_("Écraser les configurations existantes"),
|
label=_("Écraser les configurations existantes"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def clean_csv_file(self):
|
def clean_csv_file(self):
|
||||||
f = self.cleaned_data["csv_file"]
|
f = self.cleaned_data["csv_file"]
|
||||||
try:
|
try:
|
||||||
@@ -77,8 +78,14 @@ class ExportCsvForm(forms.Form):
|
|||||||
"""Formulaire de demande d'export CSV depuis ReductStore."""
|
"""Formulaire de demande d'export CSV depuis ReductStore."""
|
||||||
|
|
||||||
experiment = forms.CharField(label=_("Expérience"), max_length=100)
|
experiment = forms.CharField(label=_("Expérience"), max_length=100)
|
||||||
well = forms.CharField(label=_("Puits"), max_length=20)
|
#well = forms.CharField(label=_("Puits"), max_length=20)
|
||||||
planarian = forms.IntegerField(label=_("Index planaire"), initial=0, min_value=0)
|
well = forms.ModelChoiceField(
|
||||||
|
queryset=models.Well.objects.filter().order_by('name').all(),
|
||||||
|
label=_('Puits'),
|
||||||
|
widget=forms.Select(attrs={'class': 'w3-select',}),
|
||||||
|
required=True,initial="D1",
|
||||||
|
)
|
||||||
|
planarian = forms.IntegerField(label=_("Index planaire"), initial=0, min_value=0, max_value=20)
|
||||||
record_type = forms.ChoiceField(
|
record_type = forms.ChoiceField(
|
||||||
label=_("Type d'enregistrement"),
|
label=_("Type d'enregistrement"),
|
||||||
choices=[("frame", _("Frame par frame")), ("summary", _("Résumé"))],
|
choices=[("frame", _("Frame par frame")), ("summary", _("Résumé"))],
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
<div class="w3-margin-left w3-margin-bottom">
|
<div class="w3-margin-left w3-margin-bottom">
|
||||||
{% for ss in experiments %}
|
{% for ss in experiments %}
|
||||||
<input class="" type="radio" name="_expid" value="{{ ss.id }}" {% if ss.id == current_experiment.id %}checked{% endif %} onchange="this.form.submit()" >
|
<input class="" type="radio" name="_expid" value="{{ ss.id }}" {% if ss.id == current_experiment.id %}checked{% endif %} onchange="this.form.submit()" >
|
||||||
<label>{{ ss.multiwell }}</label>
|
<label>{{ ss }}</label>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -54,14 +54,6 @@
|
|||||||
</header>
|
</header>
|
||||||
<div class="w3-container w3-padding">
|
<div class="w3-container w3-padding">
|
||||||
<div class="w3-row w3-row-padding">
|
<div class="w3-row w3-row-padding">
|
||||||
<!-- identifier -->
|
|
||||||
<div class="w3-half">
|
|
||||||
<label class="w3-text-teal"><b>{{ form.identifier.label }}</b></label>
|
|
||||||
{{ form.identifier }}
|
|
||||||
{% if form.identifier.errors %}
|
|
||||||
<span class="w3-text-red w3-small">{{ form.identifier.errors|join:", " }}</span>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- author -->
|
<!-- author -->
|
||||||
<div class="w3-half w3-margin-bottom">
|
<div class="w3-half w3-margin-bottom">
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="w3-container w3-padding" style="max-width:1200px; margin:auto;">
|
<div class="w3-container w3-padding" style="max-width:1200px; margin:auto;">
|
||||||
|
|
||||||
<!-- En-tête -->
|
<!-- En-tête -->
|
||||||
<div class="w3-panel w3-teal w3-round-large w3-padding-16 w3-margin-bottom">
|
<div class="w3-panel w3-teal w3-round-large w3-padding-16 w3-margin-bottom">
|
||||||
<div class="w3-row">
|
<div class="w3-row">
|
||||||
@@ -21,7 +20,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Messages Django -->
|
<!-- Messages Django -->
|
||||||
{% for message in messages %}
|
{% for message in messages %}
|
||||||
<div class="w3-panel w3-round alert
|
<div class="w3-panel w3-round alert
|
||||||
@@ -137,15 +135,17 @@
|
|||||||
title="{% trans 'Interactions inter-individus' %}">🔀</span>
|
title="{% trans 'Interactions inter-individus' %}">🔀</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<!-- Date de création -->
|
<!-- Date de création -->
|
||||||
<td class="w3-hide-small w3-small w3-text-grey">
|
<td class="w3-hide-small w3-small w3-text-grey">
|
||||||
{{ cfg.created_at|date:"d/m/Y H:i" }}
|
{{ cfg.created_at|date:"d/m/Y H:i" }}
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<!-- Actions -->
|
<!-- Actions -->
|
||||||
<td style="white-space:nowrap;">
|
<td style="white-space:nowrap;">
|
||||||
|
{% if current_session and current_experiment %}
|
||||||
|
<a href="{% url 'planarian:experiment-edit' cfg.pk current_session.id current_experiment.id %}"
|
||||||
|
{% else %}
|
||||||
<a href="{% url 'planarian:experiment-edit' cfg.pk %}"
|
<a href="{% url 'planarian:experiment-edit' cfg.pk %}"
|
||||||
|
{% endif %}
|
||||||
class="w3-button w3-small w3-teal w3-round"
|
class="w3-button w3-small w3-teal w3-round"
|
||||||
title="{% trans 'Modifier' %}">✏</a>
|
title="{% trans 'Modifier' %}">✏</a>
|
||||||
<a href="{% url 'planarian:export-csv' %}?experiment={{ cfg.experiment }}&well={{ cfg.well }}"
|
<a href="{% url 'planarian:export-csv' %}?experiment={{ cfg.experiment }}&well={{ cfg.well }}"
|
||||||
|
|||||||
@@ -1,39 +1,10 @@
|
|||||||
{% extends "planarian/base.html" %}
|
{% extends "planarian/base.html" %}
|
||||||
|
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<div class="w3-container w3-padding" style="max-width:760px; margin:auto;">
|
<div class="w3-container w3-padding" style="max-width:760px; margin:auto;">
|
||||||
|
{% if current_session and current_experiment %}
|
||||||
<!-- En-tête -->
|
{% include "inc/alert.html" %}
|
||||||
<div class="w3-panel w3-blue w3-round-large w3-padding-16 w3-margin-bottom">
|
|
||||||
<div class="w3-row">
|
|
||||||
<div class="w3-col s12 m8 w3-bar-item">
|
|
||||||
<span class="w3-xlarge">
|
|
||||||
📥 {% trans "Exporter les données vers CSV" %}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="w3-col s12 m4 w3-bar-item w3-right-align" style="padding-top:6px;">
|
|
||||||
<a href="{% url 'planarian:experiment-list' %}"
|
|
||||||
class="w3-button w3-white w3-round w3-text-blue">
|
|
||||||
← {% trans "Retour à la liste" %}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Messages Django -->
|
|
||||||
{% for message in messages %}
|
|
||||||
<div class="w3-panel w3-round
|
|
||||||
{% if message.tags == 'success' %}w3-green
|
|
||||||
{% elif message.tags == 'error' %}w3-red
|
|
||||||
{% elif message.tags == 'warning' %}w3-orange
|
|
||||||
{% else %}w3-blue{% endif %}">
|
|
||||||
<p>{{ message }}</p>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
<!-- Explication -->
|
<!-- Explication -->
|
||||||
<div class="w3-panel w3-pale-blue w3-round w3-margin-bottom">
|
<div class="w3-panel w3-pale-blue w3-round w3-margin-bottom">
|
||||||
<p>
|
<p>
|
||||||
@@ -44,89 +15,54 @@
|
|||||||
{% trans "Colonnes exportées compatibles EthoVision XT : distance, vitesse, états de mobilité, thigmotactisme." %}
|
{% trans "Colonnes exportées compatibles EthoVision XT : distance, vitesse, états de mobilité, thigmotactisme." %}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Formulaire -->
|
<!-- Formulaire -->
|
||||||
<form method="post" novalidate>
|
<form method="post" novalidate>
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<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 }}">
|
||||||
|
|
||||||
{% if form.non_field_errors %}
|
|
||||||
<div class="w3-panel w3-red w3-round">
|
|
||||||
{% for error in form.non_field_errors %}
|
|
||||||
<p>⚠ {{ error }}</p>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<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>
|
||||||
</header>
|
</header>
|
||||||
<div class="w3-container w3-padding-24">
|
<div class="w3-container w3-padding-24">
|
||||||
|
|
||||||
<!-- Ligne 1 : expérience + puits -->
|
<!-- Ligne 1 : expérience + puits -->
|
||||||
<div class="w3-row-padding w3-margin-bottom">
|
<div class="w3-row-padding w3-margin-bottom">
|
||||||
|
|
||||||
<div class="w3-col m7 s12 w3-margin-bottom">
|
<div class="w3-col m7 s12 w3-margin-bottom">
|
||||||
<label class="w3-text-blue"><b>{{ form.experiment.label }}</b></label>
|
<label class="w3-text-blue"><b>{% trans "Expérience" %}</b></label>
|
||||||
{% if form.experiment.help_text %}
|
<input type="text" name="experiment" value="{{ current_experiment.identifier }}" readonly/>
|
||||||
<p class="w3-small w3-text-grey" style="margin:0 0 4px;">{{ form.experiment.help_text }}</p>
|
|
||||||
{% endif %}
|
|
||||||
<select id="experiment-list">
|
|
||||||
{% for cfg in experiments %}
|
|
||||||
<option value="{{ cfg.identifier}}" {% if current_experiment.identifier == cfg.identifier %}selected{% endif %} >{{ cfg.identifier }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
{% if form.experiment.errors %}
|
|
||||||
<!--span class="w3-text-red w3-small">{{ form.experiment.errors|join:", " }}</span-->
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="w3-col m5 s12 w3-margin-bottom">
|
<div class="w3-col m5 s12 w3-margin-bottom">
|
||||||
<label class="w3-text-blue"><b>{{ form.well.label }}</b></label>
|
<label class="w3-text-blue"><b>{% trans "Puits" %}</b></label>
|
||||||
<select id="id_well">
|
<select name="well">
|
||||||
{% for w in well_choices %}
|
{% for well in well_choices %}
|
||||||
<option value="{{ w.name }}">{{ w.name }}</option>
|
<option value="{{ well.name }}">{{ well.name }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
{% if form.well.errors %}
|
|
||||||
<span class="w3-text-red w3-small">{{ form.well.errors|join:", " }}</span>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Ligne 2 : planaire + type d'enregistrement -->
|
<!-- Ligne 2 : planaire + type d'enregistrement -->
|
||||||
<div class="w3-row-padding w3-margin-bottom">
|
<div class="w3-row-padding w3-margin-bottom">
|
||||||
|
|
||||||
<div class="w3-col m4 s12 w3-margin-bottom">
|
<div class="w3-col m4 s12 w3-margin-bottom">
|
||||||
<label class="w3-text-blue"><b>{{ form.planarian.label }}</b></label>
|
<label class="w3-text-blue"><b>{% trans "Index planaire" %}</b></label>
|
||||||
<p class="w3-small w3-text-light-grey" style="margin:0 0 4px;">
|
<p class="w3-small w3-text-light-grey" style="margin:0 0 4px;">
|
||||||
{% trans "Index du planaire dans le puits (commence à 0)" %}
|
{% trans "Index du planaire dans le puits (commence à 0)" %}
|
||||||
</p>
|
</p>
|
||||||
{{ form.planarian }}
|
<input type="number" name="planarian" step="1" min="0" max="20" value="{{ planarian }}" />
|
||||||
{% if form.planarian.errors %}
|
|
||||||
<span class="w3-text-red w3-small">{{ form.planarian.errors|join:", " }}</span>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="w3-col m8 s12 w3-margin-bottom">
|
<div class="w3-col m8 s12 w3-margin-bottom">
|
||||||
<label class="w3-text-blue"><b>{{ form.record_type.label }}</b></label>
|
<label class="w3-text-blue"><b>{% trans "Type d'enregistrement" %}</b></label>
|
||||||
<p class="w3-small w3-text-light-grey" style="margin:0 0 4px;">
|
<p class="w3-small w3-text-light-grey" style="margin:0 0 4px;">
|
||||||
{% trans "Frame par frame : une ligne par image. Résumé : métriques agrégées de la session." %}
|
{% trans "Frame par frame : une ligne par image. Résumé : métriques agrégées de la session." %}
|
||||||
</p>
|
</p>
|
||||||
<!-- Radio buttons stylisés W3 -->
|
<select name="record_type">
|
||||||
<div class="w3-row-padding" style="margin-top:6px;">
|
<option value="frame">{% trans "Frame par frame" %}</option>
|
||||||
{{ form.record_type }}
|
<option value="summary">{% trans "Résumé" %}</option>
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
{% if form.record_type.errors %}
|
|
||||||
<span class="w3-text-red w3-small">{{ form.record_type.errors|join:", " }}</span>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Ligne 3 : plage temporelle (optionnel) -->
|
<!-- Ligne 3 : plage temporelle (optionnel) -->
|
||||||
<div class="w3-border-top w3-padding-top w3-margin-top">
|
<div class="w3-border-top w3-padding-top w3-margin-top">
|
||||||
<p class="w3-text-blue-grey">
|
<p class="w3-text-blue-grey">
|
||||||
@@ -136,36 +72,26 @@
|
|||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
<div class="w3-row-padding">
|
<div class="w3-row-padding">
|
||||||
|
|
||||||
<div class="w3-col m6 s12 w3-margin-bottom">
|
<div class="w3-col m6 s12 w3-margin-bottom">
|
||||||
<label class="w3-text-blue-grey">{{ form.start_dt.label }}</label>
|
<label class="w3-text-blue-grey">{% trans "Début (UTC)" %}</label>
|
||||||
{{ form.start_dt }}
|
<input type="datetime-local" class="datetime" name="start_dt">
|
||||||
{% if form.start_dt.errors %}
|
|
||||||
<span class="w3-text-red w3-small">{{ form.start_dt.errors|join:", " }}</span>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="w3-col m6 s12 w3-margin-bottom">
|
<div class="w3-col m6 s12 w3-margin-bottom">
|
||||||
<label class="w3-text-blue-grey">{{ form.stop_dt.label }}</label>
|
<label class="w3-text-blue-grey">{% trans "Fin (UTC)" %}</label>
|
||||||
{{ form.stop_dt }}
|
<input type="datetime-local" class="datetime" name="stop_dt">
|
||||||
{% if form.stop_dt.errors %}
|
</div>
|
||||||
<span class="w3-text-red w3-small">{{ form.stop_dt.errors|join:", " }}</span>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div><!-- fin padding -->
|
</div><!-- fin padding -->
|
||||||
</div><!-- fin card -->
|
</div><!-- fin card -->
|
||||||
|
|
||||||
<!-- Boutons -->
|
<!-- Boutons -->
|
||||||
<div class="w3-row-padding w3-margin-top">
|
<div class="w3-row-padding w3-margin-top">
|
||||||
<div class="w3-col s12">
|
<div class="w3-col s12">
|
||||||
<button type="submit" class="w3-button w3-blue w3-large w3-padding-large">
|
<button type="submit" name="valid" class="w3-button w3-blue w3-large w3-padding-large" value="ok">
|
||||||
📥 {% trans "Générer et télécharger le CSV" %}
|
📥 {% trans "Générer et télécharger le CSV" %}
|
||||||
</button>
|
</button>
|
||||||
<a href="{% url 'planarian:experiment-list' %}"
|
<a href="/"
|
||||||
class="w3-button w3-light-grey w3-round w3-large w3-padding-large w3-margin-left">
|
class="w3-button w3-light-grey w3-round w3-large w3-padding-large w3-margin-left">
|
||||||
✖ {% trans "Annuler" %}
|
✖ {% trans "Annuler" %}
|
||||||
</a>
|
</a>
|
||||||
@@ -173,7 +99,6 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<!-- Rappel des colonnes exportées -->
|
<!-- Rappel des colonnes exportées -->
|
||||||
<div class="w3-card w3-round-large w3-margin-top w3-border w3-round w3-round-large w3-margin-bottom">
|
<div class="w3-card w3-round-large w3-margin-top w3-border w3-round w3-round-large w3-margin-bottom">
|
||||||
<header class="w3-container w3-blue-grey w3-round w3-round-top-large">
|
<header class="w3-container w3-blue-grey w3-round w3-round-top-large">
|
||||||
@@ -209,9 +134,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div><!-- fin container -->
|
</div><!-- fin container -->
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
input[type="text"],
|
input[type="text"],
|
||||||
input[type="number"],
|
input[type="number"],
|
||||||
@@ -231,10 +154,10 @@
|
|||||||
outline: none;
|
outline: none;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
}
|
}
|
||||||
/* Masquer le radio natif, laisser le label stylisé */
|
|
||||||
.w3-button input[type="radio"] {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
{% else %}
|
||||||
|
<div class="w3-container w3-padding-64 w3-margin" style="max-width:760px; margin:auto;">
|
||||||
|
<h3 class="w3-panel w3-blue w3-round-xlarge w3-padding-64 w3-center"> {% trans "Choisir une session" %}</h3>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -0,0 +1,233 @@
|
|||||||
|
{% extends "planarian/base.html" %}
|
||||||
|
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<div class="w3-container w3-padding" style="max-width:760px; margin:auto;">
|
||||||
|
{% if current_session and current_experiment %}
|
||||||
|
<!-- En-tête -->
|
||||||
|
<div class="w3-panel w3-blue w3-round-large w3-padding-16 w3-margin-bottom">
|
||||||
|
<div class="w3-row">
|
||||||
|
<div class="w3-col s12 m8 w3-bar-item">
|
||||||
|
<span class="w3-xlarge">
|
||||||
|
📥 {% trans "Exporter les données vers CSV" %}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="w3-col s12 m4 w3-bar-item w3-right-align" style="padding-top:6px;">
|
||||||
|
<a href="/"
|
||||||
|
class="w3-button w3-white w3-round w3-text-blue">
|
||||||
|
← {% trans "Retour" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Messages Django -->
|
||||||
|
{% for message in messages %}
|
||||||
|
<div class="w3-panel w3-round
|
||||||
|
{% if message.tags == 'success' %}w3-green
|
||||||
|
{% elif message.tags == 'error' %}w3-red
|
||||||
|
{% elif message.tags == 'warning' %}w3-orange
|
||||||
|
{% else %}w3-blue{% endif %}">
|
||||||
|
<p>{{ message }}</p>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<!-- Explication -->
|
||||||
|
<div class="w3-panel w3-pale-blue w3-round w3-margin-bottom">
|
||||||
|
<p>
|
||||||
|
{% trans "Sélectionnez l'expérience, le puits et le type d'enregistrement à exporter." %}
|
||||||
|
{% trans "Le fichier CSV sera généré depuis ReductStore et téléchargé directement." %}
|
||||||
|
</p>
|
||||||
|
<p class="w3-small w3-text-grey" style="margin:0;">
|
||||||
|
{% trans "Colonnes exportées compatibles EthoVision XT : distance, vitesse, états de mobilité, thigmotactisme." %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Formulaire -->
|
||||||
|
<form method="post" novalidate>
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" id="_sid" name="_sid" value="{{ current_session.id }}">
|
||||||
|
<input type="hidden" id="_expid" name="_expid" value="{{ current_experiment.id }}">
|
||||||
|
|
||||||
|
|
||||||
|
{% if form.non_field_errors %}
|
||||||
|
<div class="w3-panel w3-red w3-round">
|
||||||
|
{% for error in form.non_field_errors %}
|
||||||
|
<p>⚠ {{ error }}</p>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<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>
|
||||||
|
</header>
|
||||||
|
<div class="w3-container w3-padding-24">
|
||||||
|
<!-- Ligne 1 : expérience + puits -->
|
||||||
|
<div class="w3-row-padding w3-margin-bottom">
|
||||||
|
|
||||||
|
<div class="w3-col m7 s12 w3-margin-bottom">
|
||||||
|
<label class="w3-text-blue"><b>{{ form.experiment.label }}</b></label>
|
||||||
|
{% if form.experiment.help_text %}
|
||||||
|
<p class="w3-small w3-text-grey" style="margin:0 0 4px;">{{ form.experiment.help_text }}</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{{ form.experiment }}
|
||||||
|
{% if form.experiment.errors %}
|
||||||
|
<!--span class="w3-text-red w3-small">{{ form.experiment.errors|join:", " }}</span-->
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="w3-col m5 s12 w3-margin-bottom">
|
||||||
|
<label class="w3-text-blue"><b>{{ form.well.label }}</b></label>
|
||||||
|
{{ form.well }}
|
||||||
|
{% if form.well.errors %}
|
||||||
|
<span class="w3-text-red w3-small">{{ form.well.errors|join:", " }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Ligne 2 : planaire + type d'enregistrement -->
|
||||||
|
<div class="w3-row-padding w3-margin-bottom">
|
||||||
|
|
||||||
|
<div class="w3-col m4 s12 w3-margin-bottom">
|
||||||
|
<label class="w3-text-blue"><b>{{ form.planarian.label }}</b></label>
|
||||||
|
<p class="w3-small w3-text-light-grey" style="margin:0 0 4px;">
|
||||||
|
{% trans "Index du planaire dans le puits (commence à 0)" %}
|
||||||
|
</p>
|
||||||
|
{{ form.planarian }}
|
||||||
|
{% if form.planarian.errors %}
|
||||||
|
<span class="w3-text-red w3-small">{{ form.planarian.errors|join:", " }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="w3-col m8 s12 w3-margin-bottom">
|
||||||
|
<label class="w3-text-blue"><b>{{ form.record_type.label }}</b></label>
|
||||||
|
<p class="w3-small w3-text-light-grey" style="margin:0 0 4px;">
|
||||||
|
{% trans "Frame par frame : une ligne par image. Résumé : métriques agrégées de la session." %}
|
||||||
|
</p>
|
||||||
|
<!-- Radio buttons stylisés W3 -->
|
||||||
|
<div class="w3-row-padding" style="margin-top:6px;">
|
||||||
|
{{ form.record_type }}
|
||||||
|
</div>
|
||||||
|
{% if form.record_type.errors %}
|
||||||
|
<span class="w3-text-red w3-small">{{ form.record_type.errors|join:", " }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Ligne 3 : plage temporelle (optionnel) -->
|
||||||
|
<div class="w3-border-top w3-padding-top w3-margin-top">
|
||||||
|
<p class="w3-text-blue-grey">
|
||||||
|
<b>{% trans "Plage temporelle" %}</b>
|
||||||
|
<span class="w3-small w3-text-grey w3-margin-left">
|
||||||
|
{% trans "(optionnel — laisser vide pour exporter toute la session)" %}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
<div class="w3-row-padding">
|
||||||
|
|
||||||
|
<div class="w3-col m6 s12 w3-margin-bottom">
|
||||||
|
<label class="w3-text-blue-grey">{{ form.start_dt.label }}</label>
|
||||||
|
{{ form.start_dt }}
|
||||||
|
{% if form.start_dt.errors %}
|
||||||
|
<span class="w3-text-red w3-small">{{ form.start_dt.errors|join:", " }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="w3-col m6 s12 w3-margin-bottom">
|
||||||
|
<label class="w3-text-blue-grey">{{ form.stop_dt.label }}</label>
|
||||||
|
{{ form.stop_dt }}
|
||||||
|
{% if form.stop_dt.errors %}
|
||||||
|
<span class="w3-text-red w3-small">{{ form.stop_dt.errors|join:", " }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div><!-- fin padding -->
|
||||||
|
</div><!-- fin card -->
|
||||||
|
|
||||||
|
<!-- Boutons -->
|
||||||
|
<div class="w3-row-padding w3-margin-top">
|
||||||
|
<div class="w3-col s12">
|
||||||
|
<button type="submit" class="w3-button w3-blue w3-large w3-padding-large">
|
||||||
|
📥 {% trans "Générer et télécharger le CSV" %}
|
||||||
|
</button>
|
||||||
|
<a href="/"
|
||||||
|
class="w3-button w3-light-grey w3-round w3-large w3-padding-large w3-margin-left">
|
||||||
|
✖ {% trans "Annuler" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<!-- Rappel des colonnes exportées -->
|
||||||
|
<div class="w3-card w3-round-large w3-margin-top w3-border w3-round w3-round-large w3-margin-bottom">
|
||||||
|
<header class="w3-container w3-blue-grey w3-round w3-round-top-large">
|
||||||
|
<h4 class="w3-text-white" style="margin:8px 0;">
|
||||||
|
{% trans "Colonnes du fichier CSV exporté" %}
|
||||||
|
</h4>
|
||||||
|
</header>
|
||||||
|
<div class="w3-container w3-padding-16">
|
||||||
|
<div class="w3-row-padding">
|
||||||
|
|
||||||
|
<div class="w3-col m6 s12">
|
||||||
|
<p class="w3-text-blue-grey w3-small"><b>{% trans "Identification / position" %}</b></p>
|
||||||
|
<ul class="w3-ul w3-small w3-border w3-round">
|
||||||
|
<li><code>timestamp</code></li>
|
||||||
|
<li><code>x_mm</code> / <code>y_mm</code></li>
|
||||||
|
<li><code>cx_px</code> / <code>cy_px</code></li>
|
||||||
|
<li><code>area_px</code></li>
|
||||||
|
<li><code>axial_pos</code> / <code>axial_speed</code></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="w3-col m6 s12">
|
||||||
|
<p class="w3-text-blue-grey w3-small"><b>{% trans "Métriques EthoVision XT" %}</b></p>
|
||||||
|
<ul class="w3-ul w3-small w3-border w3-round">
|
||||||
|
<li><code>velocity_mm_s</code> — {% trans "vitesse instantanée" %}</li>
|
||||||
|
<li><code>total_distance_mm</code> — {% trans "distance cumulée" %}</li>
|
||||||
|
<li><code>moving</code> / <code>duration_moving_s</code></li>
|
||||||
|
<li><code>mobility_state</code> — {% trans "Immobile / Mobile / Highly mobile" %}</li>
|
||||||
|
<li><code>dist_to_wall_mm</code> / <code>near_wall</code></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div><!-- fin container -->
|
||||||
|
|
||||||
|
<style>
|
||||||
|
input[type="text"],
|
||||||
|
input[type="number"],
|
||||||
|
input[type="datetime-local"],
|
||||||
|
select {
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 15px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background-color: #fafafa;
|
||||||
|
transition: border-color 0.2s;
|
||||||
|
}
|
||||||
|
input:focus, select:focus {
|
||||||
|
border-color: #2196F3;
|
||||||
|
outline: none;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
/* Masquer le radio natif, laisser le label stylisé */
|
||||||
|
.w3-button input[type="radio"] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
@@ -7,13 +7,25 @@ app_name = "planarian"
|
|||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
# Configurations expériences
|
# Configurations expériences
|
||||||
path("experiments/", views.ExperimentConfigListView.as_view(), name="experiment-list"),
|
#path("experiments/", views.ExperimentConfigListView.as_view(), name="experiment-list2"),
|
||||||
path("experiments/new/", views.ExperimentConfigFormView.as_view(), name="experiment-new"),
|
#
|
||||||
path("experiments/<int:pk>/",views.ExperimentConfigFormView.as_view(), name="experiment-edit"),
|
#path("experiments/new/", views.ExperimentConfigFormView.as_view(), name="experiment-new"),
|
||||||
|
#path("experiments/<int:pk>/",views.ExperimentConfigFormView.as_view(), name="experiment-edit2"),
|
||||||
|
|
||||||
|
#path("config/list/", views.config_list_view, name="experiment-list"),
|
||||||
|
#path("config/list/<int:session_id>/<int:experiment_id>/", views.config_list_view, name="experiment-list"),
|
||||||
|
|
||||||
|
#path("config/edit/<int:pk>/", views.config_edit_view, name="experiment-edit"),
|
||||||
|
#path("config/edit/<int:pk>/<int:session_id>/<int:experiment_id>/", views.config_edit_view, name="experiment-edit"),
|
||||||
|
|
||||||
# Import / export
|
# Import / export
|
||||||
path("import/", views.ImportParamsView.as_view(), name="import-params"),
|
#path("import/", views.ImportParamsView.as_view(), name="import-params"),
|
||||||
path("export/", views.ExportCsvView.as_view(), name="export-csv"),
|
#path("export/", views.ExportCsvView.as_view(), name="export-csv-view"),
|
||||||
|
|
||||||
|
#path("import/", views.ImportParamsView.as_view(), name="import-params"),
|
||||||
|
path("export/csv/", views.export_csv_view, name="export-csv"),
|
||||||
|
path("export/csv/<int:session_id>/<int:experiment_id>/", views.export_csv_view, name="export-csv"),
|
||||||
|
|
||||||
|
|
||||||
# API JSON pour le front-end
|
# API JSON pour le front-end
|
||||||
path("api/tracking/", views.TrackingDataView.as_view(), name="tracking-data-api"),
|
path("api/tracking/", views.TrackingDataView.as_view(), name="tracking-data-api"),
|
||||||
|
|||||||
@@ -9,9 +9,15 @@ from django.contrib import messages
|
|||||||
from django.http import HttpResponse, JsonResponse
|
from django.http import HttpResponse, JsonResponse
|
||||||
from django.shortcuts import get_object_or_404, redirect #, render
|
from django.shortcuts import get_object_or_404, redirect #, render
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from django.shortcuts import render #, redirect
|
||||||
|
from django.views.decorators.http import require_GET, require_POST
|
||||||
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
|
from django.contrib.auth.decorators import login_required, user_passes_test
|
||||||
|
|
||||||
from django.views import View
|
from django.views import View
|
||||||
from django.views.generic import FormView, ListView
|
from django.views.generic import FormView, ListView
|
||||||
from django.views.decorators.http import require_GET
|
|
||||||
|
|
||||||
from .forms import CsvImportForm, ExperimentConfigForm, ExportCsvForm
|
from .forms import CsvImportForm, ExperimentConfigForm, ExportCsvForm
|
||||||
from .models import ExperimentConfig
|
from .models import ExperimentConfig
|
||||||
@@ -23,10 +29,13 @@ from scanner import models
|
|||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
start_background_updater()
|
start_background_updater()
|
||||||
|
|
||||||
|
|
||||||
|
def is_staff_or_admin(user):
|
||||||
|
return user.is_staff or user.is_superuser
|
||||||
|
|
||||||
|
|
||||||
@require_GET
|
@require_GET
|
||||||
def stats_view(request):
|
def stats_view(request):
|
||||||
"""
|
"""
|
||||||
@@ -45,7 +54,6 @@ def _get_reduct_client() -> ReductStoreClient:
|
|||||||
|
|
||||||
|
|
||||||
def global_context(request, **ctx):
|
def global_context(request, **ctx):
|
||||||
default_multiwell = models.MultiWell.objects.filter(default=True).first()
|
|
||||||
conf = ScannerConstants().get()
|
conf = ScannerConstants().get()
|
||||||
return dict(
|
return dict(
|
||||||
app_title=settings.APP_TITLE,
|
app_title=settings.APP_TITLE,
|
||||||
@@ -54,10 +62,6 @@ def global_context(request, **ctx):
|
|||||||
local_ip_server=settings.LOCAL_IP_SERVER,
|
local_ip_server=settings.LOCAL_IP_SERVER,
|
||||||
host_port=settings.SERVER_HOST_PORT,
|
host_port=settings.SERVER_HOST_PORT,
|
||||||
conf=conf,
|
conf=conf,
|
||||||
default_position = default_multiwell.position or 'HD',
|
|
||||||
export_destination=settings.EXPORT_DESTINATIONS,
|
|
||||||
well_choices = models.Well.objects.order_by('name').all(),
|
|
||||||
|
|
||||||
**ctx
|
**ctx
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -78,104 +82,104 @@ def get_active_experiments(session, expid=None):
|
|||||||
return [], None
|
return [], None
|
||||||
|
|
||||||
|
|
||||||
def get_experiment_config(request):
|
def get_active_session(request, session_id=None, experiment_id=None):
|
||||||
cursid = request.POST.get('_sid')
|
cursid = session_id or request.POST.get('_sid')
|
||||||
expid = request.POST.get('_expid')
|
expid = experiment_id or request.POST.get('_expid')
|
||||||
|
|
||||||
print(request.POST)
|
|
||||||
|
|
||||||
current_session = models.Session.get_session(cursid)
|
current_session = models.Session.get_session(cursid)
|
||||||
experiments, current_experiment = get_active_experiments(current_session, expid)
|
experiments, current_experiment = get_active_experiments(current_session, expid)
|
||||||
|
|
||||||
qs = ExperimentConfig.objects
|
|
||||||
if not current_session:
|
|
||||||
qs = qs.filter(experiment__session_experiments__isnull=False).select_related()
|
|
||||||
elif current_experiment:
|
|
||||||
qs = qs.filter(experiment_id=current_experiment.id)
|
|
||||||
|
|
||||||
context = dict(
|
context = dict(
|
||||||
current_session = current_session,
|
current_session = current_session,
|
||||||
current_experiment = current_experiment,
|
current_experiment = current_experiment,
|
||||||
experiments=experiments or [],
|
experiments=experiments or [],
|
||||||
sessions=models.Session.objects.filter(active=True).all(),
|
sessions=models.Session.objects.filter(active=True).all(),
|
||||||
|
well_choices=models.Well.objects.order_by('name').all(),
|
||||||
)
|
)
|
||||||
return qs.all(), context
|
return context
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Vue : liste des configurations
|
# Vue :Export CSV depuis ReductStore
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
def export_csv(request):
|
||||||
|
d = request.POST
|
||||||
|
|
||||||
class ExperimentConfigListView(ListView):
|
@async_to_sync
|
||||||
"""Liste toutes les configurations expériences."""
|
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)}, {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
|
||||||
|
|
||||||
model = ExperimentConfig
|
csv_content, n = _do_export()
|
||||||
template_name = "planarian/experiment_list.html"
|
|
||||||
context_object_name = "configs"
|
|
||||||
ordering = ["-created_at"]
|
|
||||||
|
|
||||||
def get_queryset(self):
|
logger.info(f"Export CSV: {n} lignes, content size={len(csv_content)}")
|
||||||
qs, self.config_context = get_experiment_config(self.request)
|
if not csv_content:
|
||||||
return qs
|
messages.warning(request, _("Aucune donnée trouvée."))
|
||||||
|
return None
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
filename = (
|
||||||
return super().get(request, *args, **kwargs)
|
f"{d['experiment']}_{d['well']}_planaire{d['planarian']}"
|
||||||
|
f"_{d['record_type']}.csv"
|
||||||
def post(self, request, *args, **kwargs):
|
)
|
||||||
return super().get(request, *args, **kwargs)
|
response = HttpResponse(csv_content, content_type="text/csv; charset=utf-8")
|
||||||
|
response["Content-Disposition"] = f'attachment; filename="{filename}"'
|
||||||
def get_context_data(self, **kwargs):
|
messages.success(request, _("%(n)d lignes exportées.") % {"n": n})
|
||||||
context = super().get_context_data(**kwargs)
|
return response
|
||||||
|
|
||||||
context.update(self.config_context)
|
|
||||||
return global_context(self.request, **context)
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Vue : création / modification d'une configuration
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
class ExperimentConfigFormView(FormView):
|
|
||||||
"""Formulaire de saisie des paramètres d'une expérience."""
|
|
||||||
|
|
||||||
template_name = "planarian/experiment_form.html"
|
|
||||||
form_class = ExperimentConfigForm
|
|
||||||
|
|
||||||
|
|
||||||
def get_queryset(self):
|
@login_required
|
||||||
qs, self.config_context = get_experiment_config(self.request)
|
def export_csv_view(request, session_id=None, experiment_id=None):
|
||||||
return qs
|
session_context = get_active_session(request, session_id, experiment_id)
|
||||||
|
|
||||||
def get_form(self, form_class=None):
|
if request.method == 'POST':
|
||||||
pk = self.kwargs.get("pk")
|
valid = request.POST.get('valid')
|
||||||
if pk:
|
if valid == 'ok':
|
||||||
instance = get_object_or_404(ExperimentConfig, pk=pk)
|
export_csv(request)
|
||||||
return ExperimentConfigForm(self.request.POST or None, instance=instance)
|
|
||||||
return ExperimentConfigForm(self.request.POST or None)
|
|
||||||
|
|
||||||
def form_valid(self, form):
|
ctx = {
|
||||||
form.save()
|
'choice_title': _("Export vers un fichier CSV depuis ReductStore"),
|
||||||
messages.success(self.request, _("Configuration sauvegardée."))
|
'well': 'A1',
|
||||||
return redirect("planarian:experiment-list")
|
'planarian': "0",
|
||||||
|
'record_type': 'frame',
|
||||||
|
**session_context
|
||||||
|
}
|
||||||
|
|
||||||
def form_invalid(self, form):
|
return render(request, "planarian/export_csv.html", context=global_context(request, **ctx))
|
||||||
print(f"Form validation failed: {form.errors}")
|
|
||||||
messages.error(self.request, form.errors)
|
|
||||||
return super().form_invalid(form)
|
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
|
||||||
print(f"Received POST data: {request.POST}")
|
|
||||||
return super().post(request, *args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
context = super().get_context_data(**kwargs)
|
|
||||||
context['is_creation'] = 'pk' not in self.kwargs
|
|
||||||
context['is_update'] = 'pk' in self.kwargs
|
|
||||||
return global_context(self.request, choice_title=_("Paramètres d'une expérience"), **context)
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Vue : import CSV de paramètres
|
# Vue : import CSV de paramètres
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
@login_required
|
||||||
|
def import_csv_view(request, session_id=None, experiment_id=None):
|
||||||
|
"""
|
||||||
|
Import de configurations d'expérience depuis un fichier CSV.
|
||||||
|
Une ligne CSV = un puits = un ExperimentConfig.
|
||||||
|
|
||||||
|
Colonnes CSV obligatoires : experiment, well, px_per_mm, fps
|
||||||
|
Toutes les autres colonnes correspondent aux champs du modèle.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
ctx = {
|
||||||
|
'choice_title': _("Importer des configurations depuis un fichier CSV"),
|
||||||
|
}
|
||||||
|
return render(request, "planarian/import_params.html", context=global_context(request, **ctx))
|
||||||
|
|
||||||
|
|
||||||
class ImportParamsView(FormView):
|
class ImportParamsView(FormView):
|
||||||
"""
|
"""
|
||||||
@@ -189,9 +193,6 @@ class ImportParamsView(FormView):
|
|||||||
template_name = "planarian/import_params.html"
|
template_name = "planarian/import_params.html"
|
||||||
form_class = CsvImportForm
|
form_class = CsvImportForm
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
qs, self.config_context = get_experiment_config(self.request)
|
|
||||||
return qs
|
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
rows = form.csv_rows
|
rows = form.csv_rows
|
||||||
@@ -233,68 +234,7 @@ class ImportParamsView(FormView):
|
|||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
return global_context(self.request, choice_title=_("configurations d'expérience depuis un fichier CSV"), **context)
|
return global_context(self.request, choice_title=_("Importer des configurations depuis un fichier CSV"), **context)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Vue : export CSV depuis ReductStore
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
class ExportCsvView(FormView):
|
|
||||||
"""
|
|
||||||
Export des données de tracking depuis ReductStore vers un fichier CSV.
|
|
||||||
Retourne le fichier en téléchargement HTTP.
|
|
||||||
"""
|
|
||||||
|
|
||||||
template_name = "planarian/export_csv.html"
|
|
||||||
form_class = ExportCsvForm
|
|
||||||
|
|
||||||
def form_valid(self, form):
|
|
||||||
d = form.cleaned_data
|
|
||||||
|
|
||||||
@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["experiment"],
|
|
||||||
well = d["well"],
|
|
||||||
planarian = d["planarian"],
|
|
||||||
record_type = d["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)}, {csv_content}")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Erreur export CSV: {e}")
|
|
||||||
messages.error(self.request, _("Erreur lors de l'export CSV: %(error)s") % {"error": str(e)})
|
|
||||||
return None, 0
|
|
||||||
|
|
||||||
return csv_content, n
|
|
||||||
|
|
||||||
csv_content, n = _do_export()
|
|
||||||
|
|
||||||
print(f"Export CSV: {n} lignes, content size={len(csv_content)}")
|
|
||||||
if not csv_content:
|
|
||||||
messages.warning(self.request, _("Aucune donnée trouvée."))
|
|
||||||
return self.form_invalid(form)
|
|
||||||
|
|
||||||
filename = (
|
|
||||||
f"{d['experiment']}_{d['well']}_planaire{d['planarian']}"
|
|
||||||
f"_{d['record_type']}.csv"
|
|
||||||
)
|
|
||||||
response = HttpResponse(csv_content, content_type="text/csv; charset=utf-8")
|
|
||||||
response["Content-Disposition"] = f'attachment; filename="{filename}"'
|
|
||||||
messages.success(self.request, _("%(n)d lignes exportées.") % {"n": n})
|
|
||||||
return response
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
context = super().get_context_data(**kwargs)
|
|
||||||
qs, config_context = get_experiment_config(self.request)
|
|
||||||
context.update(config_context)
|
|
||||||
return global_context(self.request, choice_title=_("Tracking depuis ReductStore vers un fichier CSV"), **context)
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -337,5 +277,5 @@ class TrackingDataView(View):
|
|||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
return global_context(self.request, choice_title=_("Métriques de tracking d'un planaire"), **context)
|
return global_context(self.request, choice_title=str(_("Métriques de tracking d'un planaire")), **context)
|
||||||
|
|
||||||
|
|||||||
@@ -99,20 +99,20 @@
|
|||||||
<i class="fa-solid fa-gear w3-text-purple w3-xlarge"></i> {% trans "Création de sessions d'expériences" %}
|
<i class="fa-solid fa-gear w3-text-purple w3-xlarge"></i> {% trans "Création de sessions d'expériences" %}
|
||||||
</a>
|
</a>
|
||||||
<hr class="w3-bar-item divider w3-grey">
|
<hr class="w3-bar-item divider w3-grey">
|
||||||
<a href="{% url 'scanner:experimentconfig_view' %}" class="w3-bar-item w3-btn w3-hover-opacity">
|
|
||||||
<i class="fa-solid fa-gears w3-text-pink w3-xlarge"></i> {% trans "Configuration des expériences" %}
|
|
||||||
</a>
|
|
||||||
<a href="{% url 'scanner:experiment_view' %}" class="w3-bar-item w3-btn w3-hover-opacity">
|
<a href="{% url 'scanner:experiment_view' %}" class="w3-bar-item w3-btn w3-hover-opacity">
|
||||||
<i class="fa-solid fa-gear w3-text-pink w3-xlarge"></i> {% trans "Gestions des expériences" %}
|
<i class="fa-solid fa-gear w3-text-pink w3-xlarge"></i> {% trans "Gestions des expériences" %}
|
||||||
</a>
|
</a>
|
||||||
|
<a href="{% url 'scanner:experimentconfig_view' %}" class="w3-bar-item w3-btn w3-hover-opacity">
|
||||||
|
<i class="fa-solid fa-gears w3-text-pink w3-xlarge"></i> {% trans "Configuration des expériences" %}
|
||||||
|
</a>
|
||||||
<a href="{% url 'scanner:calibration' %}" class="w3-bar-item w3-btn w3-hover-opacity">
|
<a href="{% url 'scanner:calibration' %}" class="w3-bar-item w3-btn w3-hover-opacity">
|
||||||
<i class="fa-solid fa-wrench w3-text-red w3-xlarge"></i> {% trans "Calibration" %}
|
<i class="fa-solid fa-wrench w3-text-orange w3-xlarge"></i> {% trans "Calibration" %}
|
||||||
</a>
|
</a>
|
||||||
<hr class="w3-bar-item divider w3-grey w3-margin-bottom">
|
<hr class="w3-bar-item divider w3-grey w3-margin-bottom">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if conf.tracking %}
|
{% if conf.tracking %}
|
||||||
<a href="{% url 'planarian:experiment-list' %}" class="w3-bar-item w3-btn w3-hover-opacity">
|
<a href="{% url 'planarian:export-csv' %}" class="w3-bar-item w3-btn w3-hover-opacity">
|
||||||
<i class="fa-solid fa-vials w3-text-lime w3-xlarge"></i> {% trans "Préparation des expériences" %}
|
<i class="fa-solid fa-vials w3-text-lime w3-xlarge"></i> {% trans "Export CSV des expériences" %}
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="{% url 'scanner:scanning' %}" class="w3-bar-item w3-btn w3-hover-opacity">
|
<a href="{% url 'scanner:scanning' %}" class="w3-bar-item w3-btn w3-hover-opacity">
|
||||||
|
|||||||
Reference in New Issue
Block a user