feat: gestion des conflits lors de l'upload de fichiers
Ajoute un sélecteur de stratégie dans le formulaire de dépôt CDN,
identique à celui du redimensionnement :
- Écraser (défaut) : comportement précédent, écrase silencieusement
- Backup : renomme l'existant en {stem}_bak_{timestamp}{ext} avant dépôt
- Renommer : auto-incrémente le nom du fichier uploadé ({stem}_1, _2…)
- Ignorer : ne dépose pas si le fichier existe déjà
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
14259c59f1
commit
130a901be7
3 changed files with 26 additions and 1 deletions
17
app/app.py
17
app/app.py
|
|
@ -554,11 +554,26 @@ def upload_file():
|
||||||
if not dest.is_dir():
|
if not dest.is_dir():
|
||||||
abort(400)
|
abort(400)
|
||||||
|
|
||||||
|
conflict = request.form.get("conflict", "overwrite")
|
||||||
|
if conflict not in ("backup", "overwrite", "rename", "skip"):
|
||||||
|
conflict = "overwrite"
|
||||||
|
|
||||||
for f in files:
|
for f in files:
|
||||||
name = secure_filename(f.filename or "")
|
name = secure_filename(f.filename or "")
|
||||||
if not name:
|
if not name:
|
||||||
continue
|
continue
|
||||||
f.save(dest / name)
|
out_path = dest / name
|
||||||
|
if out_path.exists():
|
||||||
|
if conflict == "skip":
|
||||||
|
continue
|
||||||
|
elif conflict == "backup":
|
||||||
|
try:
|
||||||
|
out_path.rename(_backup_path(out_path))
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
elif conflict == "rename":
|
||||||
|
out_path = _auto_rename(out_path)
|
||||||
|
f.save(out_path)
|
||||||
|
|
||||||
return redirect(url_for("browse", subpath=subpath) if subpath else url_for("browse"))
|
return redirect(url_for("browse", subpath=subpath) if subpath else url_for("browse"))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -165,6 +165,7 @@ main { max-width: 1100px; margin: 2rem auto; padding: 0 1.5rem 3rem; display: fl
|
||||||
|
|
||||||
/* ── Upload ───────────────────────────────────────────────────────── */
|
/* ── Upload ───────────────────────────────────────────────────────── */
|
||||||
.upload-card h2 { margin-bottom: .8rem; }
|
.upload-card h2 { margin-bottom: .8rem; }
|
||||||
|
.upload-conflict { display: flex; align-items: center; gap: .8rem; flex-wrap: wrap; margin-bottom: .8rem; }
|
||||||
.drop-zone { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: .5rem; border: 2px dashed var(--border); border-radius: var(--radius); padding: 2rem 1.5rem; cursor: pointer; background: var(--bg); transition: border-color .15s, background .15s; text-align: center; position: relative; margin-bottom: 1rem; }
|
.drop-zone { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: .5rem; border: 2px dashed var(--border); border-radius: var(--radius); padding: 2rem 1.5rem; cursor: pointer; background: var(--bg); transition: border-color .15s, background .15s; text-align: center; position: relative; margin-bottom: 1rem; }
|
||||||
.drop-zone:hover, .drop-zone:focus-within { border-color: var(--blue); background: var(--blue-light); }
|
.drop-zone:hover, .drop-zone:focus-within { border-color: var(--blue); background: var(--blue-light); }
|
||||||
.drop-zone input[type=file] { position: absolute; inset: 0; opacity: 0; cursor: pointer; width: 100%; height: 100%; }
|
.drop-zone input[type=file] { position: absolute; inset: 0; opacity: 0; cursor: pointer; width: 100%; height: 100%; }
|
||||||
|
|
|
||||||
|
|
@ -120,6 +120,15 @@
|
||||||
<span class="drop-names" id="drop-names"></span>
|
<span class="drop-names" id="drop-names"></span>
|
||||||
<input type="file" name="files" id="upload-input" multiple>
|
<input type="file" name="files" id="upload-input" multiple>
|
||||||
</label>
|
</label>
|
||||||
|
<div class="upload-conflict">
|
||||||
|
<span class="resize-group-label">Si le fichier existe déjà</span>
|
||||||
|
<div class="resize-chips resize-chips--radio">
|
||||||
|
<label class="chip"><input type="radio" name="conflict" value="overwrite" checked><span>Écraser</span></label>
|
||||||
|
<label class="chip"><input type="radio" name="conflict" value="backup"><span>Backup</span></label>
|
||||||
|
<label class="chip"><input type="radio" name="conflict" value="rename"><span>Renommer</span></label>
|
||||||
|
<label class="chip"><input type="radio" name="conflict" value="skip"><span>Ignorer</span></label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<button type="submit" class="btn btn-primary" id="upload-btn" disabled>Envoyer</button>
|
<button type="submit" class="btn btn-primary" id="upload-btn" disabled>Envoyer</button>
|
||||||
</form>
|
</form>
|
||||||
<script>
|
<script>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue