Mit AJAX und Django ein Dashboard aktualisieren

Django, Flask, Bottle, WSGI, CGI…
Antworten
Phanti
User
Beiträge: 5
Registriert: Mittwoch 2. Oktober 2019, 20:11

Guten Abend zusammen,

ich freue mich, dass ich endlich eine aktive deutsche Community finden konnte, mit deren Unterstützung ich hoffentlich meine Python Skills weiterentwicken kann, damit ich künftig selber mehr unterstüzen kann ;).

Um Python zu lernen, arbeite ich aktuell an einem Dashboard, das ich via Django als Web Application aufsetze. Im Wesentlichen sollen User/Besucher die Möglichkeit haben, ein vorgegebenes Dashboard mit den Daten unterschiedlicher Fussballvereine anschauen zu können. Das Frontend habe ich mal soweit, dass es zu Testzwecken genutzt werden kann. Jetzt möchte ich im Backend Folgendes erreichen:

Sobald ein User auf ein Team in der Sidebar klickt, wird die id des html tags (des jeweiligen Teams) einer Variable mit Hilfe von Javascript zugeordnet. Diese Variable 'team_id' soll dann via AJAX von der view Funktion genutzt werden, um über eine API die korrekten Daten zu ziehen, damit das Dashboard entsprechend dem ausgewählten Team die Daten ausweist (die team_id ist Teil der API URL).

Allerdings nutzt die view Funktion die Variable nicht und ich weis nicht, woran es liegt. Die Variable team_id, die von der View Funktion genutzt wird, ist immer leer. Sie wird aber in Javascript 100% korrekt zugeordnet (laut console log im Google Chrome developer tool.) Ich würde mich auch über Alternative Lösungswege freuen, die ohne AJAX funktionieren.

Schonmal vielen Dank für eure Unterstützung und beste Grüße aus Stuttgart.

Anbei noch ein paar Bilder und mein Code, um es zu verdeutlichen:

Views.py:

Code: Alles auswählen

import requests
from django.shortcuts import render
import json


def team_update(request):
    team_id = request.GET.get('team')
    response = requests.get(f'http://www.api-football.com/demo/api/v2/teams/team/{team_id}')
    team_data = response.json()
    teams = team_data.get('api', {}).get('teams', [])
    if teams and len(teams) == 1:
        teams = teams[0]
    return render(request, 'index.html', {
        'name': teams['name'],
        'country': teams['country'],
        'founded': teams['founded'],
        'logo': teams['logo'],
        'venue_capacity': teams['venue_capacity'],
    })
javascript:

Code: Alles auswählen

$('ul.subbar li a').on('click', function(e) {
var team_id = $(this).attr("id");
console.log(team_id);
$.ajax({
  method: "GET",
  url: "/dashboard/",
  data: {'team': team_id},
  success: function(response) {
    console.log(response)
    }
  });
});
Mein frontend:

Bild
Benutzeravatar
sparrow
User
Beiträge: 4187
Registriert: Freitag 17. April 2009, 10:28

Was hat 'team_id' für eine Wert vor dem Ajax aufruf, was wird tatsächlich übermittelt und was hat team_id für einen Wert in team_update?
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

Woher weißt Du, dass team_id leer ist? Was sagen die Server-Logs?
Phanti
User
Beiträge: 5
Registriert: Mittwoch 2. Oktober 2019, 20:11

Sirius3 hat geschrieben: Mittwoch 2. Oktober 2019, 21:23 Woher weißt Du, dass team_id leer ist? Was sagen die Server-Logs?

Danke für Eure Beiträge, anbei noch weitere Informationen:

Code: Alles auswählen

TypeError at /dashboard/FC Bayern München/
list indices must be integers or slices, not str
Request Method:	GET
Request URL:	http://127.0.0.1:8000/dashboard/FC%20Bayern%20M%C3%BCnchen/
Django Version:	2.2.5
Exception Type:	TypeError
Exception Value:	
list indices must be integers or slices, not str
Exception Location:	C:\Users\Jonas\Desktop\dasocc\dasocc_site\dasocc_app\views.py in team_update, line 14
Python Executable:	C:\Users\JONASB~1\Envs\DASOCC~1\Scripts\python.exe
Python Version:	3.7.4
Python Path:	
['C:\\Users\\Jonas\\Desktop\\dasocc\\dasocc_site',
 'C:\\Users\\JONASB~1\\Envs\\DASOCC~1\\Scripts\\python37.zip',
 'C:\\Users\\JONASB~1\\Envs\\DASOCC~1\\DLLs',
 'C:\\Users\\JONASB~1\\Envs\\DASOCC~1\\lib',
 'C:\\Users\\JONASB~1\\Envs\\DASOCC~1\\Scripts',
 'c:\\users\\jonas\\appdata\\local\\programs\\python\\python37-32\\Lib',
 'c:\\users\\jonas '
 'blickle\\appdata\\local\\programs\\python\\python37-32\\DLLs',
 'C:\\Users\\JONASB~1\\Envs\\DASOCC~1',
 'C:\\Users\\JONASB~1\\Envs\\DASOCC~1\\lib\\site-packages']
Server time:	Wed, 2 Oct 2019 22:25:00 -0500
HTML:

Code: Alles auswählen

    <!-- Sidebar -->

    <div id="sidebar">
      <header>
        <a href="#">Dasocc</a>
      </header>
      <ul class="nav">
        <li class="countries"><img src="{% static "images/germany.png" %}" alt="germany">1. Bundesliga
          <ul class="subbar">
            <li><a class="team" href="/dashboard/FC Bayern München/" id="69">FC Bayern München</a></li>
            <li><a class="team" href="/dashboard/Borussia Dortmund/" id="70">Borussia Dortmund</a></li>
Error log: http://dpaste.com/1SMF8F1

Frontend: Bild
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

Daran sieht man ja nur, dass `teams` keine Liste mit einem Element ist, und daher ein Folgefehler auftritt, weil Du die Fehlerbehandlung nicht sauber machst. Außerdem, wenn Du dem Link folgst, landest Du in der selben Funktion, wie Du siehst, aber ohne Team-Argument, sondern mit "FC Bayern München"-Pfad. Liegt daran, dass Du kein event.prevent aufrufst.
Phanti
User
Beiträge: 5
Registriert: Mittwoch 2. Oktober 2019, 20:11

Sirius3 hat geschrieben: Mittwoch 2. Oktober 2019, 21:51 Daran sieht man ja nur, dass `teams` keine Liste mit einem Element ist, und daher ein Folgefehler auftritt, weil Du die Fehlerbehandlung nicht sauber machst. Außerdem, wenn Du dem Link folgst, landest Du in der selben Funktion, wie Du siehst, aber ohne Team-Argument, sondern mit "FC Bayern München"-Pfad. Liegt daran, dass Du kein event.prevent aufrufst.
Guten Morgen und vielen Dank für deine Antwort.

Ich habe die JS Funktion entsprechend um event.prevent ergänzt. Laut dem Django debug ist die variable `team_id` innerhalb der view Funktion leer (s. unteres Drittel):

Code: Alles auswählen

C:\Users\Jonas Blickle\Desktop\dasocc\dasocc_site\dasocc_app\views.py in team_update
        'name': teams['name'], …
▼ Local vars
Variable	Value
request	
<WSGIRequest: GET '/dashboard/FC%20Bayern%20M%C3%BCnchen/'>
response	
<Response [200]>
team_data	
{'api': {'ENDPOINTS': {'countries': {'countries': 'https://www.api-football.com/demo/api/v2/countries'},
                       'events': {'events': 'https://www.api-football.com/demo/api/v2/events/{fixture_id}'},
                       'fixtures': {'date': 'https://www.api-football.com/demo/api/v2/fixtures/date/{date}',
                                    'h2h': 'https://www.api-football.com/demo/api/v2/fixtures/h2h/{team_1}/{team_2}',
                                    'id': 'https://www.api-football.com/demo/api/v2/fixtures/id/{fixture_id}',
                                    'league': 'https://www.api-football.com/demo/api/v2/fixtures/league/{league_id}',
                                    'live': 'https://www.api-football.com/demo/api/v2/fixtures/live',
                                    'team': 'https://www.api-football.com/demo/api/v2/fixtures/team/{team_id}'},
                       'leagues': {'country': 'https://www.api-football.com/demo/api/v2/leagues/country/{country_name}/{season}',
                                   'league': 'https://www.api-football.com/demo/api/v2/leagues/league/{league_id}',
                                   'leagues': 'https://www.api-football.com/demo/api/v2/leagues',
                                   'season': 'https://www.api-football.com/demo/api/v2/leagues/season/{season}'},
                       'lineups': {'lineups': 'https://www.api-football.com/demo/api/v2/lineups/{fixture_id}'},

         'ERROR': 'WRONG TEAM_ID',
         'WARNING': 'THIS IS A DEMO AND DOES NOT REPRESENT THE ENTIRE API. THE '
                    'DATA IS LIMITED AND NOT UP TO DATE AND SERVES ONLY AS AN '
                    'EXAMPLE. FOR PRODUCTION ENVIRONEMENT USE : '
                    'HTTPS://API-FOOTBALL-V1.P.RAPIDAPI.COM/V2/',
         'results': 0}}
team_id	None
teams	
[]
Deswegen erhalte ich 0 Results und den TypeError als Folge... :?

Zu deiner zweiten Ausführung bzgl. URL mapping. Mein Ziel ist, dass der User auf der Seite ../dashboard/ ist und diese sich dann nicht ändert, wenn er andere Teams auswählt. Hierzu nehme ich dann einfach wieder die href Links in den html tags der Teams raus.

Danke und schönen Feiertag!

+++++++++++++++
Update:

Es scheint jetzt zu funktionieren 8)
Aber folgendes Problem: In der Console wird die team_id korrekt angezeigt und die API Daten werden in den entsprechenden Containern gerendert.
Allerdings sehe ich es nicht im Frontend? Was ist hier die Ursache? Evtl. steht das Problem in Zusammenhang mit der AJAX success Funktion?

Bild

Dankee
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

du hast noch eine grundlegenden Designfehler in der App: dein View returniert eine komplette HTML-Seite. Wenn man AJAX benutzt möchte man aber eigentlich nur Daten z.B. als JSON zurück geliefert bekommen und aktualisiert _dann_ mit diesen Daten clientseitig via JS die Seite im Browser. Sonst kannst du auch einen "normalen" Request-Response Zyklus machen und die Seite jedes Mal neu laden.

Und der Weg via AJAX ist ein bisschen old-school. Normalerweise nimmt man die `fetch` API von JavaScript dafür, weil das der einfachere Weg (und aktuelle) Weg ist.

Gruß, noisefloor
Phanti
User
Beiträge: 5
Registriert: Mittwoch 2. Oktober 2019, 20:11

noisefloor hat geschrieben: Donnerstag 3. Oktober 2019, 10:29 Hallo,

du hast noch eine grundlegenden Designfehler in der App: dein View returniert eine komplette HTML-Seite. Wenn man AJAX benutzt möchte man aber eigentlich nur Daten z.B. als JSON zurück geliefert bekommen und aktualisiert _dann_ mit diesen Daten clientseitig via JS die Seite im Browser. Sonst kannst du auch einen "normalen" Request-Response Zyklus machen und die Seite jedes Mal neu laden.

Und der Weg via AJAX ist ein bisschen old-school. Normalerweise nimmt man die `fetch` API von JavaScript dafür, weil das der einfachere Weg (und aktuelle) Weg ist.

Gruß, noisefloor
Genau das Problem habe ich vor 15 Min. erkannt, weil ich jetzt die gesamte Seite nochmal in einen html Container packe und ich dann die Seite sozusagen doppelt sehe im Frontend, was natürlich Unsinn ist.

Der Django Doku nach ist return render dann aber die falsche Funktion für meinen Bedarf, weil ich ja ein template angeben muss
, aber eigentlich nur einzelne Daten aus der API Antwort für AJAX benötige und gar kein komplettes Template rendern möchte? https://docs.djangoproject.com/en/2.2/t ... shortcuts/

Muss ich dann eine alternative View Funktion nutzen?

Vielen Dank, so langsam verstehe ich die Zusammenhänge von Client-/Server-Seite etc. :-)
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

Django kennt eine JSONResponse.

Warum machst du den API Call eigentlich nicht direkt von der Webseite? Der Umweg über Django ist im gegebenen Fall eigentlich nicht notwendig.

Gruß, noisefloor
Phanti
User
Beiträge: 5
Registriert: Mittwoch 2. Oktober 2019, 20:11

noisefloor hat geschrieben: Donnerstag 3. Oktober 2019, 11:19 Hallo,

Django kennt eine JSONResponse.

Warum machst du den API Call eigentlich nicht direkt von der Webseite? Der Umweg über Django ist im gegebenen Fall eigentlich nicht notwendig.

Gruß, noisefloor
Hallo,

einerseits habe ich mich dafür entschieden, die App auf Basis von Django zu bauen, weil ich diverse weitere Python Frameworks benötigen werde (z.B. Plotly/Bokeh) und andererseits, weil ich es einfach lernen wollte :D.

Was meinst du mit, "direkt von der Website"? Könnte man mit der Fetch API die Daten auf der Server-Seite abrufen? (Oder mit AJAX und die View Funktion rendered nur einmal /dashboard/ bzw. index.html wenn ein User auf die Seite kommt?) Also mit anderen Worten, AJAX ruft die API Url (async) auf, zieht die Daten in JSON Format, dann bearbeite ich die Daten wie ich sie brauche und schicke sie sozusagen mit der Success Funktion ins Frontend?

Danke.
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,
Könnte man mit der Fetch API die Daten auf der Server-Seite abrufen?
Ja, was anders macht dein Django View ja auch nicht.
dann bearbeite ich die Daten wie ich sie brauche und schicke sie sozusagen mit der Success Funktion ins Frontend?
Neim, _alles_ läuft via JS im Browser. Für das gegebene Bespiel, also den Code mit der Funktionalität, wie in deinem Eingangspost gezeigt, brauchst du den Server-Teil nicht. Der braucht nur die Seite 1x liefern, der Rest kann clientseitig laufen.

Gruß, noisefloor
Benutzeravatar
sparrow
User
Beiträge: 4187
Registriert: Freitag 17. April 2009, 10:28

@Phanti: Noisefloor hat völlig recht, du kannst das alles im Browser abhandeln. Dafür brauchst du dein eigenes Backend nicht.

Aber noch einmal zu deinem Code aus den Postes weiter oben:
Ich bin nicht völlig fit in JavaScript, aber so wie ich das sehe, kommt der Aufruf der zu dem Fehler auf Seite des Fehlers führt nicht durch den Aufruf der ajax-Funktion im Script ausgelöst.
Das Element, dass du anklickst, ist ein Link. Der Link ruft die URL '/dashboard/FC%20Bayern%20M%C3%BCnchen/' auf - das ist die URL bei dem der Fehler auftritt. Das heißt: Selbst wenn du die Ajax-Funktion komplett aus der Webseite entfernst, sollte es zu dem Problem kommen.
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

@Phanti: nur um Missverständnissen vorzubeugen: wir wollen Django nicht schlecht reden. Nur für den gegeben Fall brauchst du gar kein serverseitiges Python, auch nix kleines wie Bottle oder Flask. Wenn deine Applikation noch andere Teile hat - die wir aktuell nicht kennen - dann kann es natürlich schon sein, dass Django und dessen hohe Integration aus ORM, Formularframework, Template Engine, CBV etc. Sinn macht.

Gruß, noisefloor
Antworten