Anfänger braucht Hilfe bei App

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Alice1
User
Beiträge: 6
Registriert: Montag 29. Mai 2023, 10:57

Hallo Zusammen,

ich fange gerade erst an mit Python und stehe jetzt vor einem Problem da ich seid drei Tagen nicht lösen kann ich hoffe mir kann hier jemand weiterhelfen.

ich schreibe in Pycharm einen code der in Streamlit ausgeführt werden soll.
Es handelt sich um eine App für Rezepte.
Ich habe einem die Auswahl Rezept Eigene Rezeptauswahl und Zufallsauswahl das Problem besteht in der Zufallsauswahl ich habe 7 Zufällige Rezepte unter jedem ist ein Button mit Rezept Anzeigen da wird , dann aus der .txt Datei das Rezept angezeigt und ein Button mit Rezept Wechseln beim betätigen darf sich nur das dazugehörige Rezept ändern die anderen 6 müssen gleich bleiben und das bekomme ich nicht hin. Kann mir da jemand helfen?
das ist der Bisherige Code:

import os
import random
import streamlit as st

# Logo in der Seitenleiste anzeigen
st.sidebar.image("Rezepte/Tasty and Quick.jpd.png", width=200)

st.title("Willkommen bei Tasty and Quick")
st.markdown("___")

# Seitenleiste für die Auswahl der Funktion
auswahl = st.sidebar.radio("Was möchtest du diese Woche kochen?", ("Eigene Rezeptauswahl", "Zufallsauswahl"))

# Pfad zum Ordner mit den Rezepten
REZEPTE_ORDNER = "Rezepte"

# Liste zum Speichern der ausgewählten Rezepte
ausgewaehlte_rezepte = []

# Funktion zum Laden der Rezepte aus dem Ordner
def lade_rezepte():
rezepte = []
for datei in os.listdir(REZEPTE_ORDNER):
if datei.endswith('.txt'):
rezeptname = get_rezeptname(datei)
bildpfad = os.path.join(REZEPTE_ORDNER, f'#{rezeptname}.jpg')
rezept_details = lade_rezept_details(rezeptname)
rezepte.append({'name': rezeptname, 'bildpfad': bildpfad, 'details': rezept_details})
return rezepte

# Funktion zum Laden der Rezeptdetails aus der .txt-Datei
def lade_rezept_details(rezeptname):
rezeptdatei = os.path.join(REZEPTE_ORDNER, f"#{rezeptname}.txt")
with open(rezeptdatei, "r", encoding="utf-8") as file:
rezept_details = file.read()
return rezept_details

# Funktion zum Anzeigen der ausgewählten Rezepte
def zeige_ausgewaehlte_rezepte(rezepte):
for rezept in rezepte:
st.write(rezept['name'])
st.image(rezept['bildpfad'], caption="", width=150)
if st.button(f"Rezept anzeigen: {rezept['name']}"):
st.write(rezept['details'])

# Funktion zum Zufälligen Auswählen von Rezepten
def zufaellige_auswahl(rezepte):
random.shuffle(rezepte)
return rezepte[:7] # Geändert auf zwei Rezepte

# Funktion zum Erstellen des Rezeptplans
def erstelle_rezeptplan(rezepte):
st.write('Rezeptplan für eine Woche')
for index, rezept in enumerate(rezepte, start=1):
st.write(f'Tag {index}')
st.write(rezept['name'])
st.image(rezept['bildpfad'], caption="", width=150)
st.write('---')

# Funktion für die Auswahl eigener Rezepte
def eigene_rezeptauswahl(rezepte):
st.write('Wähle 7 Rezepte aus')
for rezept in rezepte:
if st.checkbox(rezept['name']):
ausgewaehlte_rezepte.append(rezept)
if len(ausgewaehlte_rezepte) == 7:
break

# Funktion zum Extrahieren des Rezeptnamens aus dem Dateinamen
def get_rezeptname(file_name):
return file_name.lstrip("#").replace(".txt", "")

# Hauptprogramm
def main():
st.title('Rezeptplaner')

rezepte = lade_rezepte()

if auswahl == "Eigene Rezeptauswahl":
eigene_rezeptauswahl(rezepte)
if len(ausgewaehlte_rezepte) == 7:
erstelle_rezeptplan(ausgewaehlte_rezepte)
elif auswahl == "Zufallsauswahl":
rezeptplan = zufaellige_auswahl(rezepte)
if len(rezeptplan) > 0:
st.write('Rezepte für diese Woche:')
for rezept in rezeptplan:
st.write(rezept['name'])
st.image(rezept['bildpfad'], caption="", width=150)
if st.button(f"Rezept anzeigen: {rezept['name']}"):
st.write(rezept['details'])
if st.button(f"Rezept wechseln: {rezept['name']}"):
rezeptplan.remove(rezept)
if len(rezeptplan) > 0:
st.write('Neues Rezept:')
st.write(rezeptplan[0]['name'])
st.image(rezeptplan[0]['bildpfad'], caption="", width=150)
if st.button(f"Rezept anzeigen: {rezeptplan[0]['name']}"):
st.write(rezeptplan[0]['details'])
if st.button(f"Rezept wechseln: {rezeptplan[0]['name']}"):
rezeptplan = rezeptplan[1:]
erstelle_rezeptplan(rezeptplan)

if len(ausgewaehlte_rezepte) > 0:
st.write('Ausgewählte Rezepte')
zeige_ausgewaehlte_rezepte(ausgewaehlte_rezepte)

if __name__ == '__main__':
main()

Gruß Alice :)
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Du hast bereits eine main-Funktion, dann sollte da auch der Code für die sidebar stehen. `title` wird zudem zweimal gesetzt.

Für das Arbeiten mit Dateinamen benutzt man heutzutage pathlib.Path. Du setzt an vielen Stellen den Dateinamen für das Rezept neu zusammen, das sollte nicht sein.

In `zufaellige_auswahl` übergibst Du eine Liste und veränderst diese, das ist für den Anwender der Funktion überraschend. Besser Du nimmst random.sample.

`eigene_rezeptauswahl` benutzt eine globale Variable statt eines Rückgabewerts.

Der Code zum Anzeigen eines Rezepts gibt es vier mal im Code, das kann in eine Funktion wandern.
Bei "Rezept Wechseln" wird immer nur ein Rezept gelöscht, aber kein neues hinzugefügt; und dann zeigst Du immer das erste Rezept wieder an, das macht irgendwie keinen Sinn. Wie hast Du Dir das Wechseln eigentlich gedacht?

Man darf keine Liste ändern, wärend man über sie iteriert. Am besten erzeugt man immer eine neue Liste, statt eine bestehende zu ändern, hier brauchst Du eh eine Liste `ausgewaehlte_rezepte`.

Code: Alles auswählen

import random
from pathlib import Path
import streamlit as st

REZEPTE_ORDNER = Path("Rezepte")

def get_rezeptname(rezeptdatei):
    return rezeptdatei.stem.lstrip("#")

def lade_rezept_details(rezeptdatei):
    return rezeptdatei.read_text(encoding="utf-8")

def lade_rezepte():
    rezepte = []
    for datei in REZEPTE_ORDNER.glob('*.txt'):
        rezeptname = get_rezeptname(datei)
        bildpfad = datei.with_suffix('.jpg')
        rezept_details = lade_rezept_details(datei)
        rezepte.append({
            'name': rezeptname,
            'bildpfad': bildpfad,
            'details': rezept_details
        })
    return rezepte

def zufaellige_auswahl(rezepte):
    return random.sample(rezepte, 7)

def eigene_rezeptauswahl(rezepte):
    st.write('Wähle 7 Rezepte aus')
    ausgewaehlte_rezepte = []
    for rezept in rezepte:
        if st.checkbox(rezept['name']):
            ausgewaehlte_rezepte.append(rezept)
            if len(ausgewaehlte_rezepte) == 7:
                break
    return ausgewaehlte_rezepte

def rezept_anzeigen(rezept, details=True):
    st.write(rezept['name'])
    st.image(rezept['bildpfad'], caption="", width=150)
    if details and st.button(f"Rezept anzeigen: {rezept['name']}"):
        st.write(rezept['details'])

def zeige_ausgewaehlte_rezepte(rezepte):
    for rezept in rezepte:
        rezept_anzeigen(rezept)

def erstelle_rezeptplan(rezepte):
    st.write('Rezeptplan für eine Woche')
    for index, rezept in enumerate(rezepte, start=1):
        st.write(f'Tag {index}')
        rezept_anzeigen(rezept, details=False)
        st.write('---')

def main():
    st.sidebar.image(REZEPTE_ORDNER / "Tasty and Quick.jpd.png", width=200)

    st.title("Willkommen bei Tasty and Quick")
    st.markdown("___")

    rezepte = lade_rezepte()

    auswahl = st.sidebar.radio("Was möchtest du diese Woche kochen?",
        ("Eigene Rezeptauswahl", "Zufallsauswahl"))
    if auswahl == "Eigene Rezeptauswahl":
        ausgewaehlte_rezepte = eigene_rezeptauswahl(rezepte)
        if len(ausgewaehlte_rezepte) == 7:
            erstelle_rezeptplan(ausgewaehlte_rezepte)
    elif auswahl == "Zufallsauswahl":
        ausgewaehlte_rezepte = []
        rezeptplan = zufaellige_auswahl(rezepte)
        if len(rezeptplan) > 0:
            st.write('Rezepte für diese Woche:')
            for rezept in rezeptplan:
                rezept_anzeigen(rezept)
                if not st.button(f"Rezept wechseln: {rezept['name']}"):
                    ausgewaehlte_rezepte.append(rezept)
    else:
        assert False, "sollte nicht passieren"

    if len(ausgewaehlte_rezepte) > 0:
        st.write('Ausgewählte Rezepte')
        zeige_ausgewaehlte_rezepte(ausgewaehlte_rezepte)

if __name__ == '__main__':
    main()
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

(Überschneidet sich etwas mit der Antwort von Sirius3, ich bin aber zu faul das noch mal zu überarbeiten. Doppeltes als einfach ignorieren.)

@Alice1: Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Da gehört also noch einiges vom Anfang des Quelltextes in die `main()`-Funktion. Insbesondere auch `ausgewaehlte_rezepte`. Funktionen die diese Liste benötigen sollten da nicht einfach ”magisch” drauf zugreifen, sondern die als Argument übergeben bekommen.

Kommentare sollen dem Leser einen Mehrwert über den Code geben. Faustregel: Kommentare beschreiben nicht *was* der Code macht, denn das steht da bereits als Code, sondern warum er das macht. Sofern das nicht offensichtlich ist. Offensichtlich ist in aller Regel auch was in der Dokumentation von Python und den verwendeten Bibliotheken steht.

Zu einer Funktionsdefinition muss man nicht kommentieren dass das eine Funktion ist. Und auch was der Funktionsname offensichtlich schon aussagt muss man nicht noch mal in einen Kommentar schreiben. Funktionen kommentiert man auch nicht auf diese Weise sondern schreibt einen Docstring. Aber auch nur wenn da wirklich Substanz in der Dokumentation drin steht, die man nicht schon am Funktionsnamen und den Argumentnamen ablesen kann.

Falsche Kommentare sind schlimmer als keine Kommentare. Ein Kommentar sollen eventuelle Fragen mit dem Code klären, aber wenn dort falsche Informationen drin stehen, oder gar welche die dem Code direkt widersprechen, erreicht man genau das Gegenteil. Der Leser weiss dann nicht was falsch ist, der Code oder der Kommentar.

Bei ``return rezepte[:7] # Geändert auf zwei Rezepte`` stimmt der Kommentar nicht mit dem Code überein.

`os.path` & Co verwendet man in neuen Programmen nicht mehr seit es `pathlib` gibt.

`datei` ist ein Name bei dem der Leser ein Dateiobjekt erwartet, also ein Objekt das Methoden wie `read()`, `write()`, und `close()` besitzt. Den Namen sollte man nicht für Datei*namen* verwenden.

Wörterbücher die immer den gleichen Satz an Schlüsseln haben sind eigentlich Objekte. `collections.namedtuple()` bietet sich an, falls die Objekte nicht veränderbar sein müssen und keine weiteren Eigenschaften bekommen sollen.

`ausgewaehlte_rezepte` sollte nicht an `eigene_rezeptauswahl()` übergeben werden. Man übergibt keine leeren Listen an Funktionen damit diese die Liste dann füllen. Die Funktion sollte eine Liste als Rückgabewert liefern.

`index` ist kein guter Name für eine laufende Zahl die bei 1 anfängt, weil Indexwerte in Python immer 0-basiert sind.

`zufaellige_auswahl()` verändert/mischt die übergebene Liste und gibt dann einen Teil davon zurück. Das ist für den Aufrufer überraschend und ich würde das sogar als echten Fehler bezeichnen. Letztlich braucht es dafür aber auch gar keine eigene Funktion wenn man einfach `random.sample()` verwenden kann.

Das was Du da mit den Rezepten wechseln machen willst funktioniert so nicht. Das Programm wird bei Änderungen die der Benutzer in der Oberfläche macht, immer komplett neu ausgeführt, womit dann auch wieder neu zufällig Rezepte ausgesucht werden. Mir ist auch nicht so ganz klar was „wechseln“ hier überhaupt bedeuten soll.

Der Code unter dem ``elif`` ist für meinen Geschmack auch schon zu viel gewesen. Das hätte ich in eine Funktion ausgelagert. Zudem wiederholt sich da Code zum Anzeigen von Rezepten der an anderer Stelle fast genau so schon mal steht.

Zwischenstand (ungetestet):

Code: Alles auswählen

import random
from collections import namedtuple
from itertools import islice
from pathlib import Path

import streamlit as st

REZEPTE_ORDNER = Path("Rezepte")

Rezept = namedtuple("Rezept", "name bildpfad details")


def lade_rezepte():
    return [
        Rezept(
            file_path.stem.lstrip("#"),
            str(file_path.with_name(file_path.stem + ".jpg")),
            file_path.read_text(encoding="utf-8"),
        )
        for file_path in REZEPTE_ORDNER.glob("*.txt")
    ]


def eigene_rezeptauswahl(rezepte):
    st.write("Wähle 7 Rezepte aus")
    return list(
        islice((rezept for rezept in rezepte if st.checkbox(rezept.name)), 7)
    )


def erstelle_rezeptplan(rezepte):
    st.write("Rezeptplan für eine Woche")
    for tag_nummer, rezept in enumerate(rezepte, 1):
        st.write(f"Tag {tag_nummer}")
        st.write(rezept.name)
        st.image(rezept.bildpfad, caption="", width=150)
        st.write("---")


def zeige_ausgewaehlte_rezepte(rezepte):
    for rezept in rezepte:
        st.write(rezept.name)
        st.image(rezept.bildpfad, caption="", width=150)
        if st.button(f"Rezept anzeigen: {rezept.name}"):
            st.write(rezept.details)


def main():
    st.sidebar.image(
        str(REZEPTE_ORDNER / "Tasty and Quick.jpd.png"), width=200
    )
    st.title("Willkommen bei Tasty and Quick")
    st.markdown("___")

    auswahl = st.sidebar.radio(
        "Was möchtest du diese Woche kochen?",
        ["Eigene Rezeptauswahl", "Zufallsauswahl"],
    )

    st.title("Rezeptplaner")

    rezepte = lade_rezepte()
    ausgewaehlte_rezepte = []

    if auswahl == "Eigene Rezeptauswahl":
        ausgewaehlte_rezepte = eigene_rezeptauswahl(rezepte)
        if len(ausgewaehlte_rezepte) == 7:
            erstelle_rezeptplan(ausgewaehlte_rezepte)

    elif auswahl == "Zufallsauswahl":
        rezeptplan = random.sample(rezepte, 7)
        if rezeptplan:
            st.write("Rezepte für diese Woche:")
            for rezept in rezeptplan:
                st.write(rezept.name)
                st.image(rezept.bildpfad, caption="", width=150)
                if st.button(f"Rezept anzeigen: {rezept.name}"):
                    st.write(rezept.details)

    if ausgewaehlte_rezepte:
        st.write("Ausgewählte Rezepte")
        zeige_ausgewaehlte_rezepte(ausgewaehlte_rezepte)


if __name__ == "__main__":
    main()
Wenn man Zustand über Läufe hinweg benötigt, muss man das in `st.session_state` speichern.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Alice1
User
Beiträge: 6
Registriert: Montag 29. Mai 2023, 10:57

Hallo,

vielen lieben danke für eure Typs und das ihr euch die zeit genommen habe ich werde daran arbeiten und versuche es erneut. :)

Gruß Alice1
Alice1
User
Beiträge: 6
Registriert: Montag 29. Mai 2023, 10:57

hey

danke aber geht leider noch immer nicht wenn ich den Button Ändere betätige ändern sich noch immer alle Rezepte.

Gruß Alice
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Wo hast Du einen Button "Ändere" und was bedeutet, dass sich alle Rezepte ändern? Und welchen Code hast Du jetzt ausgeführt?
Alice1
User
Beiträge: 6
Registriert: Montag 29. Mai 2023, 10:57

Ich habe beide versucht.
das ist meine Anweisung.

Lastenheft Projekt KochApp

- Es soll ein Rezeptplan für eine Woche erstellt werden, d.h. 7 Rezepte für 7 Tage.
- Die Rezepte sollen wahlweise automatisch vorgeschlagen oder selbst ausgewählt werden
können. Über einen Button kann der Benutzer selbst entscheiden, was er möchte.
- Entscheidet er sich für die Selbstauswahl, so soll in geeigneter Weise eine unkomplizierte
Möglichkeit zur Auswahl der Rezepte angeboten werden, z.B. eine Tabelle mit den Bildern
und Namen der Rezepte. Der Benutzer kann dann selbst 7 Rezepte auswählen (anklicken).
Bei der Zufallsauswahl werden zufällig 7 Rezepte präsentiert. Optional soll der Benutzer die
Möglichkeit haben, eines der Rezepte abzulehnen. In diesem Fall wird ihm ein neues Rezept
vorgeschlagen.
- Ein Ordner enthält mindestens 21 Rezepte.
- Jedes Rezept besteht aus einer Textdatei (.txt) und einer Bilddatei (.jpg, .png).
- Alle Bilddateien müssen eine ausreichend hohe Auflösung haben und das Gericht deutlich
erkennbar darstellen.
- Die Bilddatei hat den Namen Gerichtname_xxx.jpg (xx steht für eine Nummerierung; z.B.
_002.jpg).
- Die Textdatei hat folgende Struktur
- Name: Gerichtname_xxx.txt


# Zutaten
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;Anzahl Menge Name
1 St. Zwiebel
30 g Butter
1.5 Liter Wasser
1 Prise Muskat
0.5 Teelöffel Salz
….
# Zusatzdaten
Arbeitszeit ca. 25 Minuten
Koch-/Backzeit ca. 30 Minuten
Gesamtzeit ca. 55 Minuten
# Zubereitung
;;;;;;;;;;;;;;;;;;;;;;;;Hier folgen die ganzen Anweisungen.
;;;;;;;;;;;;;;;;;;;;;;;;Können auch nummeriert sein.
Den Kürbis teilen, die Kerne daraus entfernen,
grob raspeln (beim Hokkaido-Kürbis kann die Schale
mit verarbeitet werden). Das geht am besten mit einem
Multizerkleinerer, da dieser Kürbis sehr hart ist.
Zwiebeln, Kartoffeln und Karotten schälen und würfeln.
Die Zwiebelwürfel in zerlassener Butter anbraten.
Dann das weitere Gemüse dazugeben und mit anbraten.
Das Wasser dazugeben und alles etwa 20 Minuten garen.
….


Das Zeichen „#“ deutet auf eine Überschrift hin.

- Nach der Erstellung des Menüs soll der Benutzer die Möglichkeit haben, sich eine Liste mit
den Mengen der Zutaten ausgeben zu lassen. Dies kann in Form einer Textdatei erfolgen, die
im Arbeitsordner gespeichert wird (optional: Der Benutzer hat die Möglichkeit, eine .pdfoder .jpg- oder .txt-Datei an einem bestimmten Ort zu speichern; hier ist die Möglichkeit der
Ortswahl entscheidend, nicht das Format).
- Die Mengenangaben sind für zwei Personen abzuspeichern.
- Es gibt eine Möglichkeit, die Menge auf mehrere Personen (1..8) umzurechnen.
- Dazu ist eine GUI zu erstellen. Seitlich bzw. unten oder auch oben sollen jeweils
Möglichkeiten zur Auswahl von Arbeitsschritten vorhanden sein (z.B. Rezeptvorschlag
erstellen, Personenanzahl ändern, Einkaufsliste erstellen etc.)
- Eine Skizze der GUI soll erstellt werden.
Leistungen:
1. Software Projekt in Python und dem Modul Streamlit (Zusatzmodule durchaus erwünscht).
Sollte das Projekt nicht ohne weitere Installationsschritte laufen, so ist eine weitere
Handlungsanweisung für die Installation abzugeben (z.B. Installation von Zusatzpaketen).
2. Ein Skizze der GUI mit den Wahlmöglichkeiten und eine Aufteilung der Arbeitsschritte auf die
jeweiligen Projektteilnehmer. Jedes Projekt umfasst 3 Mitglieder. Skizze und
Arbeitsaufteilung sind zeitnah im entsprechenden Teams Kanal1
abzulegen (Zeitpunkt:
spätestens bis Do. 25.05 20:00 Uhr, Format .docx). Jedes Team gibt sich einen
ansprechenden Teamnamen (z.B. fancyCooking ).
3. Ein Bildschirmvideo, welches das Produkt vorstellt. Maximal 2 Minuten. Das Video besteht
aus drei Teilen: Projekttitel, Mittelteil indem das Produkt dargestellt wird, ein Abspann indem
alle Projektteilnehmer benannt werden.

so läuft alles aber mit den Button Rezept Ändern kann ich noch immer nicht ein Einzels Rezept von den Zufälligen 7 Ändern.
und ich bräuchte noch einen Button der mir die txt Datei öffnet das geht in der eignen Auswahl gut aber nicht bei der Zufallsauswahl.
der Code:
import random
from collections import namedtuple
from itertools import islice
from pathlib import Path

import streamlit as st

# Definition - Ordner, in dem die Rezepte gespeichert sind
REZEPTE_ORDNER = Path("Rezepte")

Rezept = namedtuple("Rezept", "name bildpfad details")

# Lädt die Rezepte aus den Textdateien im REZEPTE_ORDNER. Erzeugt für jedes Rezept ein Rezept-Objekt, wobei der Name, der Bildpfad und die Details des Rezepts ausgelesen werden.
def lade_rezepte():
return [
Rezept(
file_path.stem.lstrip("#"),
str(file_path.with_name(file_path.stem + ".jpg")),
file_path.read_text(encoding="utf-8"),
) for file_path in REZEPTE_ORDNER.glob("*.txt")
]

# Fragt den Benutzer, seine eigene Auswahl von 7 Rezepten zu treffen. Zeigt eine Checkbox für jedes Rezept an und nimmt die ausgewählten Rezepte entgegen.
def eigene_rezeptauswahl(rezepte):
st.write("Wähle 7 Rezepte aus")
return list(islice((rezept for rezept in rezepte if st.checkbox(rezept.name)), 7))

# Erstellt den Rezeptplan für eine Woche. Zeigt den Tag, den Namen des Rezepts und ein Bild des Rezepts an.
def erstelle_rezeptplan(rezepte):
st.write("Rezeptplan für eine Woche")
for tag_nummer, rezept in enumerate(rezepte, 1):
st.write(f"Tag {tag_nummer}")
st.write(rezept.name)
st.image(rezept.bildpfad, caption="", width=250)
st.write("---")

# Zeigt die ausgewählten Rezepte an. Zeigt den Namen des Rezepts und ein Bild des Rezepts an. Wenn der "Rezept anzeigen" Button geklickt wird, werden die Details des Rezepts angezeigt.
def zeige_ausgewaehlte_rezepte(rezepte):
for rezept in rezepte:
st.write(rezept.name)
st.image(rezept.bildpfad, caption="", width=250)
if st.button(f"Rezept anzeigen: {rezept.name}"):
st.write(rezept.details)

# Hauptfunktion - Steuert den Ablauf des Rezeptplaners. Zeigt das Titelbild, den Willkommens-Titel und die Auswahlmöglichkeiten für den Benutzer an.
# Lädt die Rezepte, überprüft die Auswahl des Benutzers und erstellt den Rezeptplan basierend auf der Auswahl des Benutzers oder durch Zufallsauswahl.
# Zeigt die ausgewählten Rezepte an, falls vorhanden.
def main():
st.sidebar.image(str(REZEPTE_ORDNER / "Tasty and Quick.jpd.png"), width=250)
st.title("Willkommen bei Tasty and Quick")
st.markdown("___")

auswahl = st.sidebar.radio("Was möchtest du diese Woche kochen?", ["Eigene Rezeptauswahl", "Zufallsauswahl"])

st.title("Rezeptplaner")

rezepte = lade_rezepte()
ausgewaehlte_rezepte = []

if auswahl == "Eigene Rezeptauswahl":
ausgewaehlte_rezepte = eigene_rezeptauswahl(rezepte)
if len(ausgewaehlte_rezepte) == 7:
erstelle_rezeptplan(ausgewaehlte_rezepte)

elif auswahl == "Zufallsauswahl":
# Liste zur Aufnahme der zufällig ausgewählten Rezepte
rezeptplan = []

for i in range(7):
random_rezept = random.choice(rezepte)
rezeptplan.append(random_rezept)
rezepte.remove(random_rezept)

if rezeptplan:
st.write("Rezepte für diese Woche:")
for rezept in rezeptplan:
st.write(rezept.name)
st.image(rezept.bildpfad, caption="", width=250)
if st.button(f"Rezept ändern: {rezept.name}"):
# Entferne das aktuelle Rezept aus dem Rezeptplan
rezeptplan.remove(rezept)
# Wähle ein neues zufälliges Rezept aus
new_rezept = random.choice(rezepte)
# Füge das neue Rezept zum Rezeptplan hinzu
rezeptplan.append(new_rezept)
# Aktualisiere die Liste der verfügbaren Rezepte
rezepte.remove(new_rezept)
st.write("Neues Rezept:")
st.write(new_rezept.name)
st.image(new_rezept.bildpfad, caption="", width=250)
st.write("Details:")
st.write(new_rezept.details)
st.write("---")

if ausgewaehlte_rezepte:
st.write("Ausgewählte Rezepte")
zeige_ausgewaehlte_rezepte(ausgewaehlte_rezepte)


if __name__ == "__main__":
main()
geraldfo
User
Beiträge: 44
Registriert: Samstag 28. Januar 2023, 20:19
Wohnort: Nähe Wien

Hallo @Alice1,

Vorschläge:
* lass deine ersten Programme auf der Befehlszeile laufen
* benutze im Forum den "Vollständigen Editor" und Code-Tags (5. Button). Einrückungen bleiben dann erhalten.

LG Gerald
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Alice1: Nochmal: Die Dokumentation was eine Funktion macht gehört als Docstring in die Funktion, nicht als Kommentar vor die Funktion.

Die gängigen Dokumentationswerkzeuge, zum Beispiel Sphinx (damit wird auch die Python-Dokumentation erstellt) oder [url=https://pdoc.dev/]pdoc[/url, verarbeiten auch für Konstanten und Attribute ”Docstrings”. Dann muss man aber die ”magic”-Option von streamlit ausschalten. Würde ich sowieso machen, denn das ist nicht normales Python-Verhalten und da gewöhnt man sich am Ende etwas an, was man sich in ”normalen” Python-Programmen dann wieder abgewöhnen muss.

Warum hast Du denn `random.sample()` durch eigenen Code ersetzt? Der dazu noch `rezepte` verändert.

Nach der Auswahl von sieben Rezepten zu prüfen ob `rezeptplan` Elemente enthält, ist ein bisschen sinnlos, dann man hat da ja gerade welche drin gespeichert, also ist das immer wahr.

Es gelten weiterhin die beiden Aussagen von Sirius3, dass man eine Liste über die man iteriert, nicht verändern darf, und von mir, dass das *gesamte Programm* für *jede* Benutzerinteraktion *erneut ausgeführt* wird. Wenn Du also die „Rezept ändern“-Schaltfläche betätigst, wird das Programm neu abgearbeitet, eine neue zufällige Liste mit Rezepten erstellt, und auf *der* dann die Änderungen durchgeführt.

Zur Aufgabenstellung: Deine Dateinamen halten sich nicht an das Muster aus der Aufgabe. Dort gibt es kein #-Zeichen am Anfang, dafür aber eine vom Namen abgetrennte Nummer. Ohne weitere Beschreibung wozu die gut sein soll‽

Der Inhalt der Textdateien soll laut Aufgabenstellung mit Markdown ausgezeichnet sein. Die müssen dann natürlich auch mit der entsprechenden Funktion angezeigt werden und nicht mit `write()`.

Code: Alles auswählen

#!/usr/bin/env python3
"""
Tasty and Quick
===============

* TODO [Beschreibung des Programms/des Moduls]
"""
import random
from itertools import islice
from pathlib import Path

import streamlit as st
from attr import attrib, attrs

REZEPTE_ORDNER = Path("Rezepte")
"""
Für jedes Rezept existieren in diesem Ordner zwei Dateien, eine JPG-Datei und
eine Textdatei mit den Details dieses Rezepts im Markdown-Format.

Die Dateinamen folgen dem Muster ``#<Rezeptname>.(jpg|txt)``.

Aus diesen Dateien werden in `lade_rezepte()` `Rezept`-Objekte erstellt.
"""


@attrs(frozen=True)
class Rezept:
    name = attrib()
    """Name des Rezepts.  Eindeutig innerhalb von `REZEPTE_ORDNER`."""
    bildpfad = attrib()
    """Pfad zum Bild für dieses Rezept."""
    details = attrib()
    """Beschreibung des Rezepts im Markdown-Format."""

    def schreibe_details(self):
        st.markdown(self.details)

    def schreibe(self, detailliert=False):
        st.write(self.name)
        st.image(self.bildpfad, caption="", width=250)
        if detailliert:
            self.schreibe_details()

    @classmethod
    def laden(cls, text_file_path):
        """
        Lädt ein Rezept aus dem gegebenen Pfad für die Textdatei und erstellt einen gleichnamigen Pfad für die Bilddatei.
        """
        return cls(
            text_file_path.stem.lstrip("#"),
            str(text_file_path.with_suffix(".jpg")),
            text_file_path.read_text(encoding="utf-8"),
        )


def lade_rezepte():
    """
    Lädt die Rezepte aus den Textdateien im `REZEPTE_ORDNER`. Erzeugt für jedes
    Rezept ein `Rezept`-Objekt, wobei der Name, der Bildpfad und die Details des
    Rezepts ausgelesen werden.
    """
    return list(map(Rezept.laden, REZEPTE_ORDNER.glob("*.txt")))


def eigene_rezeptauswahl(rezepte):
    """
    Fragt den Benutzer, seine eigene Auswahl von 7 Rezepten zu treffen. Zeigt
    eine Checkbox für jedes Rezept an und nimmt die ausgewählten Rezepte
    entgegen.
    """
    st.write("Wähle 7 Rezepte aus")
    return list(
        islice((rezept for rezept in rezepte if st.checkbox(rezept.name)), 7)
    )


def erstelle_rezeptplan(rezepte):
    """
    Erstellt den Rezeptplan für eine Woche. Zeigt den Tag, den Namen des Rezepts
    und ein Bild des Rezepts an.
    """
    st.write("Rezeptplan für eine Woche")
    for tag_nummer, rezept in enumerate(rezepte, 1):
        st.write(f"Tag {tag_nummer}")
        rezept.schreibe()
        st.write("---")


def zeige_ausgewaehlte_rezepte(rezepte):
    """
    Zeigt die ausgewählten Rezepte an. Zeigt den Namen des Rezepts und ein Bild
    des Rezepts an. Wenn der "Rezept anzeigen" Button geklickt wurde, werden die
    Details des Rezepts angezeigt.
    """
    st.write("Ausgewählte Rezepte")
    for rezept in rezepte:
        rezept.schreibe()
        if st.button(f"Rezept anzeigen: {rezept.name}"):
            rezept.schreibe_details()


def main():
    """
    Hauptfunktion - Steuert den Ablauf des Rezeptplaners. Zeigt das Titelbild,
    den Willkommens-Titel und die Auswahlmöglichkeiten für den Benutzer an.

    Lädt die Rezepte, überprüft die Auswahl des Benutzers und erstellt den
    Rezeptplan basierend auf der Auswahl des Benutzers oder durch
    Zufallsauswahl.

    Zeigt die ausgewählten Rezepte an, falls vorhanden.
    """
    st.sidebar.image(
        str(REZEPTE_ORDNER / "Tasty and Quick.jpd.png"), width=250
    )
    st.title("Willkommen bei Tasty and Quick")
    st.markdown("___")

    auswahl = st.sidebar.radio(
        "Was möchtest du diese Woche kochen?",
        ["Eigene Rezeptauswahl", "Zufallsauswahl"],
    )

    st.title("Rezeptplaner")

    rezepte = lade_rezepte()
    ausgewaehlte_rezepte = []

    if auswahl == "Eigene Rezeptauswahl":
        ausgewaehlte_rezepte = eigene_rezeptauswahl(rezepte)
        if len(ausgewaehlte_rezepte) == 7:
            erstelle_rezeptplan(ausgewaehlte_rezepte)
        elif ausgewaehlte_rezepte:
            zeige_ausgewaehlte_rezepte(ausgewaehlte_rezepte)

    elif auswahl == "Zufallsauswahl":
        rezeptplan = random.sample(rezepte, 7)
        st.write("Rezepte für diese Woche:")
        for rezept in rezeptplan:
            rezept.schreibe()
            if st.button(f"Rezept ändern: {rezept.name}"):
                rezeptplan.remove(rezept)
                new_rezept = random.choice(rezepte)
                rezeptplan.append(new_rezept)
                rezepte.remove(new_rezept)
                st.write("Neues Rezept:")
                new_rezept.schreibe(detailliert=True)
            st.write("---")


if __name__ == "__main__":
    main()
Für die Umrechnung der Angaben auf n Personen müsste man die Daten entweder aus der Textdatei extrahieren, oder strukturiert irgendwie vorhalten und für die Detailausgabe aufbereiten.

Das mit der Einkaufsliste und Personenzahl eingeben ist ziemlich spät im Lastenheft. Das wäre ja eigentlich eher der erste Schritt, mal festzulegen was als erstes vom Benutzer ausgewählt werden können muss. Einkaufsliste ist nicht weiter spezifiziert, oder soll das einfach die Liste der Zutaten sein?

Letztlich braucht die Anwendung Zustand der über Programmläufe hinweg gespeichert wird, weil ja wie gesagt, jede Änderung vom Benutzer in der Benutzeroberfläche einen komplett neuen Programmlauf zur Folge hat. Und Zustand speichert man in streamlit in `st.session_state`.

Randbemerkung: Du packst immer den Rezeptnamen in die Button-Texte. Das ist redundant, man sieht ja bei welchem Rezept der Button ausgegeben wurde, da muss nicht immer noch mal der Rezeptname im Button stehen. Damit streamlit die Buttons auseinander halten kann, gibt es das `key`-Argument.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Alice1
User
Beiträge: 6
Registriert: Montag 29. Mai 2023, 10:57

Hey

vielen lieben dank ich habe es versucht aber es geht noch immer nicht das sich nur ein Rezept ändert.
”magic”-Option von streamlit ausschalten geht nicht also ich kann es nicht im Internet steht das es nicht geht.
Ich verzweifle langsam für mein erstes Projekt bin ich leicht überfordert bis jetzt hat es noch keiner in meiner Klasse hinbekommen das es nach Anweisung läuft.
für weitere Tipps und Hilfen bin ich dankbar :)

Gruß Alice1
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Alice1: Was hast Du versucht? Wie gesagt: jedes mal wenn der Benutzer in der Weboberfläche eine Schaltfläche anklickt, oder die Auswahl bei Radiobuttons ändert, und so weiter, wird das komplette Programm erneut ausgeführt. Also auch das `random.sample()` oder was auch immer Du Dir da als Alternative schreibst.

Die ”Magie” von streamlit ausschalten geht. Habe ich gemacht. Dabei habe ich dann auch gleich ausgeschaltet das die Bibliothek mit Nutzungsstatistiken nach Hause telefoniert. Alleine deswegen sollte man sich schon eine Konfigurationsdatei anlegen. Und das automatische ausführen wenn die Programmdatei(en) geändert wurde(n) kann man da auch permanent anschalten. Was die Entwicklung vereinfacht.

Kleiner Nachtrag: Weil das immer neu ausgeführt wird, und nicht garantiert ist in welcher Reihenfolge Dateinamen/Pfade vom Dateisystem geliefert werden, sollte man die Rezepte nach dem laden/vor dem anzeigen explizit sortieren.

Dann solltest Du Dir mal die Dokumentation zur `title()`-Funktion durchlesen und überlegen ob Du die wirklich *zweimal* aufrufen willst. Und was es noch so für Funktionen gibt um beispielsweise *Überschriften* auszugeben.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Alice1
User
Beiträge: 6
Registriert: Montag 29. Mai 2023, 10:57

Hey

verstehe ich das richtig das es nur funktionieren kann wen ich magic-Option von streamlit ausschalten?
oder liegt es an meinem Programm das ich geschrieben habe?

was kann ich machen damit es funktioniert?
ich verstehe nur die ein viertel von dem was ihr hier reinschreibt bitte nicht vergessen ich habe mich erst seid 2 Monaten mit dem Programm beschäftigt ich habe vorher noch nie was mit IT zu tun gehabet.

das mit magic Option von streamlit ausschalten finde ich auch nicht im Internet wie soll ich es denn ausschalten?

Gruß Alice1
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@Alice1: so lange Du nicht das grundlegende Konzept von streamlit verinnerlicht hast, macht es keinen Sinn, ein komplexeres Programm zu schreiben.
Wichtig ist es, die Dokumentation durchzuarbeiten, insbesondere https://docs.streamlit.io/library/get-s ... #data-flow . Nur wenn Du weißt, was in jedem Schritt passiert, kannst Du darauf aufbauend ein eigenes Programm schreiben.

Kurz zusammengefasst: jedes mal, wenn Du einen Knopf drückst, vergisst Dein Programm alles, was der Nutzer oder der Zufallsgenerator bisher gemacht hat und erzeugt einen neuen Satz an Rezepten. Dazu mußt Du den Zustand Deines Programms zwischen zwei Läufen speichern, mit den Mitteln, die streamlit hier bietet.
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Alice1: Die Magie muss man ausschalten wenn man Docstrings an Stellen verwendet, die Python selbst nicht für Docstrings vorsieht, aber die üblichen Werkzeuge um Dokumentation aus dem Quelltext zu erstellen. Weil diese Texte sonst im Browser auftauchen. Selbst wenn man das nicht abschaltet, ist es sinnvoll und praktisch wenn man das „nach Hause telefonieren“ von streamlit unterbindet und das neu ausführen wenn der Quelltext verändert und gespeichert wurde aktiviert.

Und wie so oft: nicht irgendwo im Internet suchen, sondern in der Dokumentation von der verwendeten Software/Bibliothek schauen wie man das konfiguriert. Da gibt es ein eigenes Kapitel für, das „Configuration“ heisst.

Man kann sich die aktuelle Konfiguration vom ``streamlit`` Kommando anzeigen lassen. Das habe ich einfach in eine Datei umgeleitet, an der Stelle wo die projektbezogene Konfiguration von streamlit erwartet wird, und dort habe ich die Änderungen in der Datei vorgenommen.

Noch eine Randbemerkung zu den Rezeptdaten: Ich würde da ganz vorsichtig mit Bildern von Essen sein, dass da garantiert nur welche verwendet werden, bei denen die Urheberrechtsfrage geklärt ist. Das ist ein beliebtes Abmahnthema.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Antworten