init
Some checks failed
Deploy to ras-dan-01 / deploy (push) Has been cancelled

This commit is contained in:
2025-08-26 14:35:48 +02:00
commit 370974a57d
22 changed files with 1523 additions and 0 deletions

View File

@@ -0,0 +1,34 @@
name: Deploy to ras-dan-01
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install Ansible
run: |
python3 -m pip install --user ansible
ansible-galaxy collection install community.docker
- name: Setup Ansible Vault
run: |
mkdir -p ~/.ansible
echo "${{ secrets.VAULT_PASSWORD }}" > ~/.ansible/.vault-pass
chmod 600 ~/.ansible/.vault-pass
- name: Setup SSH key
run: |
mkdir -p ~/.ssh
echo "${{ secrets.ANSIBLE_PRIVATE_KEY }}" > ~/.ssh/ansible
chmod 600 ~/.ssh/ansible
- name: Run Ansible Playbook
run: ansible-playbook deploy_remote.yml

19
Dockerfile Normal file
View File

@@ -0,0 +1,19 @@
FROM python:3.9-slim-bookworm
WORKDIR /app
# Install build dependencies for mariadb-connector-c
RUN apt-get update && apt-get install -y \
default-libmysqlclient-dev \
gcc \
pkg-config \
&& rm -rf /var/lib/apt/lists/*
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 5082
CMD ["python3", "main.py"]

118
README.md Normal file
View File

@@ -0,0 +1,118 @@
# Movie-DB
Eine webbasierte Anwendung zur Verwaltung einer persönlichen Filmsammlung.
## Funktionsumfang (Features)
* **Benutzer-System:** Vollständiges Registrierungs- und Login-System, sodass jeder Benutzer seine eigene, private Filmsammlung verwalten kann.
* **Persönliche Filmlisten:** Jeder Benutzer sieht und durchsucht nur die Filme, die er selbst hinzugefügt hat.
* **Filme hinzufügen:** Filme können manuell oder durch ein automatisiertes Scraping von IMDb hinzugefügt werden.
* **Umfassende Suche:** Eine globale Suchleiste ermöglicht die gleichzeitige Suche nach Titel, Genre und Regisseur.
* **Paginierung:** Die Filmliste ist in Seiten aufgeteilt, um auch bei großen Sammlungen übersichtlich zu bleiben.
* **Sichere Verbindung:** Die Anwendung wird über HTTPS mit einem selbst-signierten SSL-Zertifikat bereitgestellt.
* **Containerisierung:** Die gesamte Anwendung ist mit Docker und Docker Compose containerisiert, was die Bereitstellung vereinfacht.
* **Automatisierte Bereitstellung:** Ein Ansible-Playbook automatisiert das Deployment der Anwendung.
## Voraussetzungen
Stellen Sie sicher, dass die folgende Software auf Ihrem System installiert ist:
* Python 3
* pip
* Docker
* Docker Compose
* Ansible
## Inbetriebnahme
Es gibt drei Möglichkeiten, die Anwendung zu starten.
### 1. Direkter Start mit Python
Dies ist die klassische Methode für die lokale Entwicklung.
1. **Abhängigkeiten installieren:**
```bash
pip install -r requirements.txt
```
2. **Anwendung starten:**
```bash
python3 main.py
```
3. **Zugriff:** Öffnen Sie Ihren Browser und gehen Sie zu `https://localhost:5082`. Sie müssen eine Browser-Warnung aufgrund des selbst-signierten Zertifikats akzeptieren.
### 2. Start mit Docker
Diese Methode kapselt die Anwendung in einem Container.
1. **Docker-Image erstellen:**
```bash
docker build -t movie-db-app .
```
2. **Docker-Container starten:**
```bash
docker run -p 5082:5082 movie-db-app
```
3. **Zugriff:** Öffnen Sie Ihren Browser und gehen Sie zu `https://localhost:5082`.
### 3. Start mit Ansible und Docker Compose (Empfohlen)
Dies ist die automatisierte Methode, die einen sauberen Deployment-Prozess sicherstellt.
1. **Ansible Docker-Sammlung installieren (falls noch nicht geschehen):**
```bash
ansible-galaxy collection install community.docker
```
2. **Ansible-Playbook ausführen:**
```bash
ansible-playbook start.yml
```
(Möglicherweise müssen Sie Ihr `sudo`-Passwort eingeben.)
3. **Zugriff:** Öffnen Sie Ihren Browser und gehen Sie zu `https://localhost:5082`.
## Projektstruktur
Die wichtigsten Dateien und Ordner im Überblick:
```
.
├── Dockerfile
├── docker-compose.yml
├── main.py
├── moviedb_func.py
├── movie_db.db
├── README.md
├── requirements.txt
├── start.yml
├── cert.pem
├── key.pem
└── templates/
├── base.html
├── index.html
├── login.html
└── register.html
```
* `main.py`: Die Hauptdatei der Flask-Anwendung. Sie enthält die Routen und die Anwendungslogik.
* `moviedb_func.py`: Enthält die gesamte Geschäftslogik, Datenbankfunktionen und die `User`-Klasse.
* `templates/`: Der Ordner für alle HTML-Templates.
* `movie_db.db`: Die SQLite-Datenbankdatei.
* `Dockerfile`: Die Bauanleitung für das Docker-Image der Anwendung.
* `docker-compose.yml`: Definiert die Anwendung als Docker-Dienst für eine einfache Verwaltung.
* `start.yml`: Das Ansible-Playbook für die automatisierte Bereitstellung.
* `requirements.txt`: Liste der Python-Abhängigkeiten.
* `cert.pem`, `key.pem`: Das selbst-signierte SSL-Zertifikat und der private Schlüssel.
## Benutzung
1. **Registrieren:** Erstellen Sie auf der Registrierungsseite einen neuen Benutzeraccount.
2. **Einloggen:** Melden Sie sich mit Ihren neuen Zugangsdaten an.
3. **Filme hinzufügen:** Nutzen Sie den "Film hinzufügen"-Button, um Filme entweder durch die IMDb-Suche oder manuell zu Ihrer Sammlung hinzuzufügen.
4. **Suchen:** Verwenden Sie die Suchleiste, um Ihre Sammlung nach Titeln, Genres oder Regisseuren zu durchsuchen.
## Mögliche zukünftige Erweiterungen
* **Bearbeiten & Löschen:** Funktionen zum Bearbeiten und Löschen von bereits hinzugefügten Filmen.
* **Detailseite:** Eine eigene Seite für jeden Film mit mehr Details (Schauspieler, Handlung, etc.).
* **Sortieroptionen:** Die Möglichkeit, die Filmliste nach verschiedenen Kriterien zu sortieren.

12
ansible.cfg Normal file
View File

@@ -0,0 +1,12 @@
[defaults]
inventory = ./inv/
log_path = ~/.ansible/log_ansible.log
private_key_file = ~/.ssh/ansible
retry_files_enabled = true
retry_files_save_path = ~/.ansible/retry-files
force_handlers = true
vault_identity_list = ~/.ansible/.vault-pass
#Gather CONFIG
gathering = smart
fact_caching = yaml
fact_caching_connection = ~/.ansible/fact_caching

29
cert.pem Normal file
View File

@@ -0,0 +1,29 @@
-----BEGIN CERTIFICATE-----
MIIFCTCCAvGgAwIBAgIUDplU7xn71S8Rb5v7dXQ9cZmD1S4wDQYJKoZIhvcNAQEL
BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI1MDgyNjExNTIwMVoXDTI2MDgy
NjExNTIwMVowFDESMBAGA1UEAwwJbG9jYWxob3N0MIICIjANBgkqhkiG9w0BAQEF
AAOCAg8AMIICCgKCAgEA0J1PNqsrg+BL/zy7nTaRwfP96gGGvnQtHFHBifX7qF6e
bXKvxU77tGE9D0i5t36N2ktMl6PGI2CmhFz5mkcVrXnfWGLoYV2jLlYQOdKvwFJo
7R4QuGa8nLHLdGUkhSSgRxkol9rHUcok5ZpfA1iVFbrpgqel3Lmh0h3qXx7YiPTo
Y8ZhFQDoF4TXJYGrSC2BlAtbOSwT/L/n983VIA3dugzQ+3H78OWmfGcCB8Q8d5hN
FP+igWj/29U3vCmvlgXpzaM+X1TtL9uxqm/GWw/+jIj4Wn+2mxhMFS2B/OeWEW5y
oTYvZywK/eap9ieyKpzi+lND3eTvD+icpj1hFBt5qa+R9P1bj369q6IMSxNrf5YJ
cDesywy5MIIAPN+21nNYi13ZSwSnfEI/13klBk0rdhBulDAPGvH1DZEAc4ePagMQ
uhJtW6wl50yNJADxszAa1mbIIk/v7r+vPt32XTylqlaI3koU2XjjmyXcsAFLdgcS
ymCtxNWe37U58qb1J3QKDbaskq6kJiJBWZlASa3ANp19UdWvAnuznhhfXws0cV6b
qFoeKKkyWOsPln+Ifuw1VONJOMJmoT4L+kHuKnMDqdusuGdIlJXfeW00vMLIRx8O
8j8dUHfFqWRnE98VO9WZlsbagYgW96n9zyeZvfW3VgncrqMVZYC2YadbSH+hG40C
AwEAAaNTMFEwHQYDVR0OBBYEFIp7yQD9oKNeYS2hFMBzgIQWoZzGMB8GA1UdIwQY
MBaAFIp7yQD9oKNeYS2hFMBzgIQWoZzGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZI
hvcNAQELBQADggIBAHQpvGXNoeHjmVxuMmZSuiccOdfJXiWBTM7g7Eqq0nzTuzK/
IvmBkTxK8BG1j9X+9vBDJ3KZAIMVviepqxUNMGMQNa9DcVnOxaB01UdRqM9hh+jg
EkBgN325BgrX5Vh/HS7MX2VboM+WmBcfWtt3afKv2Tv2mzZ6bPPDdbH/SJouxfXD
Ea+n3IXniskmY7kUVTy7uRQUAJXyLulcrXAJgOwws80JEXrrjZwcpt9PxzzxXu4c
TUZQ1A5FgHgXecYM1WJA9nQr7NH9TewssKaoYOEltwOxbqc0bxAOtFX02dSTpRY/
0LwLC1Nqm34c96CX1t/WLfWf/RY2j5FoqFKxOY6CnpCW4DXjMZ3OEPmikT6XJ85G
q9LB4AqT/G1cyQzoT78SwDk6Ln3ahzP0MiaQeZ/KBJbqnfxOtcDWAmRTtIPYpMYH
eW8K+Y0Z3UvR+FqawuR5pvz5T/XYC/tutK4+KLCecorruT/Ye5qEALb+XcugU5iE
3FeZ/iAUxn8UKAGtfogQHYMOZ5lllAgRCKYhEYp28Rlkrk9LBXPzMimUfPZ2RSwx
XSkTYZEFEMw+4YQdOGqesW/hGpG6Ojmb/bmqG5TAzTdFUXsc6YgrJIP7lCIkEBnb
jF+S8Yb9Gq1s3ohiyI71kdVoOgWUGbqkkQ9xEZQzRzB1zcOLKelpdI7BEkjE
-----END CERTIFICATE-----

48
deploy_remote.yml Normal file
View File

@@ -0,0 +1,48 @@
---
- name: Deploy and start Movie-DB Application
hosts: all
become: yes
vars:
project_name: movie-db
install_dir: "/opt/{{ project_name }}"
tasks:
- name: Create installation directory
ansible.builtin.file:
path: "{{ install_dir }}"
state: directory
mode: '0755'
- name: Copy project files
ansible.builtin.copy:
src: "{{ item }}"
dest: "{{ install_dir }}/"
owner: root
group: root
mode: '0644'
with_items:
- cert.pem
- docker-compose.yml
- Dockerfile
- genre_list
- imdb_suche.py
- key.pem
- main.py
- moviedb_func.py
- regie_name.csv
- requirements.txt
- name: Copy templates directory
ansible.builtin.copy:
src: templates/
dest: "{{ install_dir }}/templates/"
owner: root
group: root
- name: Start application with Docker Compose
community.docker.docker_compose:
project_src: "{{ install_dir }}"
state: present
restarted: yes
build: yes

10
docker-compose.yml Normal file
View File

@@ -0,0 +1,10 @@
services:
web:
build: .
container_name: movie-db-web
ports:
- "5082:5082"
volumes:
- .:/app
restart: always
# command: python3 main.py # Use this if you want to run in foreground for debugging

36
genre_list Normal file
View File

@@ -0,0 +1,36 @@
action
adventure
animation
biography
comedy
crime
cult movie
disney
documentary
drama
erotic
family
fantasy
film-noir
gangster
gay and lesbian
history
horror
military
music
musical
mystery
nature
neo-noir
period
pixar
road movie
romance
sci-fi
short
spy
super hero
thriller
visually stunning
war
western

59
imdb_suche.py Normal file
View File

@@ -0,0 +1,59 @@
# Benötigt die Installation von 'requests' und 'beautifulsoup4'
# pip install requests beautifulsoup4
import requests
from bs4 import BeautifulSoup
import sys
import re
def suche_film_url(film_titel):
"""
Sucht auf IMDb nach einem Film und gibt die deutsche URL der Filmseite zurück.
Args:
film_titel: Der Titel des Films, nach dem gesucht werden soll.
Returns:
Die deutsche URL der Filmseite oder None, wenn nichts gefunden wurde.
"""
such_url = f"https://www.imdb.com/find?q={film_titel.replace(' ', '+')}"
# Header, um die deutsche Seite zu bevorzugen
headers = {
'Accept-Language': 'de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7',
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36'
}
try:
response = requests.get(such_url, headers=headers)
response.raise_for_status() # Löst eine Ausnahme für HTTP-Fehler aus
soup = BeautifulSoup(response.text, 'html.parser')
# Finden des ersten Links im Titel-Ergebnisbereich
ergebnis_bereich = soup.find('section', {'data-testid': 'find-results-section-title'})
if ergebnis_bereich:
link_tag = ergebnis_bereich.find('a', href=re.compile(r'/title/tt\d+'))
if link_tag and 'href' in link_tag.attrs:
pfad = link_tag['href']
# Extrahieren der Titel-ID (z.B. tt0133093)
match = re.search(r'(tt\d+)', pfad)
if match:
titel_id = match.group(1)
return f"https://www.imdb.com/de/title/{titel_id}"
except requests.exceptions.RequestException as e:
print(f"Ein Fehler bei der Anfrage ist aufgetreten: {e}")
return None
return None
if __name__ == "__main__":
if len(sys.argv) > 1:
film_name = " ".join(sys.argv[1:])
url = suche_film_url(film_name)
if url:
print(f"URL für '{film_name}' gefunden: {url}")
else:
print(f"Konnte keinen Film mit dem Titel '{film_name}' finden.")
else:
print("Benutzung: python imdb_suche.py <film_titel>")

8
inv/hosts Normal file
View File

@@ -0,0 +1,8 @@
FlaskServer:
hosts:
ras-dan-01:
vars:
ansible_python_interpreter: "/usr/bin/python3"
ansible_ssh_common_args: '-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null'
ansible_user: jonnybravo
ansible_become: trueras-dan-01

52
key.pem Normal file
View File

@@ -0,0 +1,52 @@
-----BEGIN PRIVATE KEY-----
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
krvHLxyb9R+o1UzlvYkUiF+lOMVpLB8=
-----END PRIVATE KEY-----

127
main.py Normal file
View File

@@ -0,0 +1,127 @@
import flask
from flask import render_template, redirect, url_for, flash
from flask_login import LoginManager, login_user, logout_user, login_required, current_user
import moviedb_func
app = flask.Flask(__name__)
app.secret_key = "Start1234!"
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = "login"
@login_manager.user_loader
def load_user(user_id):
return moviedb_func.get_user_by_id(user_id)
@app.route("/register", methods=["GET", "POST"])
def register():
if flask.request.method == "POST":
username = flask.request.form.get("username")
password = flask.request.form.get("password")
if moviedb_func.get_user_by_username(username):
flash("Benutzername existiert bereits")
return redirect(url_for("register"))
moviedb_func.add_user(username, password)
return redirect(url_for("login"))
return render_template("register.html")
@app.route("/login", methods=["GET", "POST"])
def login():
if flask.request.method == "POST":
username = flask.request.form.get("username")
password = flask.request.form.get("password")
user = moviedb_func.get_user_by_username(username)
if user and user.check_password(password):
login_user(user)
return redirect(url_for("index"))
flash("Ungültiger Benutzername oder Passwort")
return render_template("login.html")
@app.route("/logout")
@login_required
def logout():
logout_user()
return redirect(url_for("login"))
@app.route("/", methods=["GET", "POST"])
@login_required
def index():
page = flask.request.args.get('page', 1, type=int)
per_page = 20
search_query = None
if flask.request.method == "POST":
search_query = flask.request.form.get("search")
else:
search_query = flask.request.args.get("search")
data_all, total_pages = moviedb_func.show_movie_list(user_id=current_user.id, search_query=search_query, page=page, per_page=per_page)
return render_template(
"index.html",
sitename="Meine Movieliste !!!",
data_all=data_all,
total_pages=total_pages,
current_page=page,
search_query=search_query
)
@app.get("/add_movie")
def add_movie():
return render_template(
"add_movie.html",
sitename="Meine Movieliste !!!",
url="output",
select_movie="add_movie",
select_genre="add_genre",
select_medium="add_medium",
data=moviedb_func.all_select(),
data_medium=moviedb_func.all_select(what_select="medium")
)
@app.post("/output")
@login_required
def csv_output():
select_add_movie = flask.request.form["add_movie"]
select_medium = flask.request.form["add_medium"]
select_medium_id = moviedb_func.search_id(
search_name=select_medium, select_from="medium", select_where="medium")
if moviedb_func.scrape_and_add_movie(movie_name=select_add_movie,
medium_id=select_medium_id, user_id=current_user.id):
return redirect(url_for('index'))
else:
return redirect(url_for('add_movie_manually', movie_name=select_add_movie, medium_id=select_medium_id))
@app.get("/add_movie_manually")
@login_required
def add_movie_manually():
movie_name = flask.request.args.get('movie_name')
medium_id = flask.request.args.get('medium_id')
return render_template(
"add_movie_manually.html",
sitename="Manuelle Eingabe",
movie_name=movie_name,
medium_id=medium_id
)
@app.post("/save_manual")
@login_required
def save_manual():
movie_name = flask.request.form["movie_name"]
medium_id = flask.request.form["medium_id"]
director_name = flask.request.form["director_name"]
release_year = flask.request.form["release_year"]
genre_name = flask.request.form["genre_name"]
moviedb_func.add_manual_movie(movie_name=movie_name, medium_id=int(medium_id), director_name=director_name, release_year=int(release_year), genre_name=genre_name, user_id=current_user.id)
return redirect("/")
if __name__ == "__main__":
moviedb_func.create_movie_database()
app.run(port=5082, host="0.0.0.0", debug=True, ssl_context=('cert.pem', 'key.pem'))

427
moviedb_func.py Normal file
View File

@@ -0,0 +1,427 @@
import DBcm
from mariadb import ProgrammingError
import pandas as pd
import sqlite3
import urllib.request
import json
import re
import ssl
import imdb_suche
from werkzeug.security import generate_password_hash, check_password_hash
from flask_login import UserMixin
def get_movie_details(imdb_url):
"""
Ruft eine IMDb-Filmseite auf und extrahiert den Regisseur und die Hauptdarsteller.
Args:
imdb_url (str): Die vollständige URL zur IMDb-Filmseite.
Returns:
dict: Ein Wörterbuch mit "director" und "actors" oder None bei einem Fehler.
"""
if not re.match(r"https://www.imdb.com/de/title/tt\d+/?", imdb_url):
print(f"Fehler: Ungültige IMDb-URL: {imdb_url}. Die URL sollte so aussehen: https://www.imdb.com/de/title/tt...")
return None
try:
# SSL-Kontext erstellen, um Zertifikatsüberprüfungsfehler zu umgehen
context = ssl._create_unverified_context()
# User-Agent setzen, um wie ein Browser auszusehen und Blockaden zu vermeiden
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'}
req = urllib.request.Request(imdb_url, headers=headers)
with urllib.request.urlopen(req, context=context) as response:
html_content = response.read().decode('utf-8')
# Suchen Sie nach dem strukturierten Datenblock (JSON-LD), der Filminformationen enthält
match = re.search(
r'<script type="application/ld\+json">(.*?)</script>', html_content, re.DOTALL)
if not match:
print("Fehler: Konnte den JSON-LD-Datenblock auf der Seite nicht finden.")
return None
json_data = json.loads(match.group(1))
# Extrahieren des Regisseurs
director = [d.get('name') for d in json_data.get("director", [])] if isinstance(
json_data.get("director"), list) else [json_data.get("director", {}).get("name")]
# Extrahieren der Schauspieler
actors = [a.get('name') for a in json_data.get("actor", [])]
# Extrahieren des Erscheinungsjahres
release_year = json_data.get("datePublished", "").split("-")[0]
# Extrahieren des Genres
genre = json_data.get("genre", [])
if isinstance(genre, list):
genre = ", ".join(genre)
return {
"director": [d for d in director if d],
"actors": [a for a in actors if a],
"release_year": release_year,
"genre": genre
}
except urllib.error.HTTPError as e:
print(f"HTTP-Fehler beim Abrufen der URL: {e.code} {e.reason}")
return None
except Exception as e:
print(f"Ein Fehler ist aufgetreten: {e}")
return None
def create_movie_database(db_name="movie_db.db"):
create_user_table = """
CREATE TABLE IF NOT EXISTS user (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
username VARCHAR(64) NOT NULL UNIQUE,
password_hash VARCHAR(128) NOT NULL
);
"""
create_my_list = """
create table if not exists movie_list (
id integer not null primary key autoincrement,
titel varchar(64) not null,
genre_id integer not null,
regie_id integer,
medium_id integer not null,
release_year integer,
user_id integer not null,
FOREIGN KEY (user_id) REFERENCES user (id)
)
"""
create_genre = """
create table if not exists genre (
id integer not null primary key autoincrement,
name varchar(64) not null
)
"""
create_regie = """
create table if not exists regie (
id integer not null primary key autoincrement,
surname varchar(64) not null,
lastname varchar(64) not null
)
"""
create_medium = """
create table if not exists medium (
id integer not null primary key autoincrement,
medium varchar(64) not null
)
"""
ADD_GENRE_VALUE = """
INSERT INTO genre(name)
SELECT ?
WHERE NOT EXISTS (SELECT 1 FROM genre WHERE name = ?);
"""
ADD_MEDIUM_VALUE = """
INSERT INTO medium(medium)
SELECT ?
WHERE NOT EXISTS (SELECT 1 FROM medium WHERE medium = ?);
"""
ADD_REGIE_VALUE = """
INSERT INTO regie (surname, lastname )
SELECT ?, ?
WHERE NOT EXISTS
(SELECT surname, lastname
FROM regie
WHERE surname = ? AND lastname = ?);
"""
with DBcm.UseDatabase(db_name) as db:
db.execute(create_user_table)
db.execute(create_my_list)
db.execute(create_genre)
db.execute(create_regie)
db.execute(create_medium)
with open("genre_list", "r") as fs:
for genre_value in fs.readlines():
with DBcm.UseDatabase(db_name) as db:
db.execute(ADD_GENRE_VALUE,
(genre_value.strip(), genre_value.strip()))
usecols = ["Name", "Vorname"]
air = pd.read_csv("regie_name.csv", usecols=usecols)
for count, (reg_name, reg_vorname) in enumerate(zip(air["Name"], air["Vorname"])):
# print(count, reg_vorname, reg_name)
with DBcm.UseDatabase(db_name) as db:
db.execute(ADD_REGIE_VALUE, (reg_vorname,
reg_name, reg_vorname, reg_name))
LISTE_MEDIUM = ["BlueRay", "DVD", "Datei",
"BlueRay Steelbook", "DVD Steelbook"]
with DBcm.UseDatabase(db_name) as db:
for MEDIUM in LISTE_MEDIUM:
db.execute(ADD_MEDIUM_VALUE, (MEDIUM, MEDIUM))
def all_select(db_name="movie_db.db", what_select="genre"):
ALL_SELECT = "SELECT * from " + what_select
if what_select == "genre" or what_select == "medium":
with DBcm.UseDatabase(db_name) as db:
db.execute(ALL_SELECT)
all_value = [i[1] for i in db.fetchall()]
return all_value
elif what_select == 'regie':
all_value = []
with DBcm.UseDatabase(db_name) as db:
db.execute(ALL_SELECT)
for i in db.fetchall():
all_value.append(i[1] + " " + i[2])
return all_value
else:
return "Wrong Value !!!"
def get_or_create_regie(director_name: str, db_name: str = "movie_db.db"):
ADD_REGIE_VALUE = """
INSERT INTO regie (surname, lastname )
SELECT ?, ?
WHERE NOT EXISTS
(SELECT surname, lastname
FROM regie
WHERE surname = ? AND lastname = ?);
"""
director_split = director_name.split(" ")
surname = director_split[0]
lastname = " ".join(director_split[1:])
with DBcm.UseDatabase(db_name) as db:
db.execute(ADD_REGIE_VALUE, (surname, lastname, surname, lastname))
db.execute("SELECT id FROM regie WHERE surname = ? AND lastname = ?", (surname, lastname))
regie_id = db.fetchone()[0]
return regie_id
def get_or_create_genre(genre_name: str, db_name: str = "movie_db.db"):
ADD_GENRE_VALUE = """
INSERT INTO genre(name)
SELECT ?
WHERE NOT EXISTS (SELECT 1 FROM genre WHERE name = ?);
"""
with DBcm.UseDatabase(db_name) as db:
db.execute(ADD_GENRE_VALUE, (genre_name, genre_name))
db.execute("SELECT id FROM genre WHERE name = ?", (genre_name,))
genre_id = db.fetchone()[0]
return genre_id
def search_id(search_name: str, select_from: str = "genre", select_where: str = "name", db_name: str = "movie_db.db"):
if select_from == "regie":
split_search = search_name.split(" ")
GENRE_QUERY = f"""select id from {select_from}
where surname = ? and lastname = ?"""
with DBcm.UseDatabase(db_name) as db:
db.execute(GENRE_QUERY, (split_search[0], split_search[1],))
regie_id = db.fetchone()[0]
return int(regie_id)
else:
try:
GENRE_QUERY = f"""select id from {select_from}
where {select_where} = ?"""
with DBcm.UseDatabase(db_name) as db:
db.execute(GENRE_QUERY, (search_name,))
genre_id = db.fetchone()[0]
return int(genre_id)
except:
return int(0)
def scrape_and_add_movie(movie_name: str, medium_id: int, user_id: int, db_name: str = "movie_db.db"):
url = imdb_suche.suche_film_url(movie_name)
if not url:
return False
details = get_movie_details(url + "/")
if not details:
return False
regie_id = None
if details.get("director"):
regie_id = get_or_create_regie(director_name=details["director"][0])
release_year = details.get("release_year")
genre_name = details.get("genre")
genre_id = None
if genre_name:
genre_id = get_or_create_genre(genre_name=genre_name)
SQL_PARAM = f"""
INSERT INTO movie_list (titel, genre_id, regie_id, medium_id, release_year, user_id)
VALUES (?, ?, ?, ?, ?, ?);
"""
try:
with DBcm.UseDatabase(db_name) as db:
db.execute(SQL_PARAM, (movie_name.lower(), genre_id,
regie_id, medium_id, release_year, user_id,))
except ProgrammingError as e:
print(f"Error adding movie: {e}")
return False
return True
def add_manual_movie(movie_name: str, medium_id: int, director_name: str, release_year: int, genre_name: str, user_id: int, db_name: str = "movie_db.db"):
regie_id = get_or_create_regie(director_name=director_name)
genre_id = get_or_create_genre(genre_name=genre_name)
SQL_PARAM = f"""
INSERT INTO movie_list (titel, genre_id, regie_id, medium_id, release_year, user_id)
VALUES (?, ?, ?, ?, ?, ?);
"""
try:
with DBcm.UseDatabase(db_name) as db:
db.execute(SQL_PARAM, (movie_name.lower(), genre_id,
regie_id, medium_id, release_year, user_id,))
except ProgrammingError as e:
print(f"Error adding movie: {e}")
return False
return True
def show_movie_list(db_name="movie_db.db", user_id=None, search_query=None, page=1, per_page=20):
"""Zeigt eine paginierte Liste von Filmen an, die nach einem Suchbegriff gefiltert werden können."""
# Basis-SQL-Abfrage
SQL_BASE = """FROM movie_list
INNER JOIN genre ON movie_list.genre_id=genre.id
INNER JOIN regie ON movie_list.regie_id=regie.id
INNER JOIN medium ON movie_list.medium_id=medium.id"""
# Filter-Bedingungen
params = []
where_clauses = []
if user_id:
where_clauses.append("movie_list.user_id = ?")
params.append(user_id)
if search_query:
search_param = f"%{search_query}%"
where_clauses.append("(titel LIKE ? OR genre.name LIKE ? OR (regie.surname || ' ' || regie.lastname) LIKE ?)")
params.extend([search_param, search_param, search_param])
SQL_WHERE = ""
if where_clauses:
SQL_WHERE = " WHERE " + " AND ".join(where_clauses)
# Verbindung zur Datenbank
db = sqlite3.connect(db_name)
# Gesamtzahl der Filme für die Paginierung ermitteln
COUNT_SQL = f"SELECT COUNT(movie_list.id) {SQL_BASE}{SQL_WHERE}"
total_movies = db.execute(COUNT_SQL, params).fetchone()[0]
total_pages = (total_movies + per_page - 1) // per_page
# Filme für die aktuelle Seite abrufen
offset = (page - 1) * per_page
SELECT_SQL = f"""SELECT
movie_list.id,
titel,
genre.name AS genre,
regie.surname as regie_surname,
regie.lastname as regie_lastname,
medium.medium,
movie_list.release_year
{SQL_BASE}{SQL_WHERE}
LIMIT ? OFFSET ?"""
paginated_params = params + [per_page, offset]
SELCET_VALUE = pd.read_sql(SELECT_SQL, db, params=paginated_params)
# Ergebnis in eine Liste von Dictionaries umwandeln
return_list_dict = []
for id, titel, genre, regie_surname, regie_lastname, medium, release_year in zip(SELCET_VALUE["id"], SELCET_VALUE["titel"], SELCET_VALUE["genre"], SELCET_VALUE["regie_surname"], SELCET_VALUE["regie_lastname"], SELCET_VALUE["medium"], SELCET_VALUE["release_year"]):
return_list_dict.append(
{"id": id, "titel": titel, "genre": genre, "regie": regie_surname + " " + regie_lastname, "medium": medium, "release_year": release_year})
return return_list_dict, total_pages
def get_movie_by_title(movie_title: str, db_name: str = "movie_db.db"):
SQL_PARAM = f"""SELECT
movie_list.id,
titel,
genre.name AS genre,
regie.surname as regie_surname,
regie.lastname as regie_lastname,
medium.medium,
movie_list.release_year
FROM movie_list
INNER JOIN genre ON movie_list.genre_id=genre.id
INNER JOIN regie ON movie_list.regie_id=regie.id
INNER JOIN medium ON movie_list.medium_id=medium.id
WHERE movie_list.titel = ?
;
"""
with DBcm.UseDatabase(db_name) as db:
db.execute(SQL_PARAM, (movie_title.lower(),))
movie = db.fetchone()
if movie:
return {"id": movie[0], "titel": movie[1], "genre": movie[2], "regie": movie[3] + " " + movie[4], "medium": movie[5], "release_year": movie[6]}
return None
class User(UserMixin):
def __init__(self, id, username, password_hash):
self.id = id
self.username = username
self.password_hash = password_hash
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)
def add_user(username, password, db_name="movie_db.db"):
password_hash = generate_password_hash(password)
SQL = "INSERT INTO user (username, password_hash) VALUES (?, ?)"
with DBcm.UseDatabase(db_name) as db:
db.execute(SQL, (username, password_hash))
def get_user_by_username(username, db_name="movie_db.db"):
SQL = "SELECT * FROM user WHERE username = ?"
with DBcm.UseDatabase(db_name) as db:
db.execute(SQL, (username,))
user_data = db.fetchone()
if user_data:
return User(id=user_data[0], username=user_data[1], password_hash=user_data[2])
return None
def get_user_by_id(user_id, db_name="movie_db.db"):
SQL = "SELECT * FROM user WHERE id = ?"
with DBcm.UseDatabase(db_name) as db:
db.execute(SQL, (user_id,))
user_data = db.fetchone()
if user_data:
return User(id=user_data[0], username=user_data[1], password_hash=user_data[2])
return None
if __name__ == "__main__":
create_movie_database()
# print(all_select())
# id_genre = search_id(
# search_name="DVD", select_from="medium", select_where="medium")
scrape_and_add_movie(movie_name="Die unglaubliche Reise in einem verrückten Flugzeug",
medium_id=1)
for test in show_movie_list():
print(test.get("id"), test.get("medium"), test.get("release_year"))

329
regie_name.csv Normal file
View File

@@ -0,0 +1,329 @@
Name,Vorname
Rodriguez,Robert
Smith,John
Johnson,James
Williams,William
Brown,Michael
Davis,David
Miller,Richard
Wilson,Joseph
Moore,Charles
Taylor,Thomas
Anderson,Daniel
Thomas,Paul
Jackson,Mark
White,Donna
Harris,Michelle
Martin,Laura
Thompson,Sara
Garcia,Ana
Rodriguez,Carlos
Martinez,Maria
Hernandez,Jose
Lopez,Luis
Gonzalez,Rosa
Perez,Pedro
Sanchez,Miguel
Ramirez,Juan
Flores,Ana
Cruz,Isabella
Rivera,Victor
Lee,Kevin
Walker,Brian
Hall,Emily
Allen,Ryan
Young,Aaron
King,Jeffrey
Wright,Joshua
Scott,Brandon
Turner,Frank
Carter,Gregory
Phillips,Samuel
Evans,Chris
Collins,Anthony
Stewart,Eric
Snyder,Frank
Baker,Thomas
Nelson,Jeremy
Roberts,Steven
Campbell,Edward
Miller,Ryan
Davis,Jacob
Garcia,David
Rodriguez,Sophia
Martinez,Emma
Hernandez,Noah
Lopez,Ava
Gonzalez,Ethan
Perez,Mia
Sanchez,William
Ramirez,James
Flores,Olivia
Cruz,Lucas
Rivera,Isabella
Lee,David
Walker,Sophie
Hall,Matthew
Allen,Emma
Young,Ryan
King,Ava
Wright,Ethan
Scott,Mia
Turner,William
Carter,James
Phillips,Olivia
Evans,Lucas
Collins,Sophie
Stewart,Noah
Snyder,Ava
Baker,Ethan
Nelson,Mia
Roberts,Noah
Campbell,Emma
Miller,William
Davis,James
Garcia,Olivia
Rodriguez,Lucas
Martinez,Sophie
Hernandez,Noah
Lopez,Ava
Gonzalez,Ethan
Perez,Mia
Sanchez,William
Ramirez,James
Flores,Olivia
Cruz,Lucas
Rivera,Isabella
Lee,David
Walker,Sophie
Hall,Matthew
Allen,Emma
Young,Ryan
King,Ava
Wright,Ethan
Scott,Mia
Turner,William
Carter,James
Phillips,Olivia
Evans,Lucas
Collins,Sophie
Stewart,Noah
Snyder,Ava
Baker,Ethan
Nelson,Mia
Roberts,Noah
Campbell,Emma
Miller,William
Davis,James
Garcia,Olivia
Rodriguez,Lucas
Martinez,Sophie
Hernandez,Noah
Lopez,Ava
Gonzalez,Ethan
Perez,Mia
Sanchez,William
Ramirez,James
Flores,Olivia
Cruz,Lucas
Rivera,Isabella
Lee,David
Walker,Sophie
Hall,Matthew
Allen,Emma
Young,Ryan
King,Ava
Wright,Ethan
Scott,Mia
Turner,William
Carter,James
Phillips,Olivia
Evans,Lucas
Collins,Sophie
Stewart,Noah
Snyder,Ava
Baker,Ethan
Nelson,Mia
Roberts,Noah
Campbell,Emma
Miller,William
Davis,James
Garcia,Olivia
Rodriguez,Lucas
Martinez,Sophie
Hernandez,Noah
Lopez,Ava
Gonzalez,Ethan
Perez,Mia
Sanchez,William
Ramirez,James
Flores,Olivia
Cruz,Lucas
Rivera,Isabella
Lee,David
Walker,Sophie
Hall,Matthew
Allen,Emma
Young,Ryan
King,Ava
Wright,Ethan
Scott,Mia
Turner,William
Carter,James
Phillips,Olivia
Evans,Lucas
Collins,Sophie
Stewart,Noah
Snyder,Ava
Baker,Ethan
Nelson,Mia
Roberts,Noah
Campbell,Emma
Miller,William
Davis,James
Garcia,Olivia
Rodriguez,Lucas
Martinez,Sophie
Hernandez,Noah
Lopez,Ava
Gonzalez,Ethan
Perez,Mia
Sanchez,William
Ramirez,James
Flores,Olivia
Cruz,Lucas
Rivera,Isabella
Lee,David
Walker,Sophie
Hall,Matthew
Allen,Emma
Young,Ryan
King,Ava
Wright,Ethan
Scott,Mia
Turner,William
Carter,James
Phillips,Olivia
Evans,Lucas
Collins,Sophie
Stewart,Noah
Snyder,Ava
Baker,Ethan
Nelson,Mia
Roberts,Noah
Campbell,Emma
Miller,William
Davis,James
Garcia,Olivia
Rodriguez,Lucas
Martinez,Sophie
Hernandez,Noah
Lopez,Ava
Gonzalez,Ethan
Perez,Mia
Sanchez,William
Ramirez,James
Flores,Olivia
Cruz,Lucas
Rivera,Isabella
Lee,David
Walker,Sophie
Hall,Matthew
Allen,Emma
Young,Ryan
King,Ava
Wright,Ethan
Scott,Mia
Turner,William
Carter,James
Phillips,Olivia
Evans,Lucas
Collins,Sophie
Stewart,Noah
Snyder,Ava
Baker,Ethan
Nelson,Mia
Roberts,Noah
Campbell,Emma
Miller,William
Davis,James
Garcia,Olivia
Rodriguez,Lucas
Martinez,Sophie
Hernandez,Noah
Lopez,Ava
Gonzalez,Ethan
Perez,Mia
Sanchez,William
Ramirez,James
Flores,Olivia
Cruz,Lucas
Rivera,Isabella
Lee,David
Walker,Sophie
Hall,Matthew
Allen,Emma
Young,Ryan
King,Ava
Wright,Ethan
Scott,Mia
Turner,William
Carter,James
Phillips,Olivia
Evans,Lucas
Collins,Sophie
Stewart,Noah
Snyder,Ava
Baker,Ethan
Nelson,Mia
Roberts,Noah
Campbell,Emma
Miller,William
Davis,James
Garcia,Olivia
Rodriguez,Lucas
Martinez,Sophie
Hernandez,Noah
Lopez,Ava
Gonzalez,Ethan
Perez,Mia
Sanchez,William
Ramirez,James
Flores,Olivia
Cruz,Lucas
Rivera,Isabella
Lee,David
Walker,Sophie
Hall,Matthew
Allen,Emma
Young,Ryan
King,Ava
Wright,Ethan
Scott,Mia
Turner,William
Carter,James
Phillips,Olivia
Evans,Lucas
Collins,Sophie
Stewart,Noah
Snyder,Ava
Baker,Ethan
Nelson,Mia
Roberts,Noah
Campbell,Emma
Miller,William
Davis,James
Garcia,Olivia
Rodriguez,Lucas
Martinez,Sophie
Hernandez,Noah
Lopez,Ava
Gonzalez,Ethan
Perez,Mia
Sanchez,William
Ramirez,James
Flores,Olivia
Cruz,Lucas
Rivera,Isabella
Lee,David
Walker,Sophie
1 Name Vorname
2 Rodriguez Robert
3 Smith John
4 Johnson James
5 Williams William
6 Brown Michael
7 Davis David
8 Miller Richard
9 Wilson Joseph
10 Moore Charles
11 Taylor Thomas
12 Anderson Daniel
13 Thomas Paul
14 Jackson Mark
15 White Donna
16 Harris Michelle
17 Martin Laura
18 Thompson Sara
19 Garcia Ana
20 Rodriguez Carlos
21 Martinez Maria
22 Hernandez Jose
23 Lopez Luis
24 Gonzalez Rosa
25 Perez Pedro
26 Sanchez Miguel
27 Ramirez Juan
28 Flores Ana
29 Cruz Isabella
30 Rivera Victor
31 Lee Kevin
32 Walker Brian
33 Hall Emily
34 Allen Ryan
35 Young Aaron
36 King Jeffrey
37 Wright Joshua
38 Scott Brandon
39 Turner Frank
40 Carter Gregory
41 Phillips Samuel
42 Evans Chris
43 Collins Anthony
44 Stewart Eric
45 Snyder Frank
46 Baker Thomas
47 Nelson Jeremy
48 Roberts Steven
49 Campbell Edward
50 Miller Ryan
51 Davis Jacob
52 Garcia David
53 Rodriguez Sophia
54 Martinez Emma
55 Hernandez Noah
56 Lopez Ava
57 Gonzalez Ethan
58 Perez Mia
59 Sanchez William
60 Ramirez James
61 Flores Olivia
62 Cruz Lucas
63 Rivera Isabella
64 Lee David
65 Walker Sophie
66 Hall Matthew
67 Allen Emma
68 Young Ryan
69 King Ava
70 Wright Ethan
71 Scott Mia
72 Turner William
73 Carter James
74 Phillips Olivia
75 Evans Lucas
76 Collins Sophie
77 Stewart Noah
78 Snyder Ava
79 Baker Ethan
80 Nelson Mia
81 Roberts Noah
82 Campbell Emma
83 Miller William
84 Davis James
85 Garcia Olivia
86 Rodriguez Lucas
87 Martinez Sophie
88 Hernandez Noah
89 Lopez Ava
90 Gonzalez Ethan
91 Perez Mia
92 Sanchez William
93 Ramirez James
94 Flores Olivia
95 Cruz Lucas
96 Rivera Isabella
97 Lee David
98 Walker Sophie
99 Hall Matthew
100 Allen Emma
101 Young Ryan
102 King Ava
103 Wright Ethan
104 Scott Mia
105 Turner William
106 Carter James
107 Phillips Olivia
108 Evans Lucas
109 Collins Sophie
110 Stewart Noah
111 Snyder Ava
112 Baker Ethan
113 Nelson Mia
114 Roberts Noah
115 Campbell Emma
116 Miller William
117 Davis James
118 Garcia Olivia
119 Rodriguez Lucas
120 Martinez Sophie
121 Hernandez Noah
122 Lopez Ava
123 Gonzalez Ethan
124 Perez Mia
125 Sanchez William
126 Ramirez James
127 Flores Olivia
128 Cruz Lucas
129 Rivera Isabella
130 Lee David
131 Walker Sophie
132 Hall Matthew
133 Allen Emma
134 Young Ryan
135 King Ava
136 Wright Ethan
137 Scott Mia
138 Turner William
139 Carter James
140 Phillips Olivia
141 Evans Lucas
142 Collins Sophie
143 Stewart Noah
144 Snyder Ava
145 Baker Ethan
146 Nelson Mia
147 Roberts Noah
148 Campbell Emma
149 Miller William
150 Davis James
151 Garcia Olivia
152 Rodriguez Lucas
153 Martinez Sophie
154 Hernandez Noah
155 Lopez Ava
156 Gonzalez Ethan
157 Perez Mia
158 Sanchez William
159 Ramirez James
160 Flores Olivia
161 Cruz Lucas
162 Rivera Isabella
163 Lee David
164 Walker Sophie
165 Hall Matthew
166 Allen Emma
167 Young Ryan
168 King Ava
169 Wright Ethan
170 Scott Mia
171 Turner William
172 Carter James
173 Phillips Olivia
174 Evans Lucas
175 Collins Sophie
176 Stewart Noah
177 Snyder Ava
178 Baker Ethan
179 Nelson Mia
180 Roberts Noah
181 Campbell Emma
182 Miller William
183 Davis James
184 Garcia Olivia
185 Rodriguez Lucas
186 Martinez Sophie
187 Hernandez Noah
188 Lopez Ava
189 Gonzalez Ethan
190 Perez Mia
191 Sanchez William
192 Ramirez James
193 Flores Olivia
194 Cruz Lucas
195 Rivera Isabella
196 Lee David
197 Walker Sophie
198 Hall Matthew
199 Allen Emma
200 Young Ryan
201 King Ava
202 Wright Ethan
203 Scott Mia
204 Turner William
205 Carter James
206 Phillips Olivia
207 Evans Lucas
208 Collins Sophie
209 Stewart Noah
210 Snyder Ava
211 Baker Ethan
212 Nelson Mia
213 Roberts Noah
214 Campbell Emma
215 Miller William
216 Davis James
217 Garcia Olivia
218 Rodriguez Lucas
219 Martinez Sophie
220 Hernandez Noah
221 Lopez Ava
222 Gonzalez Ethan
223 Perez Mia
224 Sanchez William
225 Ramirez James
226 Flores Olivia
227 Cruz Lucas
228 Rivera Isabella
229 Lee David
230 Walker Sophie
231 Hall Matthew
232 Allen Emma
233 Young Ryan
234 King Ava
235 Wright Ethan
236 Scott Mia
237 Turner William
238 Carter James
239 Phillips Olivia
240 Evans Lucas
241 Collins Sophie
242 Stewart Noah
243 Snyder Ava
244 Baker Ethan
245 Nelson Mia
246 Roberts Noah
247 Campbell Emma
248 Miller William
249 Davis James
250 Garcia Olivia
251 Rodriguez Lucas
252 Martinez Sophie
253 Hernandez Noah
254 Lopez Ava
255 Gonzalez Ethan
256 Perez Mia
257 Sanchez William
258 Ramirez James
259 Flores Olivia
260 Cruz Lucas
261 Rivera Isabella
262 Lee David
263 Walker Sophie
264 Hall Matthew
265 Allen Emma
266 Young Ryan
267 King Ava
268 Wright Ethan
269 Scott Mia
270 Turner William
271 Carter James
272 Phillips Olivia
273 Evans Lucas
274 Collins Sophie
275 Stewart Noah
276 Snyder Ava
277 Baker Ethan
278 Nelson Mia
279 Roberts Noah
280 Campbell Emma
281 Miller William
282 Davis James
283 Garcia Olivia
284 Rodriguez Lucas
285 Martinez Sophie
286 Hernandez Noah
287 Lopez Ava
288 Gonzalez Ethan
289 Perez Mia
290 Sanchez William
291 Ramirez James
292 Flores Olivia
293 Cruz Lucas
294 Rivera Isabella
295 Lee David
296 Walker Sophie
297 Hall Matthew
298 Allen Emma
299 Young Ryan
300 King Ava
301 Wright Ethan
302 Scott Mia
303 Turner William
304 Carter James
305 Phillips Olivia
306 Evans Lucas
307 Collins Sophie
308 Stewart Noah
309 Snyder Ava
310 Baker Ethan
311 Nelson Mia
312 Roberts Noah
313 Campbell Emma
314 Miller William
315 Davis James
316 Garcia Olivia
317 Rodriguez Lucas
318 Martinez Sophie
319 Hernandez Noah
320 Lopez Ava
321 Gonzalez Ethan
322 Perez Mia
323 Sanchez William
324 Ramirez James
325 Flores Olivia
326 Cruz Lucas
327 Rivera Isabella
328 Lee David
329 Walker Sophie

6
requirements.txt Normal file
View File

@@ -0,0 +1,6 @@
flask
dbcm
pandas
requests
beautifulsoup4
Flask-Login

28
start.yml Normal file
View File

@@ -0,0 +1,28 @@
---
- name: Deploy and start Movie-DB Application
hosts: localhost
connection: local
become: yes
vars:
project_name: movie-db
install_dir: "/opt/{{ project_name }}"
project_src: "{{ playbook_dir }}"
tasks:
- name: Create installation directory
ansible.builtin.file:
path: "{{ install_dir }}"
state: directory
mode: '0755'
- name: Copy docker-compose file
ansible.builtin.copy:
src: "{{ project_src }}/docker-compose.yml"
dest: "{{ install_dir }}/docker-compose.yml"
- name: Start application with Docker Compose
community.docker.docker_compose:
project_src: "{{ install_dir }}"
state: present
restarted: yes

20
templates/add_movie.html Normal file
View File

@@ -0,0 +1,20 @@
{% extends "base.html" %}
{% block body %}
<form action="{{ url }}" method="POST" class="mb-3">
<div class="mb-3">
<input type="text"
class="form-control"
placeholder="Movie"
name="{{ select_movie }}"
id="{{ select_movie }}">
</div>
<div class="mb-3">
<select name="{{ select_medium }}" id="{{ select_medium }}" class="form-select">
{% for name in data_medium %}
<option value="{{ name }}">{{ name }}</option>
{% endfor %}
</select>
</div>
<button type="submit" class="btn btn-primary">Eingabe</button>
</form>
{% endblock %}

View File

@@ -0,0 +1,22 @@
{% extends "base.html" %}
{% block body %}
<h3 class="mb-3">Film nicht gefunden. Bitte manuell eingeben:</h3>
<form action="/save_manual" method="POST">
<input type="hidden" name="movie_name" value="{{ movie_name }}">
<input type="hidden" name="medium_id" value="{{ medium_id }}">
<div class="mb-3">
<label for="genre_name" class="form-label">Genre:</label>
<input type="text" class="form-control" id="genre_name" name="genre_name" required>
</div>
<div class="mb-3">
<label for="director_name" class="form-label">Regisseur:</label>
<input type="text" class="form-control" id="director_name" name="director_name" required>
</div>
<div class="mb-3">
<label for="release_year" class="form-label">Erscheinungsjahr:</label>
<input type="number" class="form-control" id="release_year" name="release_year" required>
</div>
<button type="submit" class="btn btn-primary">Speichern</button>
</form>
{% endblock %}

36
templates/base.html Normal file
View File

@@ -0,0 +1,36 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ sitename }}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container-fluid">
<a class="navbar-brand" href="{{ url_for('index') }}">{{ sitename }}</a>
<div class="collapse navbar-collapse">
<ul class="navbar-nav ms-auto">
{% if current_user.is_authenticated %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('logout') }}">Logout</a>
</li>
{% else %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('login') }}">Login</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('register') }}">Registrieren</a>
</li>
{% endif %}
</ul>
</div>
</div>
</nav>
<div class="container mt-4">
{% block body %}{% endblock %}
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
</body>
</html>

60
templates/index.html Normal file
View File

@@ -0,0 +1,60 @@
{% extends "base.html" %}
{% block body %}
<a href="/add_movie" class="btn btn-primary mb-3">Film hinzufügen</a>
<form method="post" action="{{ url_for('index') }}">
<div class="row mb-3">
<div class="col">
<label for="search" class="form-label">Suche</label>
<input type="text" name="search" id="search" class="form-control" value="{{ search_query }}">
</div>
</div>
<button type="submit" class="btn btn-primary">Suchen</button>
</form>
<table class="table table-striped table-bordered mt-3">
<thead class="table-dark">
<tr>
<th>Id</th>
<th>Name</th>
<th>Genre</th>
<th>Regie</th>
<th>Medium</th>
<th>Erscheinungsjahr</th>
</tr>
</thead>
<tbody>
{% for list_all in data_all %}
<tr>
<td>{{list_all.get("id")}}</td>
<td>{{list_all.get("titel")}}</td>
<td>{{list_all.get("genre")}}</td>
<td>{{list_all.get("regie")}}</td>
<td>{{list_all.get("medium")}}</td>
<td>{{list_all.get("release_year")}}</td>
</tr>
{% endfor %}
</tbody>
</table>
<nav aria-label="Page navigation">
<ul class="pagination">
<li class="page-item {% if current_page == 1 %}disabled{% endif %}">
<a class="page-link" href="{{ url_for('index', page=1, search=search_query) }}">Erste</a>
</li>
<li class="page-item {% if current_page == 1 %}disabled{% endif %}">
<a class="page-link" href="{{ url_for('index', page=current_page - 1, search=search_query) }}">Zurück</a>
</li>
<li class="page-item disabled">
<a class="page-link" href="#">Seite {{ current_page }} von {{ total_pages }}</a>
</li>
<li class="page-item {% if current_page == total_pages %}disabled{% endif %}">
<a class="page-link" href="{{ url_for('index', page=current_page + 1, search=search_query) }}">Weiter</a>
</li>
<li class="page-item {% if current_page == total_pages %}disabled{% endif %}">
<a class="page-link" href="{{ url_for('index', page=total_pages, search=search_query) }}">Letzte</a>
</li>
</ul>
</nav>
{% endblock %}

25
templates/login.html Normal file
View File

@@ -0,0 +1,25 @@
{% extends "base.html" %}
{% block body %}
<div class="container">
<h2>Login</h2>
{% with messages = get_flashed_messages() %}
{% if messages %}
<div class="alert alert-danger">
{{ messages[0] }}
</div>
{% endif %}
{% endwith %}
<form method="post">
<div class="mb-3">
<label for="username" class="form-label">Benutzername</label>
<input type="text" class="form-control" id="username" name="username" required>
</div>
<div class="mb-3">
<label for="password" class="form-label">Passwort</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<button type="submit" class="btn btn-primary">Login</button>
</form>
<p class="mt-3">Noch keinen Account? <a href="{{ url_for('register') }}">Hier registrieren</a>.</p>
</div>
{% endblock %}

18
templates/register.html Normal file
View File

@@ -0,0 +1,18 @@
{% extends "base.html" %}
{% block body %}
<div class="container">
<h2>Registrieren</h2>
<form method="post">
<div class="mb-3">
<label for="username" class="form-label">Benutzername</label>
<input type="text" class="form-control" id="username" name="username" required>
</div>
<div class="mb-3">
<label for="password" class="form-label">Passwort</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<button type="submit" class="btn btn-primary">Registrieren</button>
</form>
<p class="mt-3">Schon einen Account? <a href="{{ url_for('login') }}">Hier einloggen</a>.</p>
</div>
{% endblock %}