Benutzer Profil speichern

Django, Flask, Bottle, WSGI, CGI…
Antworten
Pitwheazle
User
Beiträge: 869
Registriert: Sonntag 19. September 2021, 09:40

Ich habe mich durch ein neues Tutorium gearbeitet und habe wieder einiges weiter gebracht.
Wenn ich das richtig sehe, muss der Nutzer eine Emailadresse eingeben, damit er sein Passwort zurücksetzen kann und ich benötige noch einige Daten von meinen Schülerinnen und Schüler. So sieht meine Registrierungsseite jetzt aus:
Bild
Mithilfe des Tutoriums habe ich zunächst die eingebaute Userdaten um das Emailfeld ergänzt und dann noch eine Datenbank für weitere Informationen erstellt (Wobei ich nicht immer weiß, was ich da gemacht habe :? ). Hier ist die Form:

Code: Alles auswählen

from django import forms
from django.db import models

from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
from core.models import UserProfile

class ExtendedUserCreationForm(UserCreationForm):
    email = forms.EmailField(required=True)

    class Meta:
        model = User
        fields = ["username", "email"]

    def save(self, commit=True):
        user = super().save(commit=False)
        user.email = self.cleaned_data('email')

        if commit:
            user.save()
            return user

class UserProfileForm(forms.ModelForm):
    class Meta:
        model = UserProfile

        fields = ('nachname', 'vorname', 'klasse', 'jg')
"ExtendedUserCreationForm" ergänzt, wenn ich das richtig verstanden habe, die "eingebaute" Userdatenbank und "UserProfileForm" ergänzt um weitere Daten. Die sollen hier gespeichert werden:

Code: Alles auswählen

class UserProfile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE )
    nachname = models.CharField(max_length=20)
    vorname = models.CharField(max_length=20) 
    klasse = models.CharField(max_length=10)
    jg = models.PositiveSmallIntegerField(default=5)

    def __str__(self):
        return f"({self.vorname} {self.nachname}, {self.klasse}, Jg: {self.jg})"
und das ist das zugehörige view:

Code: Alles auswählen

from django.shortcuts import render, redirect
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth import authenticate, login
from .forms import ExtendedUserCreationForm, UserProfileForm

def registrieren(req):
    print("1")
    if req.method == 'POST':
        print("2")
        form = ExtendedUserCreationForm(req.POST)
        profile_form = UserProfileForm(req.POST)
        if  form.is_valid() and profile_form.is_valid():
            print("3")
            user = form.save()
            profile = profile_form.save(commit=False)
            profile.user = user
            profile.save()
            username = form.cleaned_data['username']
            password = form.cleaned_data['password1']
            user = authenticate(username=username, password=password)
            login(req, user)
            return redirect('kategorien')
    else:
        print("4")
        form = ExtendedUserCreationForm()
        profile_form = UserProfileForm() 
    print("5")   
    context = {'form': form, 'profile_form' : profile_form} 
    return render(req, 'accounts/registrieren.html', context)
... tja, und da kann ich erkennen, dass bei der Eingabe der form (also der Info zur Email) ein Fehler auftritt, denn "3" wird nicht ausgegeben.
Wird auch der Code des Templates benötigt?

Code: Alles auswählen

{% extends "layout.html" %}

{% block title %}Registrieren{% endblock %}

{% block content %}
  <h2>Registrieren</h2>
  <form method="post" action="{% url 'registrieren' %}">
    {% csrf_token %}
    {{ form.as_p }}
    {{ profile_form.as_p }}
    <input type="submit" value="Registrieren">
  </form>
{% endblock %}
Zwei Fragen: Kann man das überhaupt so machen oder geht das einfacher? und 2. Warum werden meine Daten nicht gespeichert - respektive: Wie bekomme ich raus, was bei der Eingabe nicht stimmt?
Benutzeravatar
noisefloor
User
Beiträge: 3843
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

wenn du mehr als ein Formular aus HTML heraus übermitteln willst brauchst du AFAIK JavaScript, sonst wird nur eine Form gesendet. Such' mal nach "html submit multiple forms" bei Google.

Gruß, noisefloor
Pitwheazle
User
Beiträge: 869
Registriert: Sonntag 19. September 2021, 09:40

Ok, da habe ich jetzt einfach ein Formular entfernt, jetzt klappt es!
Jetzt habe ich aber ein neues Problem - ich nehme an es ist ganz einfach und das weiß wieder jeder außer mir:
Nach dem Speichern der Schülerdaten wird der user eingelogt und mein Startfenster angezeigt (ich bin ganz stolz, dass ich das jetzt hinbekommen habe)Im Startfenster wird auch der Username angezeigt - wie aber greife ich jetzt auf diesen eingeloggten User zu?
Bisher habe ich das ja mit der Funktion "FakeUser" (immer noch von Whitie) gemacht:

Code: Alles auswählen

def get_fake_user():
    return Schueler.objects.all().first()
Das entsprechende "Schueler" Objekt befindet sich im AppOrdner "core", ich habe aber jetzt ein neues Object "Schueler" mit OneToOne Verknüfung zum user im AppOrdner "accounts" angelegt.
Und im View:

Code: Alles auswählen

def main(req, slug):                                                        #hier läuft alles zusammen
    kategorie = get_object_or_404(Kategorie, slug = slug)
    kategorie_id = kategorie.id
    user = get_fake_user() 
    ...
 
Benutzeravatar
Whitie
User
Beiträge: 216
Registriert: Sonntag 4. Juni 2006, 12:39
Wohnort: Schulzendorf

Wenn der Schüler korrekt eingeloggt ist, solltest du den in "req.user" haben. Füge einfach mal ein "print(req.user)" in deinen View ein und schau nach.

Viele Grüße
Whitie
Pitwheazle
User
Beiträge: 869
Registriert: Sonntag 19. September 2021, 09:40

Prima (was würde ich nur ohne dich machen? Ich fürchte, ich werde Django nie komplett begreifen)
Also "req.user" bringt mich weiter. Ich benötige jetzt aber die Daten, die zu dem "user" in "Schueler" gespeichert sind (z.B. den Jahrgang, den Kurs und die "Stufe"). Ich habe ja "Schueler" mit einer OneToOne Relation mit "user" verbunden. Aber (ich hatte es schon befürchtet "req.user.nachname" wirft einen Fehler "'User' object has no attribute 'nachname'". Also habe ich es so probiert:

Code: Alles auswählen

def get_fake_user(user):
    return Schueler.objects.filter(user = user)

def main(req, slug):   
    #print(req.user.nachname)
    user = get_fake_user(req.user)
    #print(user.nachname)
    return HttpResponse (user.nachname)        
... das klappt auch nicht: "'QuerySet' object has no attribute 'nachname'"
Aber "return HttpResponse (user)" funktioniert und liefert mir " (Horst Musterschüler, 5.1)".
Benutzeravatar
Whitie
User
Beiträge: 216
Registriert: Sonntag 4. Juni 2006, 12:39
Wohnort: Schulzendorf

Das Schueler-Objekt müsste auf dem User schon vorhanden sein.

Code: Alles auswählen

def main(req, slug):
    return HttpResponse(req.user.<schueler>.nachname)
Den Inhalt der spitzen Klammern müsstest du noch mit deiner korrekten Bezeichnung füllen.
Pitwheazle
User
Beiträge: 869
Registriert: Sonntag 19. September 2021, 09:40

Vielen Dank für die schnelle Antwort. Ich bin aber leider zu blöd den Hinweis umzusetzen. Es geht ja auch eigentlich nicht um das Auslesen des Nachnamens, das habe ich nur mal eingesetzt um rauszubekommen, wie das alles funktioniert. Ich schicke hier besser den entsprechenden Code. Das ist das Model "Schueler":

Code: Alles auswählen

class Schueler(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE )
    nachname = models.CharField(max_length=30)
    vorname = models.CharField(max_length=30)
    
    klasse = models.CharField(max_length=10)
    jg = models.PositiveSmallIntegerField(validators=[MinValueValidator(5), MaxValueValidator(10)])

    kurs= models.CharField(max_length=1, choices=wahl_kurs.choices, default=wahl_kurs.E_KURS,)

    # werden beim Erstellen eingestellt
    stufe = models.PositiveSmallIntegerField(default=5) #, editable=False)
    halbjahr = models.PositiveSmallIntegerField(default=0)

    def __str__(self):
        return f"({self.vorname} {self.nachname}, {self.klasse})"
Und den Zugriff auf "user" habe ich zum ersten Mal hier:

Code: Alles auswählen

def main(req, slug):                                                        #hier läuft alles zusammen
    user = get_fake_user(req.user)
    kategorie = get_object_or_404(Kategorie, slug = slug)
    kategorie_id = kategorie.id
    if req.method == 'POST': 
    ...
        else:                                                                   #Aufgabenstellung
        zaehler, created = Zaehler.objects.get_or_create(user = user, kategorie = kategorie)
...
        protokoll = Protokoll.objects.create(
            user = user, titel = titel, halbjahr = halbjahr, kategorie = kategorie, text = text, pro_text = pro_text, einheit = einheit, anmerkung = anmerkung, value = result, loesung = lsg, hilfe = hilfe, grafik = grafik, individuell = ""       
        )     
        ....            
     
(Die Grundstruktur ist noch immer von dir.) Für jede gestellte Aufgabe wird ein Objekt in "Protokoll" erstellt und für jeden Schüler wird, sobald eine Kategorie gewählt wird, ein Objekt "Zaehler" erstellt, so es noch nicht existiert.
Der Aufruf des Codes wirft in der Zeile "zaehler, created ... " den Fehler "FOREIGN KEY constraint failed".
Vielleicht liegt der Fehler aber auch daran, dass das model "Schueler" sich nicht mehr im Appordner "core" (noch von dir) befindet, es befindet sich jetzt im Ordner "account". Die models "Zaehler" und "protokoll" befinden sich noch im Appordner "core". Ich importiere "Schueler" dort aber (hoffentlich) mit "from accounts.models import Schueler".
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Der Name get_fake_user ist sehr verwirrend, weil ja gar kein fake und kein user geholt wird, sondern einfach nur ein schueler. Und der Variablenname ist dann auch falsch. Und filter ist nunmal falsch, um EINEN Schüler zu bekommen, das macht man mit get, oder hier viel einfacher, dadurch dass man das reverse-Attribut deiner One-2-One-Verbindung nutzt: req.user.schueler.

Wenn ein Fehler beim Erstellen von Zaehler auftaucht, dann müssen wir die Definition von Zaehler kennen und den komplette Traceback (nicht irgendwas abgekürztes).
Pitwheazle
User
Beiträge: 869
Registriert: Sonntag 19. September 2021, 09:40

Das mit dem get statt filter war mir auch schon eingefallen, das hatte ich schon geändert und das mit dem fake_user ist noch von dem Gerüst, das whitie mir mal gebastelt hat. Am Anfang war das ja auch noch ein fake_user, das habe ich jetzt auch entfernt und:

Code: Alles auswählen

    print(req.user.schueler.nachname)
    user = Schueler.objects.get(user = req.user)
    print(user.nachname)
gibt mir jetzt tatsächlich auch schon zweimal "Musterschüler" aus. Prima! Mit dem Rest bin ich noch nicht weitergekommen. Mein model "Zaehler":

Code: Alles auswählen

class Zaehler(models.Model):
    user = models.ForeignKey(Schueler, verbose_name='schueler', related_name='zaehler', on_delete=models.CASCADE)    
    kategorie = models.ForeignKey(Kategorie, on_delete=models.CASCADE, related_name="zaehler")
    
    optionen_text=models.CharField(max_length=40, blank=True, default="", verbose_name="Optionen")
    
    typ_anf = models.SmallIntegerField(default=0)        
    typ_end = models.SmallIntegerField(default=0)    

    aufgnr = models.PositiveSmallIntegerField(default=0)  

    richtig = models.PositiveSmallIntegerField(default=0)    
    Extrapunkte = models.DecimalField(max_digits=5, decimal_places=2, default=0)
    richtig_of = models.PositiveSmallIntegerField(default=0)   
    bearbeitungszeit = models.FloatField(default=0)     

    falsch = models.PositiveSmallIntegerField(default=0)    
    loesung = models.PositiveSmallIntegerField(default=0)    
    abbrechen = models.PositiveSmallIntegerField(default=0)    
    hilfe = models.PositiveSmallIntegerField(default=0) 

    def __str__(self):
        return f"({self.user}, {self.kategorie}, {self.aufgnr})"
    
    def quote(self):
        gesamt = self.richtig + self.falsch
        return self.falsch / gesamt *100 if gesamt else 0

    class Meta:
        verbose_name = 'Zähler'
        verbose_name_plural = 'Zähler'   
Und meinen Code in "main" habe ich mich bisher nicht getraut komplett hier einzustellen - ich hatte immer Angst, ihr lacht mich aus - aber bitte, auch diesen:

Code: Alles auswählen

def main(req, slug):                                                        #hier läuft alles zusammen
    print(req.user.schueler.nachname)
    user = Schueler.objects.get(user = req.user)
    print(user.nachname)
    kategorie = get_object_or_404(Kategorie, slug = slug)
    kategorie_id = kategorie.id
    if req.method == 'POST':  
        protokoll = Protokoll.objects.get(pk = req.session.get('protokoll_id'))
        protokoll.tries += 1
        zaehler = Zaehler.objects.get(pk = req.session.get('zaehler_id'))
        zaehler.hinweis = ""
        if protokoll.value != 0:
            form = AufgabeFormZahl(req.POST)
        else:
            form = AufgabeFormStr(req.POST)
        if form.is_valid():                                                 #Aufgabe beantwortet
            eingabe = form.cleaned_data['eingabe']
            if protokoll.tries == 1:
                protokoll.eingabe = protokoll.eingabe + str(eingabe)
            elif protokoll.tries == 2:
                protokoll.eingabe =f"(1:) {protokoll.eingabe} (2:) {eingabe}"
            else:
                protokoll.eingabe = f"{protokoll.eingabe} (3:) {eingabe}"
            protokoll.bearbeitungszeit = (timezone.now() - protokoll.start).total_seconds()
            protokoll.save()
            wertung, rueckmeldung = kontrolle(eingabe, protokoll.value, protokoll.loesung, protokoll.id)
            if wertung == 1:
            #if kontrolle(eingabe, protokoll.value, protokoll.loesung):      #Anwort richtig
                protokoll.wertung = protokoll.wertung + "r"
                protokoll.save()
                zaehler.richtig += 1
                zaehler.richtig_of +=1
                zaehler.aufgnr += 1
                zaehler.save()
                if zaehler.aufgnr > 10:
                    if  zaehler.optionen_text not in ["", "keine",]:         #setzt Stufe hoch wenn eine Option angekreuzt wurde und in der Option "update" = True
                        max_stufe = 3
                        for auswahl in Auswahl.objects.filter(
                            kategorie=kategorie_id,
                            text__in=zaehler.optionen_text.split(";"),
                            ).all():
                                if(auswahl.bis_stufe) > user.stufe and auswahl.update:
                                    user.stufe = auswahl.bis_stufe+user.stufe%2
                                    user.save()
                    zaehler.optionen_text = ""
                    zaehler.hinweis = ""
                    zaehler.aufgnr = 0
                    zaehler.save()
                    return redirect('kategorien')
                quote = int(zaehler.falsch/(zaehler.richtig+zaehler.falsch)*100)
                msg = f'<br>richtig: {zaehler.richtig}, falsch: {zaehler.falsch}, Fehlerquote: {quote}%, EoF: {zaehler.richtig_of}/{kategorie.eof}'
                messages.info(req, f'Die letzte Aufgabe war richtig! {msg}')
                return redirect('main', slug)
            else: 
                titel = protokoll.titel
                text = protokoll.text
                einheit = protokoll.einheit
                anmerkung = protokoll.anmerkung
                hilfe = protokoll.hilfe
                grafik = protokoll.grafik
                if wertung < 0:
                    messages.info(req, rueckmeldung)  
                    wertung = -1                                                                                  #Antwort falsch = warten auf neue Eingabe
                if wertung == -1:
                    protokoll.wertung = protokoll.wertung + "f"
                    protokoll.save()
                    zaehler.falsch += 1
                    zaehler.richtig_of  = 0
                    zaehler.save()
                    quote = int(zaehler.falsch/(zaehler.richtig+zaehler.falsch)*100)
                    msg = f'<br>richtig: {zaehler.richtig}, falsch: {zaehler.falsch}, Fehlerquote: {quote}%, EoF: {zaehler.richtig_of}/{kategorie.eof}'
                    messages.info(req, f'Die letzte Aufgabe war leider falsch! Versuche: {protokoll.tries}, {msg}')   
                    if protokoll.tries >= 3:                                            #3 mal falsch
                        anmerkung = "Leider war deine Eingabe dreimal falsch!<br>Richtig wäre die Lösung: {0} <br>- Frage mal jemanden der dir das erklärt!".format(protokoll.loesung[0])

                        #messages.info(req, f'letzte Aufgabe: {protokoll.text}, Lösung: {protokoll.loesung}')                     
                        #return redirect('kategorien')
                else:
                    messages.info(req, f'{rueckmeldung}')                               #gibt eine Rückmeldung wenn "indiv" bei Lösung steht  
    else:                                                                   #Aufgabenstellung
        zaehler, created = Zaehler.objects.get_or_create(user = user, kategorie = kategorie)
        form = AufgabeFormZahl()
        if not zaehler.optionen_text :                                     #Aufgaben Einstellung
            return redirect('optionen', slug)
        typ, typ2, titel, text, pro_text, einheit, anmerkung, lsg, hilfe, result, grafik = aufgaben(kategorie.id, jg = user.jg, stufe = user.stufe, typ_anf = zaehler.typ_anf, typ_end = zaehler.typ_end, optionen = "") 
        if not pro_text:
            pro_text = text 
        if not titel:
            titel = kategorie.name
        halbjahr = user.halbjahr/10
        protokoll = Protokoll.objects.create(
            user = user, titel = titel, halbjahr = halbjahr, kategorie = kategorie, text = text, pro_text = pro_text, einheit = einheit, anmerkung = anmerkung, value = result, loesung = lsg, hilfe = hilfe, grafik = grafik, individuell = ""       
        )                                                                   #Protokoll wird erstellt
        req.session['protokoll_id'] = protokoll.id    
        req.session['zaehler_id'] = zaehler.id   
        if protokoll.value != 0:
            form = AufgabeFormZahl(req.POST)
        else:
            form = AufgabeFormStr(req.POST)
        if zaehler.aufgnr == 0:
            zaehler.aufgnr = 1
        zaehler.save()        
        protokoll.typ = typ
        protokoll.typ2 = typ2
        protokoll.aufgnr = zaehler.aufgnr        
        protokoll.save()  
        if zaehler.hinweis!= "":
            messages.info(req, f'{zaehler.hinweis}')   
    context = dict(kategorie = kategorie, typ = protokoll.typ, titel = titel, aufgnr = zaehler.aufgnr, text = text, 
        form = form, zaehler_id = zaehler.id, hilfe = hilfe, protokoll_id = protokoll.id, grafik = grafik, message_unten = anmerkung, einheit = einheit)
    return render(req, 'core/aufgabe.html', context)
Der letzte noch funktionierende Code ist auch in Github: https://github.com/pitweazle/rechentrainerweb
Pitwheazle
User
Beiträge: 869
Registriert: Sonntag 19. September 2021, 09:40

Hilfe! Ich bekomme es nicht hin.
Ich habe jetzt mal alle user und auch alle "Schueler" Instanzen (heißen die so?) gelöscht. Damit sind auch alle "Zaehler" und "protokoll" Instanzen weg. Dann habe ich einen neuen Benutzer Franz Musterschüler angelegt, der ist jetzt da und auch sein Profil ist jetzt in "Schueler" wunderbar eingerichtet.... Aber jetzt geht gar nichts mehr. Ich soll ja jetzt sicher nicht nochmals den ganzen Code hier hochladen, nur das wichtigste:

Code: Alles auswählen

def get_user(user):
    return Schueler.objects.get(user = user)

def main(req, slug):                                                        #hier läuft alles zusammen
    print(req.user)
    user = get_user(req.user)
    print(user)
... gibt
franz
(Franz Musterschüler, 5.1)
zurück und ergibt dann bei

Code: Alles auswählen

zaehler, created = Zaehler.objects.get_or_create(user = user, kategorie = kategorie)
den Fehler "FOREIGN KEY constraint failed"

Code: Alles auswählen

lass Zaehler(models.Model):
    user = models.ForeignKey(Schueler, verbose_name='Schüler', related_name='zaehler', on_delete=models.CASCADE)    
 
Kann das daran liegen, dass das model "Schueler" sich jetzt nicht mehr im gleichen Ordner wie "Zaehler" befindet, sondern in einem Ordner "accounts"? Sowohl im view.py als auch im model.py habe ich "from accounts.models import Schueler" ergänzt.
... oder mache ich was ganz anderes verkehrt?


Mit:

Code: Alles auswählen

def get_user():
    return Schueler.objects.all().first()

def main(req, slug): 
    print(req.user)
    user = get_user()
    print(user)
passiert genau das gleiche.
Pitwheazle
User
Beiträge: 869
Registriert: Sonntag 19. September 2021, 09:40

Uff! Ich habe es wieder hinbekommen (für`s erste).
Ich weiß ja nicht, ob das hier noch jemanden interessiert.
Ich habe eine ältere Version wieder hergestellt. In der waren die "Schueler" im gleichen Ordner ("core") wie "Zaehler" und "Protokoll", jetzt klappt es wieder. Es lag also wohl daran, dass die "Schueler" in einem anderen Ordner ("accounts") lagen. Irgendwie wäre es mir immer noch lieber, wenn ich die Accounts von meinen Aufgaben trennen könnte indem ich die models, fields,templates und views in getrennten Ordnern speichern könnte. Wenn also jemand sagen kann, was an "from accounts.models import Schueler" nicht stimmt, fände ich das prima.
Pitwheazle
User
Beiträge: 869
Registriert: Sonntag 19. September 2021, 09:40

Und noch ein Nachtrag:
Da ich nunmal die Accounts gerne in account hätte, habe ich noch ein bisschen rumprobiert und irgendwann hat es geklappt. Fragt mich nicht warum, ich habe mehrmals die Nutzer gelöscht, "Schueler" verschoben, makemigration und migrate ausgeführt und irgenwann hat es funktioniert. "Schueler" und z.B. auch "Lehrer" ist jetzt unter "accounts" zu finden und nicht mehr unter "core".
Antworten