Django - Select-Field springt wieder auf ersten Eintrag zurück

Django, Flask, Bottle, WSGI, CGI…
Antworten
luvlinux
User
Beiträge: 33
Registriert: Donnerstag 24. August 2017, 08:53

Hallo zusammen,
ich habe eine ziemlich einfach gehaltene Bücher-App erstellt. In dieser möchte ich nun über eine Suche ein Genre mittels Select-Field auswählen. Dies konnte ich auch mit Hilfe recht gut realisieren.
Wenn ich also bspw. das Genre "Thriller" auswähle, bekomme ich auch das entsprechende queryset korrekt angezeigt. Allerdings bleibt im Select-Field nicht das vorher ausgewählte Genre markiert, sondern es wird auf den ersten Eintrag zurückgesetzt.

Kann mir jemand helfen, wie ich dieses Problem lösen kann bzw. erläutern, warum dies genau passiert?

Danke schon jetzt für eine Anwort.
luvlinux

Code: Alles auswählen

views.py
def genre_filter(request):
    if request.method == 'GET':
        genres = Genre.objects.all()
        context = {'genres': genres}
        return render(request, 'books/genre_select.html', context)
    else:
        genres = Genre.objects.all()
        genre = request.POST.get('selector')
        books = Book.objects.filter(genre=genre)
        context = {
            'genres': genres,
            'books': books,
        }
    return render(request, 'books/genre_select.html', context)

Code: Alles auswählen

genre_select.html

{% block content %}
  <h4>Filtering a Genre</h4>
  <form action="" method="POST">
    {% csrf_token %}
    <select name="selector">
      {% for genre in genres %}
      <option value="{{genre.id}}">{{genre.name}}</option>
      {% endfor %}
    </select>
  <button type="submit">Filter</button>
</form>
  <h4>Output...</h4>
  {{ books }}
{% endblock %}

Code: Alles auswählen

urls.py

urlpatterns = [
    …,
    path('genre_filter/', views.genre_filter, name='genre_filter'),
]

Code: Alles auswählen

models.py
...
class Genre(models.Model):
    name = models.CharField(max_length=25)
 
    def __str__(self):
        return self.name

class Book(models.Model):
    title = models.CharField(max_length=128)
    author = models.ForeignKey(Author, on_delete=models.CASCADE)
    genre = models.ForeignKey(Genre, on_delete=models.CASCADE) 
    isbn = models.CharField(max_length=10)
    price = models.DecimalField(max_digits=7, decimal_places=2)
    date_published = models.DateField()
...
Bolitho
User
Beiträge: 219
Registriert: Donnerstag 21. Juli 2011, 07:01
Wohnort: Stade / Hamburg
Kontaktdaten:

Du musst das gewählte 'genre' an das Template übergeben und bei der entsprechend gewählten Option ein 'selected' im HTML Element setzen.
https://www.w3schools.com/tags/att_option_selected.asp

Code: Alles auswählen

#views.py
        context = {
            'genres': genres,
            'books': books,
            'selected_genre': genre
        }


#html
<option value="{{genre.id}}" {% if genre == selected_genre %}selected{% endif %}>{{genre.name}}</option>
Ich würde im View übrigens auf POST testen und dann den Rest als Standard abhandeln.

Code: Alles auswählen

views.py
def genre_filter(request):
    books = None
    genres = Genre.objects.all()
    selected_genre = False    
    if request.method == 'POST':
        selected_genre = request.POST.get('selector')
        books = Book.objects.filter(genre=genre)
    context = {
        'genres': genres,
        'books': books,
	'selected_genre': selected_genre
    }
    return render(request, 'books/genre_select.html', context)
Bolitho
User
Beiträge: 219
Registriert: Donnerstag 21. Juli 2011, 07:01
Wohnort: Stade / Hamburg
Kontaktdaten:

Update, ich konnte den vorigen Beitrag nicht mehr editieren.
request.POST.get() erlaubt die Deklaration eines Wertes, falls kein POST Request vorliegt oder das gesuchte Attribut nicht vorhanden ist. Damit lässt sich noch eine Zeile einsparen und der Code wird noch leserlicher.

Code: Alles auswählen

views.py
def genre_filter(request):
    books = None
    genres = Genre.objects.all()
    selected_genre = request.POST.get('selector', None) 
    if selected_genre:
        books = Book.objects.filter(genre=genre)
    context = {
        'genres': genres,
        'books': books,
	'selected_genre': selected_genre
    }
    return render(request, 'books/genre_select.html', context)
Benutzeravatar
noisefloor
User
Beiträge: 4151
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,
Kann mir jemand helfen, wie ich dieses Problem lösen kann bzw. erläutern, warum dies genau passiert?
Das passiert, weil du die Seite neu lädst. Spricht, du sendest die Seite neu an den Browser und darum ist alles in "Ausgangsposition". Das hat auch nicht wirklich was mit Django zu tun, sondern ist halt so, wie Browser, HTML und neu geladene Seiten sich verhalten (sollen).

Eine mögliche Lösung hat dir Bolitho schon gezeigt.

Gruß, noisefloor
luvlinux
User
Beiträge: 33
Registriert: Donnerstag 24. August 2017, 08:53

@Bolitho
Vielen Dank für deine Antwort. Ich habe deine Lösung versucht umzusetzen, leider springt der Wert immer noch auf den ersten Eintrag zurück. Was habe ich bis jetzt probiert:
In deiner views.py ist mir aufgefallen dass es wohl :

Code: Alles auswählen

books = Book.objects.filter(genre=selected_genre)
anstatt
books = Book.objects.filter(genre=genre)
heißen soll. Dies habe ich abgeändert und die Funktion genre_filter entsprechend in meine views.py eingefügt.
Dann habe ich den vorgeschlagenen HTML-Code eingesetzt:

Code: Alles auswählen

<option value="{{genre.id}}" {% if genre == selected_genre %}selected{% endif %}>{{genre.name}}</option>
Als es nicht klappte, merkte ich, dass wohl nicht mit genre sondern genre.id verglichen werden sollte, da seleccted_genre ja einen Integer zurückgibt. Somit habe ich folgendes probiert:

Code: Alles auswählen

<option value="{{ genre.id }}" {% if genre.id == selected_genre %}selected{% endif %}>{{genre.name}}</option>
Als dies auch nicht funktionierte, habe ich aus einem stackoverflow-post Folgendes abgeleitet und eingesetzt:

Code: Alles auswählen

<option value="{{ genre.id }}" {% ifequal genre.id selected_genre %}selected{% endifequal %}>{{genre.name}}</option>
Da das auch nicht funktionierte habe ich mal probiert mit den DeveloperTools die Werte für genre.id und selected_genre auszugeben, mittels:

Code: Alles auswählen

<option value="{{genre.id}}" {% if genre.id == selected_genre %}genre.id:{{ genre.id }} seleted_genre:{{ selected_genre}} {% endif %}>{{genre.name}}</option>
Ergebnis in Browser-Elements:
https://www.bilder-upload.eu/bild-de25c ... 7.png.html
Da ich merkte, dass hier nichts durchkommt habe ich die If-Abfrage in eine != Abfrage umgewandelt also so:

Code: Alles auswählen

<option value="{{genre.id}}" {% if genre.id != selected_genre %}genre.id:{{ genre.id }} seleted_genre:{{ selected_genre}} {% endif %}>{{genre.name}}</option>
Jetzt zeigte sich, dass schon die richtigen genre.id und selected_genre durchkommen. Dann sieht es in Browser-Elements wie folgt aus:
https://www.bilder-upload.eu/bild-e14fd ... 5.png.html

Eigentlich kommen doch die richtigen Werte durch, die verglichen werden können. Woran könnte es denn liegen, dass es bei mir nicht funktioniert?
Benutzeravatar
__blackjack__
User
Beiträge: 13927
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@luvlinux: Ich würde mal auf Zeichenketten vs. Zahlen tippen. Das <select> liefert Zeichenketten. Wenn Du diese ID die da kommt zum filtern in der Datenbank oder vergleichen mit *Zahlen* verwenden willst, müsstest Du sie in eine *Zahl* umwandeln.
“Java is a DSL to transform big Xml documents into long exception stack traces.”
— Scott Bellware
luvlinux
User
Beiträge: 33
Registriert: Donnerstag 24. August 2017, 08:53

@__blackjack__
Ja, genau daran liegt es. Die Variable select_genre gibt einen String zurück. Ich habe nun wie folgt in der Funktion genre_filter den String umgewandelt:

Code: Alles auswählen

selected_genre = int(selected_genre)
Und jetzt klappt das Ganze! Auch dir vielen Dank, dass ich das Problem nun lösen konnte.
luvlinux
User
Beiträge: 33
Registriert: Donnerstag 24. August 2017, 08:53

noisefloor hat geschrieben: Dienstag 12. November 2019, 11:15 Hallo,

Das passiert, weil du die Seite neu lädst. Spricht, du sendest die Seite neu an den Browser und darum ist alles in "Ausgangsposition". Das hat auch nicht wirklich was mit Django zu tun, sondern ist halt so, wie Browser, HTML und neu geladene Seiten sich verhalten (sollen).

Eine mögliche Lösung hat dir Bolitho schon gezeigt.

Gruß, noisefloor
@noisefloor: Vielen Dank für die Information, nun weiß ich auch was die Ursache für dieses Verhalten war.
luvlinux
User
Beiträge: 33
Registriert: Donnerstag 24. August 2017, 08:53

Falls jemand das gleiche Problem hat bzw. an der Lösung interessiert ist, hier nochmals die views.py und genre_select.html im funktionierenden Zustand:

Code: Alles auswählen

views.py
 
def genre_filter(request):
    books = None
    genres = Genre.objects.all()
    selected_genre = request.POST.get('selector', None)
    if selected_genre:
        books = Book.objects.filter(genre=selected_genre)
        selected_genre = int(selected_genre)
    context = {
        'genres': genres,
        'books': books,
        'selected_genre': selected_genre
    }
    return render(request, 'books/genre_select.html', context)

Code: Alles auswählen

genre_select.html

{% extends 'base.html' %}


{% block content %}
<h4>Filtering a Genre</h4>

<form action="" method="POST">
  {% csrf_token %}
  <select name="selector">
    {% for genre in genres %}
    <option value="{{genre.id}}" {% if genre.id == selected_genre %}selected{% endif %}>{{genre.name}}</option>
    {% endfor %}
  </select>
  <button type="submit">Filter</button>
</form>

<h4>Output...</h4>
{{ books }}
{% endblock %}
Bolitho
User
Beiträge: 219
Registriert: Donnerstag 21. Juli 2011, 07:01
Wohnort: Stade / Hamburg
Kontaktdaten:

oh, da habe ich ja massiv geschludert. Sorry dafür.
luvlinux
User
Beiträge: 33
Registriert: Donnerstag 24. August 2017, 08:53

@Bolitho
Passt doch, ein bisschen Debugging gehört schließlich zum Geschäft, zumal ich mir auch Gedanken machen musste. Insgesamt hat mich das sehr weitergebracht.
Antworten