Implement database path configuration and enhance category management: Update database URI to use an absolute path, ensure directory creation for the database, and implement default category creation on initialization. Add new routes for searching thoughts and user account management, while improving the UI with navigation updates for better accessibility.
This commit is contained in:
Binary file not shown.
367
website/app.py
367
website/app.py
@@ -21,15 +21,22 @@ from dotenv import load_dotenv
|
||||
# Modelle importieren
|
||||
from models import (
|
||||
db, User, Thought, Comment, MindMapNode, ThoughtRelation, ThoughtRating,
|
||||
RelationType, Category, UserMindmap, UserMindmapNode, MindmapNote
|
||||
RelationType, Category, UserMindmap, UserMindmapNode, MindmapNote,
|
||||
node_thought_association, user_thought_bookmark
|
||||
)
|
||||
|
||||
# Lade .env-Datei
|
||||
load_dotenv()
|
||||
|
||||
# Bestimme den absoluten Pfad zur Datenbank
|
||||
basedir = os.path.abspath(os.path.dirname(__file__))
|
||||
db_path = os.path.join(basedir, 'database', 'systades.db')
|
||||
# Stellen Sie sicher, dass das Verzeichnis existiert
|
||||
os.makedirs(os.path.dirname(db_path), exist_ok=True)
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'default-dev-key')
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///mindmap.db'
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = f'sqlite:///{db_path}'
|
||||
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
|
||||
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=365) # Langlebige Session für Dark Mode-Einstellung
|
||||
|
||||
@@ -67,103 +74,6 @@ def admin_required(f):
|
||||
return f(*args, **kwargs)
|
||||
return decorated_function
|
||||
|
||||
class RelationType(Enum):
|
||||
SUPPORTS = "stützt"
|
||||
CONTRADICTS = "widerspricht"
|
||||
BUILDS_UPON = "baut auf auf"
|
||||
GENERALIZES = "verallgemeinert"
|
||||
SPECIFIES = "spezifiziert"
|
||||
INSPIRES = "inspiriert"
|
||||
|
||||
class ThoughtRelation(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
source_id = db.Column(db.Integer, db.ForeignKey('thought.id'), nullable=False)
|
||||
target_id = db.Column(db.Integer, db.ForeignKey('thought.id'), nullable=False)
|
||||
relation_type = db.Column(db.Enum(RelationType), nullable=False)
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
created_by_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
|
||||
|
||||
class ThoughtRating(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
thought_id = db.Column(db.Integer, db.ForeignKey('thought.id'), nullable=False)
|
||||
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
|
||||
relevance_score = db.Column(db.Integer, nullable=False) # 1-5
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
|
||||
__table_args__ = (
|
||||
db.UniqueConstraint('thought_id', 'user_id', name='unique_thought_rating'),
|
||||
)
|
||||
|
||||
# Database Models
|
||||
class User(UserMixin, db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
username = db.Column(db.String(80), unique=True, nullable=False)
|
||||
email = db.Column(db.String(120), unique=True, nullable=False)
|
||||
password_hash = db.Column(db.String(128))
|
||||
is_admin = db.Column(db.Boolean, default=False)
|
||||
thoughts = db.relationship('Thought', backref='author', lazy=True)
|
||||
|
||||
def set_password(self, password):
|
||||
self.password_hash = generate_password_hash(password)
|
||||
|
||||
def check_password(self, password):
|
||||
return check_password_hash(self.password_hash, password)
|
||||
|
||||
class Thought(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
content = db.Column(db.Text, nullable=False)
|
||||
timestamp = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
branch = db.Column(db.String(100), nullable=False)
|
||||
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
|
||||
title = db.Column(db.String(200), nullable=False)
|
||||
abstract = db.Column(db.Text)
|
||||
keywords = db.Column(db.String(500))
|
||||
color_code = db.Column(db.String(7)) # Hex color code
|
||||
source_type = db.Column(db.String(50)) # PDF, Markdown, Text etc.
|
||||
|
||||
comments = db.relationship('Comment', backref='thought', lazy=True, cascade="all, delete-orphan")
|
||||
ratings = db.relationship('ThoughtRating', backref='thought', lazy=True)
|
||||
|
||||
outgoing_relations = db.relationship(
|
||||
'ThoughtRelation',
|
||||
foreign_keys=[ThoughtRelation.source_id],
|
||||
backref='source_thought',
|
||||
lazy=True
|
||||
)
|
||||
incoming_relations = db.relationship(
|
||||
'ThoughtRelation',
|
||||
foreign_keys=[ThoughtRelation.target_id],
|
||||
backref='target_thought',
|
||||
lazy=True
|
||||
)
|
||||
|
||||
@property
|
||||
def average_rating(self):
|
||||
if not self.ratings:
|
||||
return 0
|
||||
return sum(r.relevance_score for r in self.ratings) / len(self.ratings)
|
||||
|
||||
class Comment(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
content = db.Column(db.Text, nullable=False)
|
||||
timestamp = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
thought_id = db.Column(db.Integer, db.ForeignKey('thought.id'), nullable=False)
|
||||
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
|
||||
author = db.relationship('User', backref='comments')
|
||||
|
||||
class MindMapNode(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(100), nullable=False)
|
||||
parent_id = db.Column(db.Integer, db.ForeignKey('mind_map_node.id'), nullable=True)
|
||||
children = db.relationship('MindMapNode', backref=db.backref('parent', remote_side=[id]))
|
||||
thoughts = db.relationship('Thought', secondary='node_thought_association', backref='nodes')
|
||||
|
||||
# Association table for many-to-many relationship between MindMapNode and Thought
|
||||
node_thought_association = db.Table('node_thought_association',
|
||||
db.Column('node_id', db.Integer, db.ForeignKey('mind_map_node.id'), primary_key=True),
|
||||
db.Column('thought_id', db.Integer, db.ForeignKey('thought.id'), primary_key=True)
|
||||
)
|
||||
|
||||
@login_manager.user_loader
|
||||
def load_user(id):
|
||||
return User.query.get(int(id))
|
||||
@@ -234,6 +144,13 @@ def index():
|
||||
# Route for the mindmap page
|
||||
@app.route('/mindmap')
|
||||
def mindmap():
|
||||
"""Zeigt die öffentliche Mindmap an."""
|
||||
# Sicherstellen, dass wir Kategorien haben
|
||||
with app.app_context():
|
||||
if Category.query.count() == 0:
|
||||
create_default_categories()
|
||||
|
||||
# Hole alle Kategorien der obersten Ebene
|
||||
categories = Category.query.filter_by(parent_id=None).all()
|
||||
return render_template('mindmap.html', categories=categories)
|
||||
|
||||
@@ -690,8 +607,10 @@ def get_mindmap():
|
||||
root_nodes = MindMapNode.query.filter_by(parent_id=None).all()
|
||||
|
||||
if not root_nodes:
|
||||
# Wenn keine Nodes existieren, erstelle Beispieldaten
|
||||
create_sample_mindmap()
|
||||
# Wenn keine Nodes existieren, rufen wir initialize_database direkt auf
|
||||
# anstatt create_sample_mindmap zu verwenden
|
||||
with app.app_context():
|
||||
initialize_database()
|
||||
root_nodes = MindMapNode.query.filter_by(parent_id=None).all()
|
||||
|
||||
# Ergebnisse in hierarchischer Struktur zurückgeben
|
||||
@@ -1039,7 +958,118 @@ def chat_with_assistant():
|
||||
}), 500
|
||||
|
||||
# App-Kontext-Funktion für Initialisierung der Datenbank
|
||||
@app.before_first_request
|
||||
def create_default_categories():
|
||||
"""Erstellt die Standard-Kategorien und wissenschaftlichen Bereiche."""
|
||||
categories = [
|
||||
{
|
||||
'name': 'Naturwissenschaften',
|
||||
'description': 'Empirische Untersuchung und Erklärung natürlicher Phänomene',
|
||||
'color_code': '#4CAF50',
|
||||
'icon': 'flask',
|
||||
'children': [
|
||||
{
|
||||
'name': 'Physik',
|
||||
'description': 'Studium der Materie, Energie und deren Wechselwirkungen',
|
||||
'color_code': '#81C784',
|
||||
'icon': 'atom'
|
||||
},
|
||||
{
|
||||
'name': 'Biologie',
|
||||
'description': 'Wissenschaft des Lebens und lebender Organismen',
|
||||
'color_code': '#66BB6A',
|
||||
'icon': 'leaf'
|
||||
},
|
||||
{
|
||||
'name': 'Chemie',
|
||||
'description': 'Wissenschaft der Materie, ihrer Eigenschaften und Reaktionen',
|
||||
'color_code': '#A5D6A7',
|
||||
'icon': 'vial'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'name': 'Sozialwissenschaften',
|
||||
'description': 'Untersuchung von Gesellschaft und menschlichem Verhalten',
|
||||
'color_code': '#2196F3',
|
||||
'icon': 'users',
|
||||
'children': [
|
||||
{
|
||||
'name': 'Psychologie',
|
||||
'description': 'Wissenschaftliches Studium des Geistes und Verhaltens',
|
||||
'color_code': '#64B5F6',
|
||||
'icon': 'brain'
|
||||
},
|
||||
{
|
||||
'name': 'Soziologie',
|
||||
'description': 'Studium sozialer Beziehungen und Institutionen',
|
||||
'color_code': '#42A5F5',
|
||||
'icon': 'network-wired'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'name': 'Geisteswissenschaften',
|
||||
'description': 'Studium menschlicher Kultur und Kreativität',
|
||||
'color_code': '#9C27B0',
|
||||
'icon': 'book',
|
||||
'children': [
|
||||
{
|
||||
'name': 'Philosophie',
|
||||
'description': 'Untersuchung grundlegender Fragen über Existenz, Wissen und Ethik',
|
||||
'color_code': '#BA68C8',
|
||||
'icon': 'lightbulb'
|
||||
},
|
||||
{
|
||||
'name': 'Geschichte',
|
||||
'description': 'Studium der Vergangenheit und ihres Einflusses auf die Gegenwart',
|
||||
'color_code': '#AB47BC',
|
||||
'icon': 'landmark'
|
||||
},
|
||||
{
|
||||
'name': 'Literatur',
|
||||
'description': 'Studium literarischer Werke und ihrer Bedeutung',
|
||||
'color_code': '#CE93D8',
|
||||
'icon': 'feather'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'name': 'Technologie',
|
||||
'description': 'Anwendung wissenschaftlicher Erkenntnisse für praktische Zwecke',
|
||||
'color_code': '#FF9800',
|
||||
'icon': 'microchip',
|
||||
'children': [
|
||||
{
|
||||
'name': 'Informatik',
|
||||
'description': 'Studium von Computern und Berechnungssystemen',
|
||||
'color_code': '#FFB74D',
|
||||
'icon': 'laptop-code'
|
||||
},
|
||||
{
|
||||
'name': 'Künstliche Intelligenz',
|
||||
'description': 'Entwicklung intelligenter Maschinen und Software',
|
||||
'color_code': '#FFA726',
|
||||
'icon': 'robot'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
# Kategorien in die Datenbank einfügen
|
||||
for category_data in categories:
|
||||
children_data = category_data.pop('children', [])
|
||||
category = Category(**category_data)
|
||||
db.session.add(category)
|
||||
db.session.flush() # Um die ID zu generieren
|
||||
|
||||
# Unterkategorien hinzufügen
|
||||
for child_data in children_data:
|
||||
child = Category(**child_data, parent_id=category.id)
|
||||
db.session.add(child)
|
||||
|
||||
db.session.commit()
|
||||
print("Standard-Kategorien wurden erstellt!")
|
||||
|
||||
def initialize_database():
|
||||
"""Initialisiert die Datenbank, falls sie noch nicht existiert."""
|
||||
db.create_all()
|
||||
@@ -1048,119 +1078,26 @@ def initialize_database():
|
||||
if Category.query.count() == 0:
|
||||
create_default_categories()
|
||||
|
||||
def create_default_categories():
|
||||
"""Erstellt die Standard-Kategorien und wissenschaftlichen Bereiche."""
|
||||
with app.app_context():
|
||||
# Hauptkategorien erstellen
|
||||
categories = [
|
||||
{
|
||||
'name': 'Naturwissenschaften',
|
||||
'description': 'Empirische Untersuchung und Erklärung natürlicher Phänomene',
|
||||
'color_code': '#4CAF50',
|
||||
'icon': 'flask',
|
||||
'children': [
|
||||
{
|
||||
'name': 'Physik',
|
||||
'description': 'Studium der Materie, Energie und deren Wechselwirkungen',
|
||||
'color_code': '#81C784',
|
||||
'icon': 'atom'
|
||||
},
|
||||
{
|
||||
'name': 'Biologie',
|
||||
'description': 'Wissenschaft des Lebens und lebender Organismen',
|
||||
'color_code': '#66BB6A',
|
||||
'icon': 'leaf'
|
||||
},
|
||||
{
|
||||
'name': 'Chemie',
|
||||
'description': 'Wissenschaft der Materie, ihrer Eigenschaften und Reaktionen',
|
||||
'color_code': '#A5D6A7',
|
||||
'icon': 'vial'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'name': 'Sozialwissenschaften',
|
||||
'description': 'Untersuchung von Gesellschaft und menschlichem Verhalten',
|
||||
'color_code': '#2196F3',
|
||||
'icon': 'users',
|
||||
'children': [
|
||||
{
|
||||
'name': 'Psychologie',
|
||||
'description': 'Wissenschaftliches Studium des Geistes und Verhaltens',
|
||||
'color_code': '#64B5F6',
|
||||
'icon': 'brain'
|
||||
},
|
||||
{
|
||||
'name': 'Soziologie',
|
||||
'description': 'Studium sozialer Beziehungen und Institutionen',
|
||||
'color_code': '#42A5F5',
|
||||
'icon': 'network-wired'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'name': 'Geisteswissenschaften',
|
||||
'description': 'Studium menschlicher Kultur und Kreativität',
|
||||
'color_code': '#9C27B0',
|
||||
'icon': 'book',
|
||||
'children': [
|
||||
{
|
||||
'name': 'Philosophie',
|
||||
'description': 'Untersuchung grundlegender Fragen über Existenz, Wissen und Ethik',
|
||||
'color_code': '#BA68C8',
|
||||
'icon': 'lightbulb'
|
||||
},
|
||||
{
|
||||
'name': 'Geschichte',
|
||||
'description': 'Studium der Vergangenheit und ihres Einflusses auf die Gegenwart',
|
||||
'color_code': '#AB47BC',
|
||||
'icon': 'landmark'
|
||||
},
|
||||
{
|
||||
'name': 'Literatur',
|
||||
'description': 'Studium literarischer Werke und ihrer Bedeutung',
|
||||
'color_code': '#CE93D8',
|
||||
'icon': 'feather'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'name': 'Technologie',
|
||||
'description': 'Anwendung wissenschaftlicher Erkenntnisse für praktische Zwecke',
|
||||
'color_code': '#FF9800',
|
||||
'icon': 'microchip',
|
||||
'children': [
|
||||
{
|
||||
'name': 'Informatik',
|
||||
'description': 'Studium von Computern und Berechnungssystemen',
|
||||
'color_code': '#FFB74D',
|
||||
'icon': 'laptop-code'
|
||||
},
|
||||
{
|
||||
'name': 'Künstliche Intelligenz',
|
||||
'description': 'Entwicklung intelligenter Maschinen und Software',
|
||||
'color_code': '#FFA726',
|
||||
'icon': 'robot'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
# Kategorien in die Datenbank einfügen
|
||||
for category_data in categories:
|
||||
children_data = category_data.pop('children', [])
|
||||
category = Category(**category_data)
|
||||
db.session.add(category)
|
||||
db.session.flush() # Um die ID zu generieren
|
||||
|
||||
# Unterkategorien hinzufügen
|
||||
for child_data in children_data:
|
||||
child = Category(**child_data, parent_id=category.id)
|
||||
db.session.add(child)
|
||||
|
||||
db.session.commit()
|
||||
print("Standard-Kategorien wurden erstellt!")
|
||||
# Führe die Datenbankinitialisierung beim Starten der App aus
|
||||
with app.app_context():
|
||||
initialize_database()
|
||||
|
||||
@app.route('/search')
|
||||
def search_thoughts_page():
|
||||
"""Seite zur Gedankensuche anzeigen."""
|
||||
return render_template('search.html')
|
||||
|
||||
@app.route('/my_account')
|
||||
def my_account():
|
||||
"""Zeigt die persönliche Merkliste an."""
|
||||
if not current_user.is_authenticated:
|
||||
flash('Bitte melde dich an, um auf deine Merkliste zuzugreifen.', 'warning')
|
||||
return redirect(url_for('login'))
|
||||
|
||||
# Hole die Lesezeichen des Benutzers
|
||||
bookmarked_thoughts = current_user.bookmarked_thoughts
|
||||
|
||||
return render_template('my_account.html', bookmarked_thoughts=bookmarked_thoughts)
|
||||
|
||||
# Flask starten
|
||||
if __name__ == '__main__':
|
||||
|
||||
BIN
website/database/systades.db
Normal file
BIN
website/database/systades.db
Normal file
Binary file not shown.
@@ -9,4 +9,5 @@ OPENAI_API_KEY=sk-dein-openai-api-schluessel-hier
|
||||
|
||||
# Datenbank
|
||||
# Bei Bedarf kann hier eine andere Datenbank-URL angegeben werden
|
||||
# SQLALCHEMY_DATABASE_URI=sqlite:///mindmap.db
|
||||
# Der Pfad wird relativ zum Projektverzeichnis angegeben
|
||||
# SQLALCHEMY_DATABASE_URI=sqlite:////absoluter/pfad/zu/database/systades.db
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from app import app, initialize_database
|
||||
from app import app, initialize_database, db_path
|
||||
from models import db, User, Thought, Comment, MindMapNode, ThoughtRelation, ThoughtRating, RelationType
|
||||
from models import Category, UserMindmap, UserMindmapNode, MindmapNote
|
||||
import os
|
||||
@@ -10,8 +10,11 @@ def init_database():
|
||||
"""Initialisiert die Datenbank mit Beispieldaten."""
|
||||
with app.app_context():
|
||||
# Datenbank löschen und neu erstellen
|
||||
if os.path.exists('instance/mindmap.db'):
|
||||
os.remove('instance/mindmap.db')
|
||||
if os.path.exists(db_path):
|
||||
os.remove(db_path)
|
||||
|
||||
# Stellen Sie sicher, dass das Verzeichnis existiert
|
||||
os.makedirs(os.path.dirname(db_path), exist_ok=True)
|
||||
|
||||
db.create_all()
|
||||
|
||||
|
||||
Binary file not shown.
@@ -545,8 +545,8 @@
|
||||
<!-- App-Container -->
|
||||
<div id="app-container" class="flex flex-col min-h-screen" x-data="layout">
|
||||
<!-- Hauptnavigation -->
|
||||
<nav class="sticky top-0 left-0 right-0 z-50 transition-all duration-300 py-4 px-5 border-b"
|
||||
x-bind:class="darkMode ? 'glass-navbar-dark' : 'glass-navbar-light'">
|
||||
<nav class="sticky top-0 left-0 right-0 z-50 transition-all duration-300 py-4 px-5 border-b glass-morphism"
|
||||
x-bind:class="darkMode ? 'glass-navbar-dark border-white/10' : 'glass-navbar-light border-gray-200/50'">
|
||||
<div class="container mx-auto flex justify-between items-center">
|
||||
<!-- Logo -->
|
||||
<a href="{{ url_for('index') }}" class="flex items-center group">
|
||||
@@ -569,6 +569,13 @@
|
||||
: '{{ 'nav-link-light-active' if request.endpoint == 'mindmap' else 'nav-link-light' }}'">
|
||||
<i class="fa-solid fa-diagram-project mr-2"></i>Mindmap
|
||||
</a>
|
||||
<a href="{{ url_for('search_thoughts_page') }}"
|
||||
class="nav-link flex items-center"
|
||||
x-bind:class="darkMode
|
||||
? '{{ 'nav-link-active' if request.endpoint == 'search_thoughts_page' else '' }}'
|
||||
: '{{ 'nav-link-light-active' if request.endpoint == 'search_thoughts_page' else 'nav-link-light' }}'">
|
||||
<i class="fa-solid fa-search mr-2"></i>Suche
|
||||
</a>
|
||||
{% if current_user.is_authenticated %}
|
||||
<a href="{{ url_for('profile') }}"
|
||||
class="nav-link flex items-center"
|
||||
@@ -715,6 +722,13 @@
|
||||
: '{{ 'bg-purple-500/10 text-gray-900' if request.endpoint == 'mindmap' else 'text-gray-700 hover:bg-gray-100 hover:text-gray-900' }}'">
|
||||
<i class="fa-solid fa-diagram-project w-5 mr-3"></i>Mindmap
|
||||
</a>
|
||||
<a href="{{ url_for('search_thoughts_page') }}"
|
||||
class="block py-3.5 px-4 rounded-xl transition-all duration-200 flex items-center"
|
||||
x-bind:class="darkMode
|
||||
? '{{ 'bg-purple-500/20 text-white' if request.endpoint == 'search_thoughts_page' else 'text-white/80 hover:bg-gray-800/80 hover:text-white' }}'
|
||||
: '{{ 'bg-purple-500/10 text-gray-900' if request.endpoint == 'search_thoughts_page' else 'text-gray-700 hover:bg-gray-100 hover:text-gray-900' }}'">
|
||||
<i class="fa-solid fa-search w-5 mr-3"></i>Suche
|
||||
</a>
|
||||
{% if current_user.is_authenticated %}
|
||||
<a href="{{ url_for('profile') }}"
|
||||
class="block py-3.5 px-4 rounded-xl transition-all duration-200 flex items-center"
|
||||
|
||||
Reference in New Issue
Block a user