Skip to content

Commit

Permalink
Merge pull request #17 from ecolabdata/feat/connection-step
Browse files Browse the repository at this point in the history
Add connection step
  • Loading branch information
abulte authored Sep 11, 2024
2 parents b133110 + 84f5390 commit 63ea298
Show file tree
Hide file tree
Showing 10 changed files with 140 additions and 45 deletions.
78 changes: 57 additions & 21 deletions ecospheres_migrator/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,20 @@
from datetime import datetime
from pathlib import Path

from flask import Flask, abort, redirect, render_template, request, send_file, session, url_for

import requests
from flask import (
Flask,
abort,
flash,
redirect,
render_template,
request,
send_file,
session,
url_for,
)

from ecospheres_migrator.auth import authenticated, connection_infos
from ecospheres_migrator.migrator import Migrator
from ecospheres_migrator.queue import get_job, get_queue

Expand All @@ -13,6 +25,42 @@


@app.route("/")
def login_form():
return render_template(
"login.html.j2",
url=session.get("url", ""),
username=session.get("username", ""),
password=session.get("password", ""),
)


@app.route("/login", methods=["POST"])
def login():
url = request.form.get("url")
username = request.form.get("username")
password = request.form.get("password")
if not username or not password or not url:
abort(400, "Missing login parameter(s)")

try:
migrator = Migrator(url=url, username=username, password=password)
gn_info = migrator.gn.info()
except requests.exceptions.RequestException as e:
flash(f"Problème d'authentification ({e})", "error")
return redirect(url_for("login_form"))
else:
authenticated = gn_info.get("me", {}).get("@authenticated", "false") == "true"
if not authenticated:
flash("Problème d'authentification (retour api geonetwork)", "error")
return redirect(url_for("login_form"))

session["url"] = url
session["username"] = username
session["password"] = password
return redirect(url_for("select"))


@app.route("/select")
def select():
return render_template(
"select.html.j2",
Expand All @@ -22,41 +70,29 @@ def select():


@app.route("/select/preview", methods=["POST"])
@authenticated(redirect=False)
def select_preview():
url = request.form.get("url")
url, username, password = connection_infos()
if not url:
return "Veuillez entrer une URL de catalogue"
query = request.form.get("query")
if not query:
return "Veuillez entrer une requête de recherche"
# Need auth to ensure the records retrieved in selection are consistent with
# the records that'll be updated during the migration. Otherwise we might miss
# things like workflow status.
# TODO: required auth? or skip items with drafts in migration? ...?
username = request.form.get("username")
password = request.form.get("password")
migrator = Migrator(url=url, username=username, password=password)
results = migrator.select(query=query)
return render_template("fragments/select_preview.html.j2", results=results)


@app.route("/transform", methods=["POST"])
@authenticated()
def transform():
url = request.form.get("url")
if not url:
abort(400, "Missing `url` parameter")
session["url"] = url
url, username, password = connection_infos()
query = request.form.get("query")
if not query:
abort(400, "Missing `query` parameter")
transformation = request.form.get("transformation")
if not transformation:
abort(400, "Missing `transformation` parameter")
username = request.form.get("username")
password = request.form.get("password")
if username and password:
session["username"] = username
session["password"] = password
migrator = Migrator(url=url, username=username, password=password)
selection = migrator.select(query=query)
job = get_queue().enqueue(migrator.transform, transformation, selection)
Expand Down Expand Up @@ -98,19 +134,19 @@ def transform_download_result(job_id: str):


@app.route("/migrate/<job_id>", methods=["POST"])
@authenticated()
def migrate(job_id: str):
transform_job = get_job(job_id)
if not transform_job:
abort(404)
username = session["username"]
password = session["password"]
url, username, password = connection_infos()
mode = request.form.get("mode")
group = request.form.get("group")
overwrite = mode == "overwrite"
if not overwrite and not group:
# TODO: display group field only when needed
abort(400, "Missing `group` parameter")
migrator = Migrator(url=session["url"], username=username, password=password)
migrator = Migrator(url=url, username=username, password=password)
migrate_job = get_queue().enqueue(
migrator.migrate, transform_job.result, overwrite=overwrite, group=group
)
Expand Down
25 changes: 25 additions & 0 deletions ecospheres_migrator/auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from functools import wraps

from flask import abort, flash, session, url_for
from flask import redirect as flask_redirect


def authenticated(redirect: bool = True):
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if not all(k in session for k in ("url", "username", "password")):
if redirect:
flash("Pas d'information de connexion trouvée en session", "error")
return flask_redirect(url_for("login_form"))
else:
abort(401, "Pas d'information de connexion trouvée en session")
return f(*args, **kwargs)

return decorated_function

return decorator


def connection_infos() -> tuple[str, str, str]:
return session["url"], session["username"], session["password"]
5 changes: 5 additions & 0 deletions ecospheres_migrator/geonetwork.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ def __init__(self, url, username: str | None = None, password: str | None = None
log.debug(f"Authenticating as: {username}")
self.authenticate()

def info(self):
r = self.session.get(f"{self.api}/info?_content_type=json&type=me")
r.raise_for_status()
return r.json()

def authenticate(self):
r = self.session.post(f"{self.api}/info?_content_type=json&type=me")
# don't abort on error here, it's expected
Expand Down
9 changes: 9 additions & 0 deletions ecospheres_migrator/templates/base.html.j2
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@
{% include "header.html.j2" %}
<main role="main" id="content">
<div class="fr-container fr-mb-4w">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="fr-alert fr-alert--{{ category }} fr-mt-2w">
<p>{{ message }}</p>
</div>
{% endfor %}
{% endif %}
{% endwith %}
{% block content %}
{% endblock content %}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
hx-trigger="load delay:1s"
hx-swap="outerHTML">{{ now }} — le job est en cours de traitement ({{ status }}).</div>
{% elif status == "finished" %}
{# TODO: add normalized info about current connected catalog #}
<p>Job terminé.</p>
<p>Vous pouvez <a href="{{ url_for('transform_download_result', job_id=job.id) }}">télécharger le résultat</a>
et appliquer vous-mêmes les modifications sur votre catalogue en important le fichier.</p>
<p>Vous pouvez également utiliser le formulaire ci-dessous pour appliquer les modifications automatiquement
sur le catalogue {{ url }}. Pour ce faire, nous avons besoin d'identifiants privilégiés sur votre catalogue.
Ces identifiants sont transmis de manière sécurisée et nous ne les stockons pas.</p>
sur le catalogue {{ url }}.</p>
<form action="{{ url_for('migrate', job_id=job.id) }}" method="post" class="fr-mt-2w">
<div class="fr-select-group">
<label class="fr-label" for="mode">Mode de mise à jour *</label>
Expand Down
4 changes: 2 additions & 2 deletions ecospheres_migrator/templates/header.html.j2
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
</div>
</div>
<div class="fr-header__service">
<a href="{{ url_for('select') }}" title="Accueil - Ecosphères migrator">
<a href="{{ url_for('login_form') }}" title="Accueil - Ecosphères migrator">
<p class="fr-header__service-title">Ecosphères migrator</p>
</a>
<p class="fr-header__service-tagline">Migrations XML Geonetwork</p>
Expand All @@ -44,7 +44,7 @@
aria-label="Menu principal">
<ul class="fr-nav__list">
<li class="fr-nav__item">
<a class="fr-nav__link" href="{{ url_for('select') }}" target="_self">Migration</a>
<a class="fr-nav__link" href="{{ url_for('login_form') }}" target="_self">Migration</a>
</li>
<li class="fr-nav__item">
<a class="fr-nav__link" href="{{ url_for('documentation') }}" target="_self">Documentation</a>
Expand Down
33 changes: 33 additions & 0 deletions ecospheres_migrator/templates/login.html.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{% extends "base.html.j2" %}
{% block content %}
<div class="fr-stepper fr-mt-4v">
<h2 class="fr-stepper__title">
Connexion au catalogue
<span class="fr-stepper__state">Étape 1 sur 4</span>
</h2>
<div class="fr-stepper__steps" data-fr-current-step="1" data-fr-steps="4"></div>
<p class="fr-stepper__details">
<span class="fr-text--bold">Étape suivante :</span> Sélection des données
</p>
</div>
<form method="post" action="{{ url_for('login') }}">
<div class="fr-input-group">
<label class="fr-label" for="query">URL du catalogue *
<span class="fr-hint-text">Ex : https://www.example.com/geonetwork/srv</span>
</label>
<input required class="fr-input" type="url" id="url" name="url" value="{{ url }}" />
</div>
<div class="fr-input-group">
<label class="fr-label" for="username">Identifiant sur le catalogue *</label>
<input required class="fr-input" type="text" id="username" name="username" value="{{ username }}" />
</div>
<div class="fr-input-group">
<label class="fr-label" for="password">Mot de passe sur le catalogue *</label>
<input required class="fr-input" type="password" id="password" name="password" value="{{ password }}" />
</div>
<div class="fr-hint-text">Les données de ce formulaire sont chiffrées et stockées uniquement sur votre navigateur.</div>
<div class="fr-mt-2w fr-grid-row">
<button type="submit" class="fr-btn">Suivant</button>
</div>
</form>
{% endblock content %}
4 changes: 2 additions & 2 deletions ecospheres_migrator/templates/migrate.html.j2
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
<div class="fr-stepper fr-mt-4v">
<h2 class="fr-stepper__title">
Migration des données
<span class="fr-stepper__state">Étape 3 sur 3</span>
<span class="fr-stepper__state">Étape 4 sur 4</span>
</h2>
<div class="fr-stepper__steps" data-fr-current-step="3" data-fr-steps="3"></div>
<div class="fr-stepper__steps" data-fr-current-step="4" data-fr-steps="4"></div>
</div>
Votre traitement (job) porte l'identifiant <code>{{ job.id }}</code>.
Son avancement est présenté ci-dessous.
Expand Down
19 changes: 3 additions & 16 deletions ecospheres_migrator/templates/select.html.j2
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,15 @@
<div class="fr-stepper fr-mt-4v">
<h2 class="fr-stepper__title">
Sélection des données à migrer
<span class="fr-stepper__state">Étape 1 sur 3</span>
<span class="fr-stepper__state">Étape 2 sur 4</span>
</h2>
<div class="fr-stepper__steps" data-fr-current-step="1" data-fr-steps="3"></div>
<div class="fr-stepper__steps" data-fr-current-step="2" data-fr-steps="4"></div>
<p class="fr-stepper__details">
<span class="fr-text--bold">Étape suivante :</span> Transformation des données
</p>
</div>
<form hx-validate="true" method="post" action="{{ url_for('transform') }}">
<div class="fr-input-group">
<label class="fr-label" for="query">URL du catalogue *
<span class="fr-hint-text">Ex : https://www.example.com/geonetwork/srv</span>
</label>
<input required class="fr-input" type="url" id="url" name="url" value="{{ url }}" />
</div>
<div class="fr-input-group">
<label class="fr-label" for="username">Identifiant sur le catalogue *</label>
<input required class="fr-input" type="text" id="username" name="username" />
</div>
<div class="fr-input-group">
<label class="fr-label" for="password">Mot de passe sur le catalogue *</label>
<input required class="fr-input" type="password" id="password" name="password" />
</div>
{# TODO: add info about current connected catalog #}
<div class="fr-input-group">
<label class="fr-label" for="query">Requête de recherche *
<span class="fr-hint-text">Ex : _source=&lt;SOURCE_ID&gt;,type=dataset</span>
Expand Down
4 changes: 2 additions & 2 deletions ecospheres_migrator/templates/transform.html.j2
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
<div class="fr-stepper fr-mt-4v">
<h2 class="fr-stepper__title">
Transformation des données
<span class="fr-stepper__state">Étape 2 sur 3</span>
<span class="fr-stepper__state">Étape 3 sur 4</span>
</h2>
<div class="fr-stepper__steps" data-fr-current-step="2" data-fr-steps="3"></div>
<div class="fr-stepper__steps" data-fr-current-step="3" data-fr-steps="4"></div>
<p class="fr-stepper__details">
<span class="fr-text--bold">Étape suivante :</span> Migration des données (facultatif)
</p>
Expand Down

0 comments on commit 63ea298

Please sign in to comment.