This commit is contained in:
34
.gitea/workflows/deploy.yml
Normal file
34
.gitea/workflows/deploy.yml
Normal 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
19
Dockerfile
Normal 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
118
README.md
Normal 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
12
ansible.cfg
Normal 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
29
cert.pem
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
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
|
||||||
|
-----END CERTIFICATE-----
|
||||||
48
deploy_remote.yml
Normal file
48
deploy_remote.yml
Normal 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
10
docker-compose.yml
Normal 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
36
genre_list
Normal 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
59
imdb_suche.py
Normal 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
8
inv/hosts
Normal 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
52
key.pem
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQDQnU82qyuD4Ev/
|
||||||
|
PLudNpHB8/3qAYa+dC0cUcGJ9fuoXp5tcq/FTvu0YT0PSLm3fo3aS0yXo8YjYKaE
|
||||||
|
XPmaRxWted9YYuhhXaMuVhA50q/AUmjtHhC4Zrycsct0ZSSFJKBHGSiX2sdRyiTl
|
||||||
|
ml8DWJUVuumCp6XcuaHSHepfHtiI9OhjxmEVAOgXhNclgatILYGUC1s5LBP8v+f3
|
||||||
|
zdUgDd26DND7cfvw5aZ8ZwIHxDx3mE0U/6KBaP/b1Te8Ka+WBenNoz5fVO0v27Gq
|
||||||
|
b8ZbD/6MiPhaf7abGEwVLYH855YRbnKhNi9nLAr95qn2J7IqnOL6U0Pd5O8P6Jym
|
||||||
|
PWEUG3mpr5H0/VuPfr2rogxLE2t/lglwN6zLDLkwggA837bWc1iLXdlLBKd8Qj/X
|
||||||
|
eSUGTSt2EG6UMA8a8fUNkQBzh49qAxC6Em1brCXnTI0kAPGzMBrWZsgiT+/uv68+
|
||||||
|
3fZdPKWqVojeShTZeOObJdywAUt2BxLKYK3E1Z7ftTnypvUndAoNtqySrqQmIkFZ
|
||||||
|
mUBJrcA2nX1R1a8Ce7OeGF9fCzRxXpuoWh4oqTJY6w+Wf4h+7DVU40k4wmahPgv6
|
||||||
|
Qe4qcwOp26y4Z0iUld95bTS8wshHHw7yPx1Qd8WpZGcT3xU71ZmWxtqBiBb3qf3P
|
||||||
|
J5m99bdWCdyuoxVlgLZhp1tIf6EbjQIDAQABAoICAD8OHWBX0ppU1IO8drTpRptK
|
||||||
|
RYhSl8i/UtdJeudZW4yjtN0+GhTYb8owzgTtpXzqGrRNqEP5c61DzHEbrTJNpCbA
|
||||||
|
PJr5omWzqNwblWH05byK7D4npUQngYEXWHjBDY3U6UE0QInNzgtRv19LDsbzInTj
|
||||||
|
/yrHw6F41omtEydXKnoTFJ772mI/2Ocq9uDWVdBGOPFnkVYxlYUC+Hl1VKMQreFL
|
||||||
|
eGtYI5/Huk/Fzt5MGT7I4RukvRjChPuMcgFE1FbTSL0oR3UHNX4OuJ/ckyb5KaaO
|
||||||
|
zAqGqcotSOnpzG2GDl0Mdkd2GlPkAxqFev/1NawdqZmiS1Qpm3zCgX7Z9JrgRyBP
|
||||||
|
nMKMdH/+Yi6sQ16BLuq1/wBSoN4hh5AIjbS2SRV8P0RR9TfuKP0G3UZeXEJVu7VZ
|
||||||
|
LjMqfNc5Frco8iJdm4dv9bAjMgDgKBwRzECTSPcUByS7H72v7I81X9HDaNlMk9Py
|
||||||
|
B0sy5mduihQE4ZeSUVloiq/Mm8K3/6lPuC94+8hfZ7ZhmMTsXkeHtyo+tNNEHtFS
|
||||||
|
wnF96zzUE5l7quCmqPzz5l5ll9wYAjMXr48Fv9C5E5QshoYhmzBrF6+Kn315bSa6
|
||||||
|
PHf3v2nJ//lJHyJR6o/MWLAdW61nbvnlLbwBUt0blv+PUnoOFKQhDcHteU7Ht5zi
|
||||||
|
vyzJKEGK0nL+hScit94dAoIBAQD+ttlWLHVo32jX83cVLNQFo5TVQJlbf05yZUqm
|
||||||
|
rEVu+kH59JAUPYxypiQ+CWc3OMFqDHiSrP9VdL1HSfZURS3K8hw25ThWn5jXEoWF
|
||||||
|
SkyCWBcPp8EyCwW14DUCp45VKyZlNpM/halT+eG04lElEilUHDMbinz01R2Gwkrk
|
||||||
|
zgirxRBpAU1OMnvCTTjwmLUXCKvHBDek4AZBY0Gw9eWgir+If6Gyn88Nr2N1ZtFt
|
||||||
|
+kkHrLgyo+nanOu1N/02RC3Je3lSxI7VsLP0Lt/zs5Qag8r/a7lEDUI69eQbh/Rh
|
||||||
|
29NaqUyPOQn8DjfPzB8a0F7qXbcB94CYWfoKZ/RkdJ9dj4mTAoIBAQDRquN/ftwE
|
||||||
|
CBfgwE5BJNZuofj733LBrnvZH51UOMvnoTxPDdCyf684un8qylmYu4WIV5VvWZUH
|
||||||
|
G3gOw+zmmpEOHerh8/G7X9FPST8txTlAxlFBVN0iXMyU8SDypw2XF+CKcq0goz2s
|
||||||
|
/XA0b9NdtF9zziYsZSj+hR5E1Nf16GX2SeczSlaxPRTZ1pxqRbmC+1iE+yJuXcQ2
|
||||||
|
sJgr2zI9jUf0zGamYPqpKvZDKLclZSJAIYTLE/nUMR87QqgLU90x3bBeRR4uLnD3
|
||||||
|
yemtFuJDRcSZa+4j/m5fmLZvA2wpvZvvd70DC3xfh6RKZYyzj26rHgK3qnY9owzS
|
||||||
|
q44qLTbNH3pfAoIBAQCHmjy2XdDb1iRw41LAWS8/GUlBBrUeOvY+fKJzfT8xx9Wc
|
||||||
|
BpE11VaSKtUcmdDzWynNFXqTS7pbelzSORQE5Rdt9dsDdqC5rRmnVlrzDQ0ST2dn
|
||||||
|
lD2MMMYHf6d5FE74FJLKS3W02am95Ug+DkZ43+RUvPaQcPf5OAQSHBnXGcEQAUet
|
||||||
|
P14yhTnRxV7M599wtdMNjrc49XMfGIJ+fUqUX4l8V8Zz3BRVCsx+UhCo5hg/F7qw
|
||||||
|
awcytVvfDEGnxEBEBZIfeDk6lmoXU2UyzqXIP+WweV8WVaCu1v1rksON3YP1vi/e
|
||||||
|
B9XC9KbN37OniMsfJmkpiORTN4OgSpuXZ2b4XlQ1AoIBAQCsVFMMdc6MmcJWfnlT
|
||||||
|
Xd3JrOOi0fPWcX1l5nA/yzb9OS8Cow5HyF7St6bSGuMtf+OvPp2amfy9jLxPFoZe
|
||||||
|
u+hBvCFUelbTdVvqWP/OtO3r6ZTbp87h8XhNaytzhg7Lg1qRMNRPoRyOVPimgMsv
|
||||||
|
l5Nk8am6j6L0H6HTXUXr3on/tpqLM6yxnMzIh3akeo6Rs9j75BZWtEcT1G4ejdlr
|
||||||
|
cfYUpPQyCD++T4t9g7eUyt0t7N7is5aB66YJ5S2Nse56kAXoVdMKji1X3ONt2wM5
|
||||||
|
29xNZcKkXvmFJVW9RQjf/fJvDvGR0Rz2v9wvMexFobyKdO4y26631o4xkcQVsnf6
|
||||||
|
fMbtAoIBAE6rOneLYxM8X6hJ7NoyVIwUH52WKuqWn4SFHU42C8SGeUTxFtr6ze+L
|
||||||
|
z0ZYTowYufMOUMjtS4nwa2kWkohPJccDfJHG1NJf6WA80Yt/qQItsjv/TGItxDE0
|
||||||
|
4j47r+h1NGj01xzJHe0QG38NWnnpw0hHFSG5q73qWFyYSWs/unz+HMSGsg+KtpPT
|
||||||
|
K7RJMx2ROUXlMku8TmEWO0EfwhVmTp2g/1XoH7DvtAIv5GLiUL+pYp9qs7LtSVI/
|
||||||
|
3WYsk3Op/x5VRG/V2hByPf25IrpLS3eoDB3uPeeIHb0rXx0QdMr2rElGg4vgcLUw
|
||||||
|
krvHLxyb9R+o1UzlvYkUiF+lOMVpLB8=
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
127
main.py
Normal file
127
main.py
Normal 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
427
moviedb_func.py
Normal 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
329
regie_name.csv
Normal 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
|
||||||
|
6
requirements.txt
Normal file
6
requirements.txt
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
flask
|
||||||
|
dbcm
|
||||||
|
pandas
|
||||||
|
requests
|
||||||
|
beautifulsoup4
|
||||||
|
Flask-Login
|
||||||
28
start.yml
Normal file
28
start.yml
Normal 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
20
templates/add_movie.html
Normal 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 %}
|
||||||
22
templates/add_movie_manually.html
Normal file
22
templates/add_movie_manually.html
Normal 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
36
templates/base.html
Normal 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
60
templates/index.html
Normal 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
25
templates/login.html
Normal 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
18
templates/register.html
Normal 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 %}
|
||||||
Reference in New Issue
Block a user