From 82c78c13cd6224cb6a16129292eb08475b6b85f5 Mon Sep 17 00:00:00 2001 From: nocci Date: Tue, 22 Apr 2025 13:20:15 +0200 Subject: [PATCH] integrate import/export funcion --- setup.sh | 103 ++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 79 insertions(+), 24 deletions(-) diff --git a/setup.sh b/setup.sh index 5fbd40a..773442d 100644 --- a/setup.sh +++ b/setup.sh @@ -23,15 +23,17 @@ flask-babel jinja2<3.1.0 EOL -# 3. app.py (angepasst für SQLAlchemy 2.x) +# 3. app.py (inkl. Import/Export) cat <<'PYTHON_END' > app.py -from flask import Flask, render_template, request, redirect, url_for, flash, make_response, session, abort +from flask import Flask, render_template, request, redirect, url_for, flash, make_response, session, abort, send_file from flask_sqlalchemy import SQLAlchemy from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user from flask_babel import Babel, _ from werkzeug.security import generate_password_hash, check_password_hash from datetime import datetime import os +import io +import csv app = Flask(__name__) app.config['SECRET_KEY'] = os.urandom(24) @@ -41,7 +43,6 @@ app.config['BABEL_DEFAULT_LOCALE'] = 'de' app.config['BABEL_SUPPORTED_LOCALES'] = ['de', 'en'] app.config['BABEL_TRANSLATION_DIRECTORIES'] = 'translations' - db = SQLAlchemy(app) login_manager = LoginManager(app) login_manager.login_view = 'login' @@ -165,7 +166,7 @@ def add_game(): recipient=request.form.get('recipient', ''), notes=request.form.get('notes', ''), url=url, - steam_appid=steam_appid, # <- jetzt wird sie gesetzt! + steam_appid=steam_appid, redeem_date=datetime.strptime(request.form['redeem_date'], '%Y-%m-%d') if request.form['redeem_date'] else None, user_id=current_user.id ) @@ -178,45 +179,37 @@ def add_game(): flash(_('Error: ') + str(e), 'danger') return render_template('add_game.html') - @app.route('/edit/', methods=['GET', 'POST']) @login_required def edit_game(game_id): - game = db.session.get(Game, game_id) # SQLAlchemy 2.x-kompatibel + game = db.session.get(Game, game_id) if not game or game.owner != current_user: return _("Not allowed!"), 403 - + if request.method == 'POST': try: - # Steam AppID aus Formular oder URL extrahieren url = request.form.get('url', '') steam_appid = request.form.get('steam_appid', '').strip() if not steam_appid: steam_appid = extract_steam_appid(url) - - # Aktualisiere alle Felder game.name = request.form['name'] game.steam_key = request.form['steam_key'] game.status = request.form['status'] game.recipient = request.form.get('recipient', '') game.notes = request.form.get('notes', '') game.url = url - game.steam_appid = steam_appid # <- FEHLTE HIER + game.steam_appid = steam_appid game.redeem_date = datetime.strptime(request.form['redeem_date'], '%Y-%m-%d') if request.form['redeem_date'] else None - db.session.commit() flash(_('Changes saved!'), 'success') return redirect(url_for('index')) - except Exception as e: db.session.rollback() flash(_('Error: ') + str(e), 'danger') - return render_template('edit_game.html', game=game, redeem_date=game.redeem_date.strftime('%Y-%m-%d') if game.redeem_date else '') - @app.route('/delete/', methods=['POST']) @login_required def delete_game(game_id): @@ -232,6 +225,57 @@ def delete_game(game_id): flash(_('Error deleting: ') + str(e), 'danger') return redirect(url_for('index')) +# --- Import/Export Funktionen --- + +@app.route('/export', methods=['GET']) +@login_required +def export_games(): + games = Game.query.filter_by(user_id=current_user.id).all() + output = io.StringIO() + writer = csv.writer(output) + writer.writerow(['Name', 'Steam Key', 'Status', 'Recipient', 'Notes', 'URL', 'Created', 'Redeem by', 'Steam AppID']) + for game in games: + writer.writerow([ + game.name, game.steam_key, game.status, game.recipient, game.notes, + game.url, game.created_at.strftime('%Y-%m-%d %H:%M:%S') if game.created_at else '', + game.redeem_date.strftime('%Y-%m-%d') if game.redeem_date else '', + game.steam_appid + ]) + output.seek(0) + return send_file( + io.BytesIO(output.getvalue().encode('utf-8')), + mimetype='text/csv', + as_attachment=True, + download_name='games_export.csv' + ) + +@app.route('/import', methods=['GET', 'POST']) +@login_required +def import_games(): + if request.method == 'POST': + file = request.files.get('file') + if file and file.filename.endswith('.csv'): + stream = io.StringIO(file.stream.read().decode("UTF8"), newline=None) + reader = csv.DictReader(stream) + for row in reader: + new_game = Game( + name=row['Name'], + steam_key=row['Steam Key'], + status=row['Status'], + recipient=row.get('Recipient', ''), + notes=row.get('Notes', ''), + url=row.get('URL', ''), + created_at=datetime.strptime(row['Created'], '%Y-%m-%d %H:%M:%S') if row.get('Created') else datetime.utcnow(), + redeem_date=datetime.strptime(row['Redeem by'], '%Y-%m-%d') if row.get('Redeem by') else None, + steam_appid=row.get('Steam AppID', ''), + user_id=current_user.id + ) + db.session.add(new_game) + db.session.commit() + flash(_('Import erfolgreich!'), 'success') + return redirect(url_for('index')) + flash(_('Bitte eine gültige CSV-Datei hochladen.'), 'danger') + return render_template('import.html') if __name__ == '__main__': with app.app_context(): @@ -249,10 +293,8 @@ EOL cat < Dockerfile FROM python:3.10-slim -# Shell explizit setzen SHELL ["/bin/bash", "-c"] -# Datenbankordner erstellen und Berechtigungen setzen RUN mkdir -p /app/data && chmod -R a+rwX /app/data WORKDIR /app @@ -263,7 +305,6 @@ COPY . . ARG UID=1000 ARG GID=1000 - RUN groupadd -g \$GID appuser && \ useradd -u \$UID -g \$GID -m appuser && \ chown -R appuser:appuser /app @@ -295,34 +336,46 @@ COMPOSE_END mkdir -p ../data ../steam-translations chmod -R a+rwX ../data ../steam-translations -# 7. Übersetzungs-Workflow-Script +# 8. Übersetzungs-Workflow-Script cat <<'SCRIPT_END' > ../translate.sh #!/bin/bash set -e cd "$(dirname "$0")/steam-gift-manager" -# 1. Extrahiere alle Texte docker-compose exec steam-manager pybabel extract -F babel.cfg -o translations/messages.pot . -# 2. Initialisiere Sprachen (nur einmal nötig, danach auskommentieren) for lang in de en; do if [ ! -f "../steam-translations/$lang/LC_MESSAGES/messages.po" ]; then docker-compose exec steam-manager pybabel init -i translations/messages.pot -d translations -l $lang fi done -# 3. Aktualisiere Übersetzungen docker-compose exec steam-manager pybabel update -i translations/messages.pot -d translations - -# 4. Kompiliere Übersetzungen docker-compose exec steam-manager pybabel compile -d translations echo "✅ Übersetzungen extrahiert, aktualisiert und kompiliert!" SCRIPT_END chmod +x ../translate.sh +# 9. Templates (nur neue/erweiterte hier, Rest wie gehabt) +cat < templates/import.html +{% extends "base.html" %} +{% block content %} +
+

{{ _('Import Games') }}

+
+
+ + +
+ + {{ _('Abbrechen') }} +
+
+{% endblock %} +HTML_END cat < templates/base.html @@ -372,6 +425,8 @@ cat < templates/base.html {% if current_user.is_authenticated %} + ⬇️ {{ _('Export') }} + ⬆️ {{ _('Import') }} {{ _('Logout') }} {% endif %}