Von Flask zu Django -> Wert an Webseite zurück geben

Django, Flask, Bottle, WSGI, CGI…
Benutzeravatar
Dennis89
User
Beiträge: 1652
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo zusammen,

bis jetzt hatte ich immer `Flask` genutzt und da ich aktuell eine neue Web-Anwendung entwickle, dachte ich, das wäre ein guter Zeitpunkt `Django` zu verwenden. Allerdings habe ich zwei Probleme. Damit die Seite dynamisch ist, will ich wieder `Vue` verwenden.
Meine Index.html sieht so aus:

Code: Alles auswählen

<html lang="de">
<head>
  <meta name="viewport" content="width=device-width,initial-scale=1.0" charset="utf-8">
  <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
  <title>Betriebsstunden-Auswertung</title>
</head>

<body>
  <div id="app">
      <form method="post">
        {% csrf_token %}
        {{ date_range.as_p }}
        {{ pressure_range.as_p }}

        <button @click="get_operation_hours">Auswertung</button>
      </form>
      {{ operationHours }}blaaaaaa
  </div>

<script>
  const { createApp, ref } = Vue

  createApp({
    setup() {
      let operationHours = ref(99999999)

    async function get_operation_hours() {
        const response = await fetch('http://localhost:8000/evaluation');
        operationHours.value = await response.json();
      }

  return { operationHours, get_operation_hours }
  }
  }).mount('#app')
</script>
</body>
</html>
Erstes Problem, das ich gar nicht verstehe. Wieso wird meine `operationHours` von 999999 nicht angezeigt, wenn ich die Seite lade? `blaaaaaa` wird angezeigt und in der Browser-Konsole kommt keine Fehlermeldung.

Wie man sieht, will ich anhand der Eingaben, aus einer Datenbank Betriebsstunden aussuchen und diese zurück geben. Dafür habe ich folgende `views.py`:

Code: Alles auswählen

from django.http import JsonResponse
from django.shortcuts import render

from .forms import DateRange, PressureRange


def index(request):
    if request.method == "POST":
        date_range = DateRange(request.POST)
        pressure_range = PressureRange(request.POST)
        if date_range.is_valid() and pressure_range.is_valid():
            date_range = (
                date_range.cleaned_data["start"],
                date_range.cleaned_data["end"],
            )
            pressure_range = {
                "min": pressure_range.cleaned_data["min"],
                "max": pressure_range.cleaned_data["max"],
            }
            # operation_hours = get_operation_hours(date_range, pressure_range)
            operating_hours = 385
            return JsonResponse({"operating_hours": operating_hours})
    else:
        date_range = DateRange()
        pressure_range = PressureRange()
    return render(
        request,
        "evaluation/index.html",
        context={"date_range": date_range, "pressure_range": pressure_range},
    )
Zum testen, gebe ich einfach mal 385 zurück. Wenn ich den Button auf der Webseite klicke, dann öffnet sich eine "schwarze" Seite mit dem Inhalt, den ich als JSON geschickt habe. Ihr wisst bestimmt was ich meine, die Seite hat Reiter: "JSON", "Rohdaten" und "Kopfzeilen".
Mit Flask hätte ich nur das Wörterbuch zurück geschickt und hätte dass dann im JavaScript-Teil erhalten. Hier läuft das irgendwie anders.
Im Django-Tutorial wird `HttpResponse` verwendet, ich will allerdings den Wert auf der aktuellen Seite anzeigen und keine andere Seite.

Und was ich grundsätzlich noch nicht verstanden habe. Ich kann den Button auch mit

Code: Alles auswählen

<input type="submit" value="Drück mich">
erstellen und `index` wird trotzdem ausgeführt. Ich kenne das halt von `Flask` so, das ich immer die Route angeben muss. Woher weiß der Button wo's lang geht?

Achja, `Flask` bringt alles mit, was es für die Anwendung benötigt. Allerdings, wenn die Zeit reicht, dann wird die Anwendung immer erweitert und es soll dann für bestimmte Bereiche, abhängig vom Benutzer, Zugänge verweigert werden. Daher will ich gleich mit `Django` starten.

Danke und Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Sirius3
User
Beiträge: 18340
Registriert: Sonntag 21. Oktober 2012, 17:20

Du mußt Dich entscheiden, ob Du nun Django zum Rendern verwenden möchtest, oder VUE. Du läßt django rendern und wunderst Dich, dass dann keine Variablen mehr durch vue erkannt werden.
Was mich wundert ist, dass Dein fetch GET zu einer json Rückgabe führt. Du scheinst doch ein form-submit abzusetzen, was natürlich die aktuelle Seite ersetzt.
Mach eine klare Trennung: ein http-Endpunkt sollte nicht sowohl html als auch json zurückliefen. Bei Formularen on-submit abfangen, damit das keine neu Seite lädt und alles mit javascript abarbeiten.
Benutzeravatar
sparrow
User
Beiträge: 4614
Registriert: Freitag 17. April 2009, 10:28

@Dennis89: Und der obligatorische Hinweis, der einem nicht immer gleich klar ist: Django kommt mit sehr viel, aber man muss nicht alles verwenden.
Django + Django Rest Framework eignet sich hervorragend um einfach REST APIs zu bauen, weil das DRF direkt an Djangos ORM dockt. Und Djangos Render/Template-Engine müsstest du gar nicht verwenden, wenn im Frontend so etwas wie Vue oder React läuft.
Benutzeravatar
Dennis89
User
Beiträge: 1652
Registriert: Freitag 11. Dezember 2020, 15:13

Danke für die Hinweise.
Ja das macht natürlich Sinn. Ich würde dann Vue für das redern verwenden, weil ich denke so habe ich die Logik und das Aussehen besser von einander getrennt und könnte auch schneller von `Django` auf was anderes wechseln oder von Vue auf was anderes.

Kann von euch bitte mal jemand dieses Beispiel testen?
https://vcalendar.io/getting-started/in ... e-from-cdn

Ich habe das 1:1 kopiert, in eine extra Datei unter `/var/www/html/` abgelegt und lasses es über `httpd` unter Fedora ausliefern. Es erscheint eine leere Seite und die Browser-Konsole sagt mir folgendes:

Code: Alles auswählen

Uncaught TypeError: a.a.mixin is not a function
    1315 			https://unpkg.com/v-calendar:1			v-calendar:1:10238
    r 				https://unpkg.com/v-calendar:1
    2af9 			https://unpkg.com/v-calendar:1
    r 				https://unpkg.com/v-calendar:1
    34e9 			https://unpkg.com/v-calendar:1
    34e9 			https://unpkg.com/v-calendar:1
    r 				https://unpkg.com/v-calendar:1
    fb15 			https://unpkg.com/v-calendar:1
    r 				https://unpkg.com/v-calendar:1
    <anonymous> https://unpkg.com/v-calendar:1
    <anonymous> https://unpkg.com/v-calendar:1
    <anonymous> https://unpkg.com/v-calendar:1
    <anonymous> https://unpkg.com/v-calendar:1

[Vue warn]: Failed to resolve component: vdatepicker			vue.global.js:2348:15
If this is a native custom element, make sure to exclude it from component resolution via compilerOptions.isCustomElement. 
  at <App> 
[Vue warn]: Failed to resolve component: vcalendar			vue.global.js:2348:15
If this is a native custom element, make sure to exclude it from component resolution via compilerOptions.isCustomElement. 
  at <App> 
Weil ich nicht wirklich was einrichten muss, würde ich gerne CDN benutzen.

Danke und Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
Dennis89
User
Beiträge: 1652
Registriert: Freitag 11. Dezember 2020, 15:13

Hm, das ist wohl bekannt, aber es wird nichts unternommen:
https://github.com/nathanreyes/v-calendar/issues/1511
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
noisefloor
User
Beiträge: 4270
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Alternativ zum Django REST Framework geht auch Django Ninja. Das ist von FastAPI inspiriert. Was man am Ende nimmt ist in Teilen Geschmackssache.

Jedenfalls sind beide relativ verbreitet, Django REST Framework gibt es (deutlich) länger. Aber für beide sollte man viele Infos im Netz (SO usw.) finden.

Gruß, noisefloor
Benutzeravatar
Dennis89
User
Beiträge: 1652
Registriert: Freitag 11. Dezember 2020, 15:13

Danke, schaue ich mir auch mal an.

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
nezzcarth
User
Beiträge: 1794
Registriert: Samstag 16. April 2011, 12:47

Nebenbemerkung: Eingebettetes JavaScript im HTML wie in deinem Eingangsspost sollte man nach Möglichkeit vermeiden, zumindest wenn man perspektivisch vorhat CSP umzusetzen (was man in Erwägung ziehen sollte). Es ist erfahrungsgemäß leichter, das gleich mit zu bedenken, als es später mühselig nachzuarbeiten.
Benutzeravatar
noisefloor
User
Beiträge: 4270
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

zumal Django das ab Version 6.0 OOTB unterstützt. Also selbst, wenn man "nur" LTS Versionen von Django nutzt, muss bzw. sollte man sich spätestens mit Django 6.2 LTS damit befassen.

Wenn ich das richtig verstehe, sollte man Anfangs den "report only" Modus nutzen, um die Logs zu nutzen, um zu sehen, ob man alles richtig konfiguriert hat.

Gruß, noisefloor
Benutzeravatar
Dennis89
User
Beiträge: 1652
Registriert: Freitag 11. Dezember 2020, 15:13

Guten Morgen,

danke für die Info. Ich habe das Script jetzt in eine extra Datei gepackt.


Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
Dennis89
User
Beiträge: 1652
Registriert: Freitag 11. Dezember 2020, 15:13

Ich muss mal nach fragen, weil ich eventuell ein Verständnis Problem habe. Ich rendere die Webseite mit VUE und `django` macht zur Zeit nichts weiteres, als ein paar Eingabewerte entgegen zu nehmen und einen Wert zurück zu geben. Ich habe nun mal, wie im Django-Tutorial die App zu einem Package gebaut und kann das jetzt via `pip` installieren. In der Vergangenheit habe ich die Webseite, von Apache ausliefern lassen und die Flask-App auf einem `gunicorn`-Server laufen lassen.
Ich weiß gar nicht wie ich jetzt vorgehen soll. Die geschriebene Web-Anwendung soll nach und nach erweitert werden und `django` bekommt in der Zukunft noch weitere "Aufgaben". Ich hätte jetzt gerne eine Struktur/ein Vorgehen, wie ich das aufbauen muss, damit ich an dem Projekt arbeiten kann und bei Updates das dann relativ einfach auf dem Produktions-Server aktualisieren kann.
Und davor frage ich, wie setze ich das Projekt jetzt eigentlich auf? Auch mit zwei Server wie oben beschrieben? Macht es überhaupt Sinn, dass ich die App zu einem Package gebaut habe? Ein Git-Repo habe ich auch angelegt und einen Raspberry-Pi habe ich hier liegen, der soll zum testen, den späteren Server simulieren.

Meine Struktur sieht so aus:

Code: Alles auswählen

.
├── LICENSE
├── MANIFEST.in
├── README.md
├── requirements.txt
├── BlaManagement
│   ├── dist
│   │   ├── blamanagement-0.1-py3-none-any.whl
│   │   └── blamanagement-0.1.tar.gz
│   ├── docs
│   ├── LICENSE
│   ├── pyproject.toml
│   ├── README.rst
│   ├── bla_management
│   │   ├── admin.py
│   │   ├── apps.py
│   │   ├── forms.py
│   │   ├── __init__.py
│   │   ├── migrations
│   │   ├── models.py
│   │   ├── __pycache__
│   │   ├── static
│   │   ├── templates
│   │   ├── tests.py
│   │   ├── urls.py
│   │   └── views.py
│   └── BlaManagement.egg-info
│       ├── dependency_links.txt
│       ├── PKG-INFO
│       ├── requires.txt
│       ├── SOURCES.txt
│       └── top_level.txt
└── BlaWebsite
    ├── db.sqlite3
    ├── blamanagement
    │   ├── admin.py
    │   ├── apps.py
    │   ├── forms.py
    │   ├── __init__.py
    │   ├── migrations
    │   ├── models.py
    │   ├── __pycache__
    │   ├── static
    │   ├── templates
    │   ├── tests.py
    │   ├── urls.py
    │   └── views.py
    ├── __init__.py
    ├── manage.py
    └── BlaManagement
        ├── asgi.py
        ├── __init__.py
        ├── __pycache__
        ├── settings.py
        ├── urls.py
        └── wsgi.py
Hier ist jetzt alles drin, der erste Ordner "BlaManagement" wurde erstellt nach dem das Testprojekt fertig war, um das Package installierbar zu machen. Davor habe ich in "BlaWebsite/blamanagement" gearbeitet und dass dann in den neuen Ordner kopiert.
In diesem Projekt habe ich das erstellte Package installiert und es in `settings.py` hinzugefügt und `urls.py` habe ich angepasst. Wenn ich dann den Server starte, den `django` mitbringt, dann funktioniert das alles. Zum hier während der Entwicklung zu testen, ist also alles gut. Nur wie setze ich das jetzt auf meinem richtigen Server auf?

Vielen Dank und Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
noisefloor
User
Beiträge: 4270
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

Django hat ja das Konzept von Projekt und Applikation(en), dass ist auch direkt zu Anfang des Django Tutorials erklärt. Das Projekt ist quasi der "Container" (nicht im Sinne von Docker oder so) für X Applikationen. Innerhalb eines Projekt teilen sich die Applikationen das Nutzermanagement, die Datenbank (sofern man nicht explizit mehrere DBs konfiguriert), die Apps können sich untereinander importieren bzw. auf Daten zugreifen (falls nötig) und alle Apps im Projekt können auf die zusätzlich installierten / aktivierten Module, Middleware etc (wie z.B. Whitenoise - siehe unten) zugreifen.

Ich mache das so, dass wenn ich ein Projekt im eine App erweitere, einen neuen Branch in git dafür anlege, darin entwickel und wenn die App fertig ist mit dem Main Branch merge und dann deploye. Ein Package habe ich noch nicht genutzt.

Bzgl. des Server: IMHO gängigste Praxis ist: Django App -> WSGI (oder ASGI, wenn man asynchron nutzt) Applikationsserverr auf localhost -> Reverse Proxy, der externe Anfrage auf den lokalen Applikationsserver weiterleitet und sich um SSL für HTTPS kümmert. Sehr gängig ist IMHO Gunicorn als WSGI Applikationsserver (alternativen: uwsgi oder waitress) und nginx als Reverse Proxy. Wenn du aber sowieso Apache nutzt, weil noch was anderes darauf läuft, kann du Apache natürlich auch als Reverse Proxy konfigurieren.

Was ich auch noch empfehlen wurde - wurde hier im Forum auch schon oft empfohlen: Whitenoise für statische Dateien. Macht das Deployment einfacher, weil sich Django dann immer um das Ausliefern von statischem Content kümmert und du das nicht beim Deploy extra in extra Verzeichnis auf den Server kopieren musst, weil statischer Content vom Webserver selber geliefert wird. Oder anders ausgedrückt: dein Django Projekt verhält sich im Dev-Server und Produktiv gleich. Whitenoise macht IMHO immer Sinn, wenn man nicht bergeweise hochfrequent statischen Content ausliefern muss, wo dann ein dedizierter Server oder ein CDN sinnvoller ist.

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

Zu "asynchron" noch ein sehr persönlicher Hinweis von mir: Ich finde diesen async Syntax in gefühlt allen Programmiersprachen nicht so schön. Ich bin alt und mag meinen synchronen, sequentiellen Code im Backend. Wenn ich nicht heavy I/O-Load erwarte, mache ich mir darüber gar keine Gedanken. Wenn ich es erwarte, nehme ich trotzdem kein asycn sondern verwende gevent+gunicorn, um das selbe Ergebnis zu haben
Benutzeravatar
noisefloor
User
Beiträge: 4270
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

AFAIK ist das ganze async Zeug in Django auch noch nicht komplett / durchgehend umgesetzt.

Wenn man Django Channels benutzt, ist async und ein ASGI Server halt zwingend. Für „konventionelle“ Sachen kann man IMHO bedenkenlos bei synchron und WSGI bleiben.

Gruss, noisefloor
Benutzeravatar
Dennis89
User
Beiträge: 1652
Registriert: Freitag 11. Dezember 2020, 15:13

Hi,

vielen Dank für die Ausführung. Das mit Gunicorn in Verbindung mit Django ist ja mehr als einfach.
`whitenoise` hört sich gut an, da ich aber VUE verwendet, kann ich das nicht kombinieren. Zumindest kennt VUE `static` nicht und kann auch nicht auf Django "zugreifen". Dann müsste ich mit Django rendern, wenn ich das richtig verstehe.
Ich könnte ja Apache meinen Projektordner, als Arbeitsverzeichnis zuweisen, dann muss ich auch keine Dateien hin und her kopieren. So zumindest meine Gedanken dazu. Werde mich später darum kümmern.

Zum Thema `async`, ehrlicherweise hat es bei meinem vorherigen Projekt so funktioniert und daher ist das jetzt wieder so geworden. Einen hohen Datentransfer habe ich nicht, also so ganz und gar nicht. Könnte es mal ohne versuchen. Gibt es Nachteile, wenn ich `async` verwende?

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
noisefloor
User
Beiträge: 4270
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,
`whitenoise` hört sich gut an, da ich aber VUE verwendet, kann ich das nicht kombinieren.
Da gibt es IMHO keinen Zusammenhang... Whitenoise erweitert Django um die Fähigkeit, statische Dateien (CSS, JS, Bilder usw) direkt aus Django heraus zu liefern. Was Django / WSGI sonst nicht kann bzw. Django nur, wenn der Dev-Server läuft. D.h. wenn du alle statischen Dateien unter z.B. `/static` hast, dann kümmert sich Whitenoise um's ausliefern. Das hat _nicht_ mit den anderen Routen zu tun, die man in den url.py Dateien definiert.
Zumindest kennt VUE `static` nicht
`static` ist doch nur irgendeine URL. Die kannst du absolut eintrage oder in den Templates von Django automatisch von einem Platzhalter in eine relative URL rendern lassen. Die ändert sich ja auch nicht, von daher sollte auch Vue & Co. kein Problem damit haben. Wenn in deiner Applikation sich Vue Bilder oder sonstigen statischen Content von API Endpunkten zieht, dann steht das auch nicht im Konflikt mit Whitenoise.
Gibt es Nachteile, wenn ich `async` verwende?
Nein - aber ggf. auch keine Vorteile. Im Code aus dem Ausgangspost verwendet kein async, Flask kann AFAIK sowieso kein async, dann müsste man Quart nehmen. Wenn man nicht weiß, ob man asynchrone oder synchronen Views und DB Abfrage braucht, dann braucht man in der Regel keine. Mehr Details siehe auch https://docs.djangoproject.com/en/6.0/topics/async/.

Gruß, noisefloor
Benutzeravatar
Dennis89
User
Beiträge: 1652
Registriert: Freitag 11. Dezember 2020, 15:13

Ich muss in meiner *.html Datei folgendes einfügen:

Code: Alles auswählen

{% load static %}
und `static` ist ein Built-in Template von Django und daher kann VUE damit nichts anfangen. So verstehe ich das zumindest bzw. das waren meine Gedankengänge aus denen mein vorheriger Post entstanden ist. Vielleicht habe ich aber auch nicht richtig verstanden, wo und wie ich den `load static` einbinden muss.

Danke für den Link.

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
noisefloor
User
Beiträge: 4270
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

`{% load static %}` ist einfach nur dafür da, dass in Django Template der `static` Platzhalter beim Rendern des HTML Templates richtig aufgelöst wird. Mehr nicht. Man kann die URL für statische Inhalte auch händisch eintragen - ist aber halt ein Haufen Arbeit, alle Templates zu ändern, wenn sich der statische Pfad mal ändern sollten. Siehe auch Django Doku Static Files.

Wie gesagt: Vue "sieht" das nie, weil Vue ja erst im Browser ausgeführt wird, dass HTML Template aber auf dem Server gerendert wird. Und Whitenoise greift sowieso erst, wenn Django einen Request auf die `static" Route bekommt. Dabei ist es total egal, ob der Request auf dem HTML kommt oder sonst wo her.

Gruß, noisefloor
Benutzeravatar
Dennis89
User
Beiträge: 1652
Registriert: Freitag 11. Dezember 2020, 15:13

Hm, ich versuche das noch mal. Bis jetzt habe ich wohl noch einen Gedankenfehler.
Weil du sagst:
dass HTML Template aber auf dem Server gerendert wird
Bei mir ja nicht. Django macht bei mir nichts anders als einen JSON-String anzunehmen, der wird in einer Funktion, die in`views.py` steht, verarbeitet und der Server gibt dann nur einen anderen JSON-String zurück.
Meine *.html, *.js und *.css Dateien "verarbeitet Apache.

Zurzeit sehe ich nicht, wo Django bzw. `whitenose` da irgendwie eingreifen könnte. Werde mich aber noch mal dahinter klemmen, vielleicht macht's auch heute Nacht oder morgen früh mal klick und ich verstehe, was du meintest.

Vielen Dank und Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
sparrow
User
Beiträge: 4614
Registriert: Freitag 17. April 2009, 10:28

@Dennis89: Django gibt das nur zurück, weil du ihm das sagst. Wenn du whitenoise + static aktivierst, kann Django auch auch (relativ performant) andere Dinge ausliefern. Es ist ja deine Konfiguration von Apache, die Zugriffe auf bestimme URLs nicht an Django weiterreicht sondern in Apache verarbeitet.

Wie gesagt, ich verwende kein async, weil man das Verhalten auch problemlos mit gevent erhält. Und gunicorn + django lässt sich super in einen Container packen. Ohne einen mächtigen Apache. Und wer immer die SSL Verbindung terminiert, kann dann einfach auf den gunicorn Port im Container zugreifen.
Gerade wenn man einen flexiblem Monolithen baut (und dafür ist Django perfekt), nimmt man so extrem viel Kompliziertheit raus, was zum Beispiel die Umgebung bei einem Hyperscaler betrifft.
Antworten