initial import
This commit is contained in:
parent
16991fe2c0
commit
fe1f00978b
234
app.py
Normal file
234
app.py
Normal file
@ -0,0 +1,234 @@
|
||||
from flask import Flask, request, jsonify, redirect, url_for, session, render_template
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from werkzeug.security import generate_password_hash, check_password_hash
|
||||
from flask_session import Session
|
||||
from flask_login import LoginManager, UserMixin, login_user, login_required, logout_user, current_user
|
||||
import random
|
||||
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config['SECRET_KEY'] = 'your_secret_key' # Ändere dies zu einem echten geheimen Schlüssel
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///vocab.db'
|
||||
app.config['SESSION_TYPE'] = 'filesystem'
|
||||
app.config['SESSION_PERMANENT'] = False
|
||||
app.config['SESSION_USE_SIGNER'] = True
|
||||
|
||||
db = SQLAlchemy(app)
|
||||
Session(app)
|
||||
|
||||
login_manager = LoginManager()
|
||||
login_manager.init_app(app)
|
||||
login_manager.login_view = 'login'
|
||||
|
||||
# Models
|
||||
class User(db.Model, UserMixin):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
username = db.Column(db.String(150), unique=True, nullable=False)
|
||||
password = db.Column(db.String(150), nullable=False)
|
||||
vocabularies = db.relationship('Vocabulary', backref='owner', lazy=True)
|
||||
|
||||
class Vocabulary(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
english = db.Column(db.String(100), nullable=False)
|
||||
german = db.Column(db.String(100), nullable=False)
|
||||
level = db.Column(db.Integer, nullable=False)
|
||||
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
|
||||
|
||||
|
||||
def get_random_vocab(user_id):
|
||||
vocab_list = Vocabulary.query.filter_by(user_id=user_id).all()
|
||||
if not vocab_list:
|
||||
return None
|
||||
|
||||
# Weight levels: Level 1 should be most common, Level 5 the least
|
||||
weighted_vocab_list = []
|
||||
for vocab in vocab_list:
|
||||
weight = max(1, 6 - vocab.level) # Weight 5 for level 1, 4 for level 2, ..., 1 for level 5
|
||||
weighted_vocab_list.extend([vocab] * weight)
|
||||
|
||||
return random.choice(weighted_vocab_list)
|
||||
|
||||
@app.route('/get_random_vocab', methods=['GET'])
|
||||
@login_required
|
||||
def get_random_vocab_route():
|
||||
vocab = get_random_vocab(current_user.id)
|
||||
if vocab:
|
||||
return jsonify({
|
||||
'english': vocab.english,
|
||||
'german': vocab.german,
|
||||
'level': vocab.level,
|
||||
'id': vocab.id,
|
||||
}), 200
|
||||
return jsonify({'error': 'No vocabulary found'}), 404
|
||||
|
||||
@app.route('/update_vocab_level/<int:id>', methods=['POST'])
|
||||
@login_required
|
||||
def update_vocab_level(id):
|
||||
vocab = Vocabulary.query.get(id)
|
||||
if vocab and vocab.user_id == current_user.id:
|
||||
if vocab.level < 5:
|
||||
vocab.level += 1
|
||||
db.session.commit()
|
||||
return jsonify({'success': True}), 200
|
||||
return jsonify({'message': 'Max level reached'}), 200
|
||||
return jsonify({'error': 'Not found or unauthorized'}), 404
|
||||
|
||||
@login_manager.user_loader
|
||||
def load_user(user_id):
|
||||
return User.query.get(int(user_id))
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
return render_template('start.html')
|
||||
|
||||
@app.route('/train')
|
||||
@login_required
|
||||
def train():
|
||||
return render_template('train.html')
|
||||
|
||||
@app.route('/settings')
|
||||
@login_required
|
||||
def settings():
|
||||
return render_template('settings.html')
|
||||
|
||||
@app.route('/vocab')
|
||||
@login_required
|
||||
def vocab():
|
||||
return render_template('vocab.html')
|
||||
|
||||
@app.route('/register', methods=['GET', 'POST'])
|
||||
def register():
|
||||
if request.method == 'POST':
|
||||
username = request.form.get('username')
|
||||
password = request.form.get('password')
|
||||
|
||||
if not username or not password:
|
||||
return render_template('register.html', error='Username and password are required'), 400
|
||||
|
||||
existing_user = User.query.filter_by(username=username).first()
|
||||
if existing_user:
|
||||
return render_template('register.html', error='Username already taken'), 409 # 409 Conflict
|
||||
|
||||
hashed_password = generate_password_hash(password, method='pbkdf2:sha256')
|
||||
new_user = User(username=username, password=hashed_password)
|
||||
|
||||
db.session.add(new_user)
|
||||
db.session.commit()
|
||||
|
||||
return redirect(url_for('login'))
|
||||
|
||||
return render_template('register.html')
|
||||
|
||||
@app.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
if request.method == 'POST':
|
||||
username = request.form.get('username')
|
||||
password = request.form.get('password')
|
||||
user = User.query.filter_by(username=username).first()
|
||||
if user and check_password_hash(user.password, password):
|
||||
login_user(user)
|
||||
return jsonify({'success': True}), 200
|
||||
else:
|
||||
return jsonify({'error': 'Invalid credentials'}), 401
|
||||
|
||||
# GET request: render login page
|
||||
return render_template('login.html')
|
||||
|
||||
@app.route('/logout', methods=['POST'])
|
||||
@login_required
|
||||
def logout():
|
||||
logout_user()
|
||||
return redirect(url_for('index'))
|
||||
|
||||
@app.route('/add_vocab', methods=['POST'])
|
||||
@login_required
|
||||
def add_vocab():
|
||||
english = request.form.get('english')
|
||||
german = request.form.get('german')
|
||||
level = int(request.form.get('level', 1))
|
||||
|
||||
if not english or not german or not level:
|
||||
return jsonify({'error': 'All fields are required'}), 400
|
||||
|
||||
vocab = Vocabulary(english=english, german=german, level=level, user_id=current_user.id)
|
||||
try:
|
||||
db.session.add(vocab)
|
||||
db.session.commit()
|
||||
return jsonify({'success': True}), 201
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@app.route('/update_vocab/<int:id>', methods=['PUT'])
|
||||
@login_required
|
||||
def update_vocab(id):
|
||||
vocab = Vocabulary.query.get(id)
|
||||
if not vocab or vocab.user_id != current_user.id:
|
||||
return jsonify({'error': 'Not found or unauthorized'}), 404
|
||||
|
||||
english = request.json.get('english')
|
||||
german = request.json.get('german')
|
||||
level = request.json.get('level')
|
||||
|
||||
if english:
|
||||
vocab.english = english
|
||||
if german:
|
||||
vocab.german = german
|
||||
if level:
|
||||
vocab.level = level
|
||||
|
||||
try:
|
||||
db.session.commit()
|
||||
return jsonify({'success': True}), 200
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@app.route('/delete_vocab/<int:id>', methods=['DELETE'])
|
||||
@login_required
|
||||
def delete_vocab(id):
|
||||
vocab = Vocabulary.query.get(id)
|
||||
if not vocab or vocab.user_id != current_user.id:
|
||||
return jsonify({'error': 'Not found or unauthorized'}), 404
|
||||
|
||||
try:
|
||||
db.session.delete(vocab)
|
||||
db.session.commit()
|
||||
return jsonify({'success': True}), 200
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@app.route('/get_vocab', methods=['GET'])
|
||||
@login_required
|
||||
def get_vocab():
|
||||
vocab_list = Vocabulary.query.filter_by(user_id=current_user.id).all()
|
||||
result = [{'id': v.id, 'english': v.english, 'german': v.german, 'level': v.level} for v in vocab_list]
|
||||
return jsonify(result)
|
||||
|
||||
@app.route('/change_username', methods=['POST'])
|
||||
@login_required
|
||||
def change_username():
|
||||
new_username = request.form.get('new_username')
|
||||
|
||||
if not new_username:
|
||||
return jsonify({'error': 'Benutzername ist erforderlich'}), 400
|
||||
|
||||
if User.query.filter_by(username=new_username).first():
|
||||
return jsonify({'error': 'Benutzername bereits vergeben'}), 409
|
||||
|
||||
current_user.username = new_username
|
||||
try:
|
||||
db.session.commit()
|
||||
return jsonify({'success': True}), 200
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
if __name__ == '__main__':
|
||||
with app.app_context():
|
||||
db.create_all()
|
||||
app.run(debug=True)
|
||||
|
||||
|
||||
|
3
defindatabank.py
Normal file
3
defindatabank.py
Normal file
@ -0,0 +1,3 @@
|
||||
from app import db
|
||||
db.create_all()
|
||||
|
13
forms.py
Normal file
13
forms.py
Normal file
@ -0,0 +1,13 @@
|
||||
from app import db
|
||||
from flask_login import UserMixin
|
||||
|
||||
class User(db.Model, UserMixin):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
username = db.Column(db.String(150), unique=True, nullable=False)
|
||||
password = db.Column(db.String(150), nullable=False)
|
||||
notes = db.relationship('Note', backref='author', lazy=True)
|
||||
|
||||
class Note(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
content = db.Column(db.Text, nullable=False)
|
||||
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
|
BIN
instance/vocab.db
Normal file
BIN
instance/vocab.db
Normal file
Binary file not shown.
18
models.py
Normal file
18
models.py
Normal file
@ -0,0 +1,18 @@
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from flask_login import UserMixin
|
||||
|
||||
db = SQLAlchemy()
|
||||
|
||||
class User(db.Model, UserMixin):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
username = db.Column(db.String(150), unique=True, nullable=False)
|
||||
password = db.Column(db.String(150), nullable=False)
|
||||
notes = db.relationship('Note', backref='author', lazy=True)
|
||||
|
||||
class Note(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
content = db.Column(db.Text, nullable=False)
|
||||
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
|
||||
|
||||
|
||||
|
151
static/script.js
Normal file
151
static/script.js
Normal file
@ -0,0 +1,151 @@
|
||||
const canvas = document.getElementById('drawingCanvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
const textElement = document.getElementById('text');
|
||||
const wordDisplay = document.getElementById('wordDisplay');
|
||||
const textInput = document.getElementById('textInput');
|
||||
const submitAnswer = document.getElementById('submitAnswer');
|
||||
const toggleInput = document.getElementById('toggleInput');
|
||||
const clearCanvas = document.getElementById('clearCanvas');
|
||||
const previewDrawing = document.getElementById('previewDrawing');
|
||||
const recognizedText = document.getElementById('recognizedText');
|
||||
|
||||
let currentWord = null;
|
||||
let currentInputMode = 'text'; // 'text' or 'drawing'
|
||||
let isDrawing = false;
|
||||
|
||||
// Initialize input mode
|
||||
function initializeInputMode() {
|
||||
textInput.style.display = 'block';
|
||||
canvas.style.display = 'none';
|
||||
clearCanvas.style.display = 'none';
|
||||
previewDrawing.style.display = 'none';
|
||||
toggleInput.textContent = 'Zu Zeichnen wechseln';
|
||||
recognizedText.textContent = '';
|
||||
}
|
||||
|
||||
// Load a new word
|
||||
function loadWord() {
|
||||
fetch('/get_random_vocab')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.error) {
|
||||
wordDisplay.textContent = 'Keine Vokabeln gefunden.';
|
||||
} else {
|
||||
currentWord = data;
|
||||
const isEnglish = Math.random() < 0.5;
|
||||
wordDisplay.textContent = isEnglish ? `English: ${currentWord.english}` : `Deutsch: ${currentWord.german}`;
|
||||
textElement.textContent = '';
|
||||
textInput.value = '';
|
||||
recognizedText.textContent = '';
|
||||
if (currentInputMode === 'drawing') {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => console.error('Fehler beim Laden der Vokabel:', error));
|
||||
}
|
||||
|
||||
// Switch between input modes
|
||||
toggleInput.addEventListener('click', () => {
|
||||
if (currentInputMode === 'text') {
|
||||
textInput.style.display = 'none';
|
||||
canvas.style.display = 'block';
|
||||
clearCanvas.style.display = 'inline-block';
|
||||
previewDrawing.style.display = 'inline-block';
|
||||
toggleInput.textContent = 'Zu Texteingabe wechseln';
|
||||
currentInputMode = 'drawing';
|
||||
} else {
|
||||
textInput.style.display = 'block';
|
||||
canvas.style.display = 'none';
|
||||
clearCanvas.style.display = 'none';
|
||||
previewDrawing.style.display = 'none';
|
||||
toggleInput.textContent = 'Zu Zeichnen wechseln';
|
||||
currentInputMode = 'text';
|
||||
}
|
||||
});
|
||||
|
||||
// Clear the canvas
|
||||
clearCanvas.addEventListener('click', () => {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
recognizedText.textContent = ''; // Clear the recognized text as well
|
||||
});
|
||||
|
||||
// Drawing functionality
|
||||
canvas.addEventListener('mousedown', () => {
|
||||
isDrawing = true;
|
||||
});
|
||||
|
||||
canvas.addEventListener('mouseup', () => {
|
||||
isDrawing = false;
|
||||
ctx.beginPath();
|
||||
});
|
||||
|
||||
canvas.addEventListener('mousemove', draw);
|
||||
|
||||
function draw(event) {
|
||||
if (!isDrawing) return;
|
||||
ctx.lineWidth = 5;
|
||||
ctx.lineCap = 'round';
|
||||
ctx.strokeStyle = 'black';
|
||||
|
||||
ctx.lineTo(event.clientX - canvas.offsetLeft, event.clientY - canvas.offsetTop);
|
||||
ctx.stroke();
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(event.clientX - canvas.offsetLeft, event.clientY - canvas.offsetTop);
|
||||
}
|
||||
|
||||
// Preview the drawing recognition
|
||||
previewDrawing.addEventListener('click', () => {
|
||||
const dataURL = canvas.toDataURL('image/png');
|
||||
|
||||
fetch(dataURL)
|
||||
.then(res => res.blob())
|
||||
.then(blob => {
|
||||
Tesseract.recognize(
|
||||
blob,
|
||||
'eng',
|
||||
{ logger: info => console.log(info) }
|
||||
).then(({ data: { text } }) => {
|
||||
recognizedText.textContent = `Erkannter Text: ${text.trim()}`;
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Fehler beim Erkennen des Textes:', error);
|
||||
});
|
||||
});
|
||||
|
||||
// Submit answer
|
||||
submitAnswer.addEventListener('click', () => {
|
||||
let answer = '';
|
||||
|
||||
if (currentInputMode === 'text') {
|
||||
answer = textInput.value.trim();
|
||||
} else {
|
||||
answer = recognizedText.textContent.replace('Erkannter Text:', '').trim();
|
||||
}
|
||||
|
||||
checkAnswer(answer);
|
||||
});
|
||||
|
||||
// Check if the answer is correct
|
||||
function checkAnswer(answer) {
|
||||
const correctAnswer = (wordDisplay.textContent.startsWith('English:') ? currentWord.german : currentWord.english).trim().toLowerCase();
|
||||
if (answer.toLowerCase() === correctAnswer) {
|
||||
textElement.textContent = 'Richtig!';
|
||||
textElement.style.color = 'green';
|
||||
// Update the vocabulary level
|
||||
fetch(`/update_vocab_level/${currentWord.id}`, { method: 'POST' })
|
||||
.then(() => {
|
||||
setTimeout(loadWord, 2000); // Show the result for 2 seconds before loading the next word
|
||||
});
|
||||
} else {
|
||||
textElement.textContent = `Falsch! Die richtige Antwort ist: ${correctAnswer}`;
|
||||
textElement.style.color = 'red';
|
||||
setTimeout(loadWord, 3000); // Show the result for 3 seconds before loading the next word
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the input mode and load the first word
|
||||
initializeInputMode();
|
||||
loadWord();
|
||||
|
33
static/styles.css
Normal file
33
static/styles.css
Normal file
@ -0,0 +1,33 @@
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
background-color: #f4f4f4;
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
canvas {
|
||||
border: 1px solid #000;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
button {
|
||||
margin-top: 10px;
|
||||
padding: 10px 20px;
|
||||
background-color: #007bff;
|
||||
color: #fff;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
160
static/vocab.js
Normal file
160
static/vocab.js
Normal file
@ -0,0 +1,160 @@
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
loadVocab();
|
||||
|
||||
document.getElementById('addVocab').addEventListener('click', () => {
|
||||
const englishInput = document.getElementById('englishInput').value;
|
||||
const germanInput = document.getElementById('germanInput').value;
|
||||
|
||||
if (englishInput && germanInput) {
|
||||
fetch('/add_vocab', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
body: new URLSearchParams({
|
||||
english: englishInput,
|
||||
german: germanInput,
|
||||
level: 1 // Automatisch Level 1 setzen
|
||||
})
|
||||
}).then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
loadVocab();
|
||||
|
||||
document.getElementById('englishInput').value = '';
|
||||
document.getElementById('germanInput').value = '';
|
||||
} else {
|
||||
alert(data.error);
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error('Fehler beim Hinzufügen der Vokabel:', error);
|
||||
});
|
||||
} else {
|
||||
alert('Bitte alle Felder ausfüllen.');
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('backToHome').addEventListener('click', () => {
|
||||
window.location.href = '/'; // Pfad zur Startseite anpassen
|
||||
});
|
||||
});
|
||||
|
||||
function loadVocab() {
|
||||
fetch('/get_vocab')
|
||||
.then(response => response.json())
|
||||
.then(vocabList => {
|
||||
const tables = [1, 2, 3, 4, 5].map(level => document.getElementById(`vocab-list-${level}`));
|
||||
tables.forEach(table => table.querySelector('tbody').innerHTML = '');
|
||||
|
||||
vocabList.forEach(vocab => {
|
||||
const row = document.createElement('tr');
|
||||
row.dataset.id = vocab.id;
|
||||
row.dataset.level = vocab.level; // Level in dataset speichern
|
||||
row.innerHTML = `
|
||||
<td class="editable">${vocab.english}</td>
|
||||
<td class="editable">${vocab.german}</td>
|
||||
<td>
|
||||
<button class="edit-btn">Bearbeiten</button>
|
||||
<button class="delete-btn" data-id="${vocab.id}">Löschen</button>
|
||||
</td>
|
||||
`;
|
||||
|
||||
const targetTable = document.getElementById(`vocab-list-${vocab.level}`);
|
||||
targetTable.querySelector('tbody').appendChild(row);
|
||||
});
|
||||
|
||||
// Event-Listener für den „Löschen“-Button
|
||||
document.querySelectorAll('.delete-btn').forEach(btn => {
|
||||
btn.addEventListener('click', (event) => {
|
||||
const vocabId = event.target.getAttribute('data-id');
|
||||
fetch(`/delete_vocab/${vocabId}`, {
|
||||
method: 'DELETE'
|
||||
}).then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
loadVocab();
|
||||
} else {
|
||||
alert(data.error);
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error('Fehler beim Löschen der Vokabel:', error);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Event-Listener für den „Bearbeiten“-Button
|
||||
document.querySelectorAll('.edit-btn').forEach(btn => {
|
||||
btn.addEventListener('click', (event) => {
|
||||
const row = event.target.closest('tr');
|
||||
const id = row.dataset.id;
|
||||
const english = row.children[0].textContent;
|
||||
const german = row.children[1].textContent;
|
||||
const level = row.dataset.level || 1; // Level aus dem dataset lesen, Fallback auf 1
|
||||
|
||||
// Setze die Zeile in den Bearbeitungsmodus
|
||||
row.innerHTML = `
|
||||
<td><input type="text" class="editable-input" value="${english}"></td>
|
||||
<td><input type="text" class="editable-input" value="${german}"></td>
|
||||
<td class="edit-controls">
|
||||
<div class="level-input-container">
|
||||
<label for="levelInput">Level:</label>
|
||||
<input type="number" id="levelInput" class="level-input" min="1" max="5" value="${level}">
|
||||
</div>
|
||||
<button class="save-btn">Speichern</button>
|
||||
<button class="cancel-btn">Abbrechen</button>
|
||||
</td>
|
||||
`;
|
||||
|
||||
row.querySelector('.save-btn').addEventListener('click', () => {
|
||||
const englishInput = row.querySelector('.editable-input').value;
|
||||
const germanInput = row.querySelectorAll('.editable-input')[1].value;
|
||||
const levelInput = row.querySelector('#levelInput').value;
|
||||
|
||||
fetch(`/update_vocab/${id}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
english: englishInput,
|
||||
german: germanInput,
|
||||
level: parseInt(levelInput, 10)
|
||||
})
|
||||
}).then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
loadVocab();
|
||||
} else {
|
||||
alert(data.error);
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error('Fehler beim Aktualisieren der Vokabel:', error);
|
||||
});
|
||||
});
|
||||
|
||||
row.querySelector('.cancel-btn').addEventListener('click', () => {
|
||||
loadVocab();
|
||||
});
|
||||
});
|
||||
});
|
||||
}).catch(error => {
|
||||
console.error('Fehler beim Laden der Vokabeln:', error);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//sqlite3 anstatt cokkies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
98
templates/login.html
Normal file
98
templates/login.html
Normal file
@ -0,0 +1,98 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Anmelden</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
text-align: center;
|
||||
background-color: #f0f0f0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
h1 {
|
||||
color: #333;
|
||||
margin-top: 50px;
|
||||
}
|
||||
.container {
|
||||
margin-top: 30px;
|
||||
}
|
||||
form {
|
||||
display: inline-block;
|
||||
text-align: left;
|
||||
background-color: #fff;
|
||||
padding: 20px;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 0 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
.form-group {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.form-group input {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.button {
|
||||
display: inline-block;
|
||||
padding: 15px 25px;
|
||||
font-size: 18px;
|
||||
color: white;
|
||||
background-color: #007bff;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
.button:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
.flash-message {
|
||||
margin-bottom: 15px;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.flash-message.success {
|
||||
color: green;
|
||||
border: 1px solid green;
|
||||
background-color: #d4edda;
|
||||
}
|
||||
.flash-message.error {
|
||||
color: red;
|
||||
border: 1px solid red;
|
||||
background-color: #f8d7da;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Anmelden</h1>
|
||||
<div class="container">
|
||||
{% with messages = get_flashed_messages(with_categories=True) %}
|
||||
{% if messages %}
|
||||
{% for category, message in messages %}
|
||||
<div class="flash-message {{ category }}">{{ message }}</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
<form method="post" action="/login">
|
||||
<div class="form-group">
|
||||
<label for="username">Benutzername:</label>
|
||||
<input type="text" id="username" name="username" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">Passwort:</label>
|
||||
<input type="password" id="password" name="password" required>
|
||||
</div>
|
||||
<button type="submit" class="button">Anmelden</button>
|
||||
</form>
|
||||
<a href="/register" class="button">Noch kein Konto? Registrieren</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
77
templates/register.html
Normal file
77
templates/register.html
Normal file
@ -0,0 +1,77 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Register</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
text-align: center;
|
||||
background-color: #f0f0f0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
h1 {
|
||||
color: #333;
|
||||
margin-top: 50px;
|
||||
}
|
||||
.container {
|
||||
margin-top: 30px;
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
margin: 50px auto;
|
||||
padding: 20px;
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.form-group {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.form-group input {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
font-size: 16px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.form-group button {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
font-size: 16px;
|
||||
color: white;
|
||||
background-color: #007bff;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.form-group button:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
.error {
|
||||
color: red;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Register</h1>
|
||||
<div class="container">
|
||||
<form method="POST" action="/register">
|
||||
<div class="form-group">
|
||||
<input type="text" name="username" placeholder="Username" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="password" name="password" placeholder="Password" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button type="submit">Register</button>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<p class="error">{{ error }}</p>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
127
templates/settings.html
Normal file
127
templates/settings.html
Normal file
@ -0,0 +1,127 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Einstellungen</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
text-align: center;
|
||||
background-color: #f0f0f0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
h1 {
|
||||
color: #333;
|
||||
margin-top: 50px;
|
||||
}
|
||||
.container {
|
||||
margin: 20px auto;
|
||||
max-width: 600px;
|
||||
padding: 20px;
|
||||
background-color: white;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.button {
|
||||
display: inline-block;
|
||||
padding: 15px 25px;
|
||||
font-size: 18px;
|
||||
color: white;
|
||||
background-color: #007bff;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
text-decoration: none;
|
||||
margin: 10px;
|
||||
}
|
||||
.button:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
.form-group {
|
||||
margin: 15px 0;
|
||||
}
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.form-group input {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
font-size: 16px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.form-group button {
|
||||
padding: 10px 15px;
|
||||
font-size: 16px;
|
||||
color: white;
|
||||
background-color: #28a745;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.form-group button:hover {
|
||||
background-color: #218838;
|
||||
}
|
||||
.error {
|
||||
color: red;
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Einstellungen</h1>
|
||||
<div class="container">
|
||||
<!-- Formular zum Ändern des Benutzernamens -->
|
||||
<form id="username-form">
|
||||
<div class="form-group">
|
||||
<label for="new-username">Neuer Benutzername:</label>
|
||||
<input type="text" id="new-username" name="new-username" placeholder="Neuer Benutzername" required>
|
||||
</div>
|
||||
<button type="submit">Benutzernamen ändern</button>
|
||||
<div id="username-error" class="error"></div>
|
||||
</form>
|
||||
|
||||
<!-- Formular zum Abmelden -->
|
||||
<form id="logout-form" action="/logout" method="POST" style="display: inline;">
|
||||
<button type="submit" class="button">Abmelden</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.getElementById('username-form').addEventListener('submit', function(event) {
|
||||
event.preventDefault();
|
||||
const newUsername = document.getElementById('new-username').value;
|
||||
|
||||
fetch('/change_username', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
body: new URLSearchParams({
|
||||
new_username: newUsername
|
||||
})
|
||||
}).then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
window.location.reload();
|
||||
} else {
|
||||
document.getElementById('username-error').textContent = data.error;
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error('Error changing username:', error);
|
||||
document.getElementById('username-error').textContent = 'Ein Fehler ist aufgetreten.';
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
47
templates/start.html
Normal file
47
templates/start.html
Normal file
@ -0,0 +1,47 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Vokabeltrainer</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
text-align: center;
|
||||
background-color: #f0f0f0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
h1 {
|
||||
color: #333;
|
||||
margin-top: 50px;
|
||||
}
|
||||
.container {
|
||||
margin-top: 30px;
|
||||
}
|
||||
.button {
|
||||
display: inline-block;
|
||||
padding: 15px 25px;
|
||||
font-size: 18px;
|
||||
color: white;
|
||||
background-color: #007bff;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
text-decoration: none;
|
||||
margin: 10px;
|
||||
}
|
||||
.button:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Vokabeltrainer</h1>
|
||||
<div class="container">
|
||||
<a href="/train" class="button">Vokabeln trainieren</a>
|
||||
<a href="/settings" class="button">Einstellungen</a>
|
||||
<a href="/vocab" class="button">Meine Vokabeln</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
89
templates/train.html
Normal file
89
templates/train.html
Normal file
@ -0,0 +1,89 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Vokabeln trainieren</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
text-align: center;
|
||||
background-color: #f0f0f0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
h1 {
|
||||
color: #333;
|
||||
margin-top: 50px;
|
||||
}
|
||||
#wordDisplay {
|
||||
font-size: 24px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
#inputArea {
|
||||
margin: 20px 0;
|
||||
}
|
||||
#textInput {
|
||||
font-size: 18px;
|
||||
padding: 10px;
|
||||
width: 300px;
|
||||
}
|
||||
canvas {
|
||||
border: 1px solid black;
|
||||
margin: 0 auto;
|
||||
background-color: white;
|
||||
}
|
||||
.button {
|
||||
display: inline-block;
|
||||
padding: 15px 25px;
|
||||
font-size: 18px;
|
||||
color: white;
|
||||
background-color: #007bff;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
text-decoration: none;
|
||||
margin: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.button:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
#result {
|
||||
margin-top: 20px;
|
||||
font-size: 20px;
|
||||
color: green;
|
||||
min-height: 50px;
|
||||
}
|
||||
#recognizedText {
|
||||
margin-top: 10px;
|
||||
font-size: 18px;
|
||||
color: #333;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Vokabeln trainieren</h1>
|
||||
<div id="wordDisplay">Lade...</div>
|
||||
|
||||
<div id="inputArea">
|
||||
<input type="text" id="textInput" placeholder="Antwort eingeben">
|
||||
<canvas id="drawingCanvas" width="600" height="400"></canvas>
|
||||
<button id="clearCanvas" class="button">Zeichnung löschen</button>
|
||||
</div>
|
||||
|
||||
<button id="toggleInput" class="button">Zu Zeichnen wechseln</button>
|
||||
<button id="previewDrawing" class="button">Zeichnung prüfen</button>
|
||||
<button id="submitAnswer" class="button">Abgeben</button>
|
||||
|
||||
<div id="recognizedText"></div>
|
||||
|
||||
<div id="result">
|
||||
<pre id="text"></pre>
|
||||
</div>
|
||||
<a href="/" class="button">Zurück zur Startseite</a>
|
||||
<script src="https://cdn.jsdelivr.net/npm/tesseract.js@2.1.1/dist/tesseract.min.js"></script>
|
||||
<script src="/static/script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
268
templates/vocab.html
Normal file
268
templates/vocab.html
Normal file
@ -0,0 +1,268 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Meine Vokabeln</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
text-align: center;
|
||||
background-color: #f0f0f0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
h1 {
|
||||
color: #333;
|
||||
margin-top: 50px;
|
||||
}
|
||||
.container {
|
||||
margin: 0 auto;
|
||||
max-width: 1200px;
|
||||
padding: 20px;
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 10px 0;
|
||||
border-radius: 15px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
border: 1px solid #cccccc;
|
||||
}
|
||||
th, td {
|
||||
padding: 10px;
|
||||
border-bottom: 1px solid #cccccc;
|
||||
text-align: left;
|
||||
}
|
||||
th {
|
||||
background-color: white; /* Weiß für Tabelleninhalt */
|
||||
}
|
||||
.table-header {
|
||||
text-align: center;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
}
|
||||
.red .table-header, .red th { background-color: #d9534f; color: white; }
|
||||
.orange .table-header, .orange th { background-color: #f0ad4e; color: white; }
|
||||
.yellow .table-header, .yellow th { background-color: #ffd500; color: white; }
|
||||
.green .table-header, .green th { background-color: #4dd64d; color: white; }
|
||||
.darkgreen .table-header, .darkgreen th { background-color: #2f742f; color: white; }
|
||||
.actions button {
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
padding: 5px 10px;
|
||||
margin-right: 5px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
}
|
||||
/* Allgemeine Button-Stile */
|
||||
button {
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
padding: 5px 10px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
color: white;
|
||||
margin-right: 5px;
|
||||
}
|
||||
/* Spezielle Button-Stile */
|
||||
.edit-btn, .delete-btn, .save-btn, .cancel-btn, #moveUp, #moveDown, #moveFinish {
|
||||
border-radius: 5px; /* Gleiche abgerundete Ecken für alle Buttons */
|
||||
padding: 5px 10px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
color: white;
|
||||
}
|
||||
.edit-btn {
|
||||
background-color: #007bff; /* Blau */
|
||||
}
|
||||
.delete-btn {
|
||||
background-color: #dc3545; /* Rot */
|
||||
}
|
||||
.save-btn {
|
||||
background-color: #28a745; /* Grün */
|
||||
}
|
||||
.cancel-btn {
|
||||
background-color: #dc3545; /* Rot */
|
||||
}
|
||||
/* Level-Input-Feld */
|
||||
.level-input-container {
|
||||
display: inline-block;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.level-input {
|
||||
width: 60px;
|
||||
text-align: center;
|
||||
}
|
||||
/* Bearbeitungsmodus-Stil */
|
||||
.editing {
|
||||
background-color: #e7f0ff; /* Hellblau als Hinweis für Bearbeitungsmodus */
|
||||
border: 1px solid #007bff;
|
||||
padding: 5px;
|
||||
}
|
||||
#backToHome {
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
padding: 5px 10px;
|
||||
cursor: pointer;
|
||||
background-color: #007bff; /* Blau */
|
||||
color: white;
|
||||
margin: 20px;
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
}
|
||||
|
||||
/* Stile für den Container */
|
||||
#vocab-add-container {
|
||||
margin-bottom: 20px; /* Abstand nach unten für die Tabelle */
|
||||
}
|
||||
|
||||
/* Stile für das Formular */
|
||||
#add-vocab-form {
|
||||
margin-bottom: 10px; /* Abstand nach unten für den Button */
|
||||
}
|
||||
|
||||
/* Stile für den "Hinzufügen"-Button */
|
||||
#addVocab {
|
||||
background-color: #007bff; /* Hintergrundfarbe des Buttons */
|
||||
color: #fff; /* Schriftfarbe */
|
||||
border: none; /* Kein Rand */
|
||||
border-radius: 5px; /* Abgerundete Ecken */
|
||||
padding: 10px 20px; /* Innenabstand (Padding) */
|
||||
font-size: 16px; /* Schriftgröße */
|
||||
cursor: pointer; /* Zeiger-Cursor beim Hover */
|
||||
transition: background-color 0.3s ease; /* Übergangseffekt für Hover */
|
||||
}
|
||||
|
||||
/* Hover-Effekt für den Button */
|
||||
#addVocab:hover {
|
||||
background-color: #0056b3; /* Dunklere Hintergrundfarbe beim Hover */
|
||||
}
|
||||
|
||||
/* Stile für die Tabelle */
|
||||
#vocab-list {
|
||||
width: 100%; /* Tabellenbreite auf 100% setzen */
|
||||
border-collapse: collapse; /* Ränder zusammenführen */
|
||||
}
|
||||
|
||||
/* Stile für die Tabellenüberschriften */
|
||||
#vocab-list th {
|
||||
background-color: #f2f2f2; /* Hintergrundfarbe der Überschriften */
|
||||
padding: 10px; /* Innenabstand der Überschriften */
|
||||
text-align: left; /* Textausrichtung */
|
||||
border-bottom: 2px solid #ddd; /* Untere Grenze der Überschriften */
|
||||
}
|
||||
|
||||
/* Stile für die Tabellenzellen */
|
||||
#vocab-list td {
|
||||
padding: 10px; /* Innenabstand der Zellen */
|
||||
border-bottom: 1px solid #ddd; /* Untere Grenze der Zellen */
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<button id="backToHome">Zurück zur Startseite</button>
|
||||
<h1>Meine Vokabeln</h1>
|
||||
<div class="container">
|
||||
<!-- Input-Bereich für Vokabeln -->
|
||||
<div id="vocab-input">
|
||||
<input type="text" id="englishInput" placeholder="Englisch">
|
||||
<input type="text" id="germanInput" placeholder="Deutsch">
|
||||
<!-- Das Level-Feld wird entfernt -->
|
||||
<button id="addVocab">Hinzufügen</button>
|
||||
</div>
|
||||
|
||||
<!-- Tabellen für Vokabeln -->
|
||||
<table id="vocab-list-1" class="red">
|
||||
<thead>
|
||||
<tr class="table-header">
|
||||
<td colspan="4">Neue Vokabeln</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Englisch</th>
|
||||
<th>Deutsch</th>
|
||||
<th>Aktionen</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
|
||||
<table id="vocab-list-2" class="orange">
|
||||
<thead>
|
||||
<tr class="table-header">
|
||||
<td colspan="4">Vokabeln, die noch nicht sitzen</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Englisch</th>
|
||||
<th>Deutsch</th>
|
||||
<th>Aktionen</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
|
||||
<table id="vocab-list-3" class="yellow">
|
||||
<thead>
|
||||
<tr class="table-header">
|
||||
<td colspan="4">Vokabeln, die fast sitzen</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Englisch</th>
|
||||
<th>Deutsch</th>
|
||||
<th>Aktionen</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
|
||||
<table id="vocab-list-4" class="green">
|
||||
<thead>
|
||||
<tr class="table-header">
|
||||
<td colspan="4">Vokabeln, die gut sitzen</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Englisch</th>
|
||||
<th>Deutsch</th>
|
||||
<th>Aktionen</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
|
||||
<table id="vocab-list-5" class="darkgreen">
|
||||
<thead>
|
||||
<tr class="table-header">
|
||||
<td colspan="4">Perfekte Vokabeln</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Englisch</th>
|
||||
<th>Deutsch</th>
|
||||
<th>Aktionen</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<script src="static/vocab.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
168
test.txt
Normal file
168
test.txt
Normal file
@ -0,0 +1,168 @@
|
||||
from flask import Flask, request, jsonify, redirect, url_for, session, render_template
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from werkzeug.security import generate_password_hash, check_password_hash
|
||||
from flask_session import Session
|
||||
from flask_login import LoginManager, UserMixin, login_user, login_required, logout_user, current_user, login_manager
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///vocab.db'
|
||||
app.config['SECRET_KEY'] = 'your_secret_key' # Ändere dies zu einem echten geheimen Schlüssel
|
||||
app.config['SESSION_TYPE'] = 'filesystem'
|
||||
app.config['SESSION_PERMANENT'] = False
|
||||
app.config['SESSION_USE_SIGNER'] = True
|
||||
|
||||
db = SQLAlchemy(app)
|
||||
Session(app)
|
||||
|
||||
# Flask-Login Setup
|
||||
login_manager = LoginManager()
|
||||
login_manager.init_app(app)
|
||||
login_manager.login_view = 'login'
|
||||
|
||||
# Models
|
||||
class User(db.Model, UserMixin):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
username = db.Column(db.String(150), unique=True, nullable=False)
|
||||
password = db.Column(db.String(150), nullable=False)
|
||||
vocabularies = db.relationship('Vocabulary', backref='owner', lazy=True)
|
||||
|
||||
class Vocabulary(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
english = db.Column(db.String(100), nullable=False)
|
||||
german = db.Column(db.String(100), nullable=False)
|
||||
level = db.Column(db.Integer, nullable=False)
|
||||
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
|
||||
|
||||
# User loader callback for Flask-Login
|
||||
@login_manager.user_loader
|
||||
def load_user(user_id):
|
||||
return User.query.get(int(user_id))
|
||||
|
||||
# Route Handlers
|
||||
@app.route('/')
|
||||
def index():
|
||||
return render_template('start.html')
|
||||
|
||||
@app.route('/train')
|
||||
@login_required
|
||||
def train():
|
||||
return render_template('train.html') # Stelle sicher, dass diese Datei existiert
|
||||
|
||||
@app.route('/settings')
|
||||
@login_required
|
||||
def settings():
|
||||
return render_template('settings.html') # Stelle sicher, dass diese Datei existiert
|
||||
|
||||
@app.route('/vocab')
|
||||
@login_required
|
||||
def vocab():
|
||||
return render_template('vocab.html')
|
||||
|
||||
@app.route('/register', methods=['POST'])
|
||||
def register():
|
||||
username = request.form.get('username')
|
||||
password = request.form.get('password')
|
||||
if not username or not password:
|
||||
return jsonify({'error': 'Username and password are required'}), 400
|
||||
|
||||
hashed_password = generate_password_hash(password, method='pbkdf2:sha256')
|
||||
user = User(username=username, password=hashed_password)
|
||||
|
||||
try:
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
return jsonify({'success': True}), 201
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@app.route('/login', methods=['POST'])
|
||||
def login():
|
||||
username = request.form['username']
|
||||
password = request.form['password']
|
||||
|
||||
user = User.query.filter_by(username=username).first()
|
||||
if user and check_password_hash(user.password, password):
|
||||
session['logged_in'] = True
|
||||
session['user_id'] = user.id
|
||||
return redirect('/settings')
|
||||
else:
|
||||
return jsonify({"error": "Invalid credentials"}), 401
|
||||
|
||||
|
||||
@app.route('/logout', methods=['POST'])
|
||||
@login_required
|
||||
def logout():
|
||||
logout_user()
|
||||
return redirect(url_for('index'))
|
||||
|
||||
@app.route('/add_vocab', methods=['POST'])
|
||||
@login_required
|
||||
def add_vocab():
|
||||
english = request.form.get('english')
|
||||
german = request.form.get('german')
|
||||
level = int(request.form.get('level'))
|
||||
|
||||
if not english or not german or not level:
|
||||
return jsonify({'error': 'All fields are required'}), 400
|
||||
|
||||
vocab = Vocabulary(english=english, german=german, level=level, user_id=current_user.id)
|
||||
try:
|
||||
db.session.add(vocab)
|
||||
db.session.commit()
|
||||
return jsonify({'success': True}), 201
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@app.route('/update_vocab/<int:id>', methods=['PUT'])
|
||||
@login_required
|
||||
def update_vocab(id):
|
||||
vocab = Vocabulary.query.get(id)
|
||||
if not vocab or vocab.user_id != current_user.id:
|
||||
return jsonify({'error': 'Not found or unauthorized'}), 404
|
||||
|
||||
english = request.json.get('english')
|
||||
german = request.json.get('german')
|
||||
level = request.json.get('level')
|
||||
|
||||
if english:
|
||||
vocab.english = english
|
||||
if german:
|
||||
vocab.german = german
|
||||
if level:
|
||||
vocab.level = level
|
||||
|
||||
try:
|
||||
db.session.commit()
|
||||
return jsonify({'success': True}), 200
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@app.route('/delete_vocab/<int:id>', methods=['DELETE'])
|
||||
@login_required
|
||||
def delete_vocab(id):
|
||||
vocab = Vocabulary.query.get(id)
|
||||
if not vocab or vocab.user_id != current_user.id:
|
||||
return jsonify({'error': 'Not found or unauthorized'}), 404
|
||||
|
||||
try:
|
||||
db.session.delete(vocab)
|
||||
db.session.commit()
|
||||
return jsonify({'success': True}), 200
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@app.route('/get_vocab', methods=['GET'])
|
||||
@login_required
|
||||
def get_vocab():
|
||||
vocab_list = Vocabulary.query.filter_by(user_id=current_user.id).all()
|
||||
result = [{'id': v.id, 'english': v.english, 'german': v.german, 'level': v.level} for v in vocab_list]
|
||||
return jsonify(result)
|
||||
|
||||
if __name__ == '__main__':
|
||||
with app.app_context():
|
||||
db.create_all()
|
||||
app.run(debug=True)
|
Loading…
x
Reference in New Issue
Block a user