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

Django, Flask, Bottle, WSGI, CGI…
Antworten
Benutzeravatar
Dennis89
User
Beiträge: 1640
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: 18337
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: 4606
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: 1640
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: 1640
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: 4266
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: 1640
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: 4266
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: 1640
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: 1640
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: 4266
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: 4606
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: 4266
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
Antworten