Slide 1

Slide 1 text

Django for Data Science Will Vincent Boston Python Meetup (March 2025) Deploying Machine Learning Models with Django

Slide 2

Slide 2 text

Will Vincent ● JetBrains/PyCharm Developer Advocate ● Django Board Member (2020-2022) ● LearnDjango.com ● Books: Django for Beginners/APIs/Professionals ● Django Chat podcast ● Django News newsletter ● Open source: awesome-django, DjangoX

Slide 3

Slide 3 text

Image: AlmaBetter.com Image: AlmaBetter.com

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

What is Data Science?

Slide 6

Slide 6 text

What do Data Scientists Do?

Slide 7

Slide 7 text

Python 2010-Today

Slide 8

Slide 8 text

The Rise of Data Science in Python

Slide 9

Slide 9 text

No content

Slide 10

Slide 10 text

Python Web Frameworks

Slide 11

Slide 11 text

Why Django in 2025?

Slide 12

Slide 12 text

Maths

Slide 13

Slide 13 text

Train a Model

Slide 14

Slide 14 text

Jupyter Notebook - Setup

Slide 15

Slide 15 text

Jupyter Notebook - Layout

Slide 16

Slide 16 text

Iris Dataset

Slide 17

Slide 17 text

Iris Dataset

Slide 18

Slide 18 text

requirements.txt pandas~=2.2.3 scikit-learn~=1.6.1

Slide 19

Slide 19 text

Jupyter Notebook # Load libraries import pandas as pd from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score from sklearn.svm import SVC from joblib import dump, load # Load dataset df = pd.read_csv("data/iris.csv") # Extract features (X) and labels (y) X = df[["SepalLengthCm", "SepalWidthCm", "PetalLengthCm", "PetalWidthCm"]].values y = df["Species"].values # Split into training and testing data X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.20, random_state=1 )

Slide 20

Slide 20 text

Jupyter Notebook # Train the model model = SVC(gamma="auto") model.fit(X_train, y_train) # Make predictions on validation dataset predictions = model.predict(X_test) # Evaluate model accuracy accuracy = accuracy_score(y_test, predictions) print(f"Model Accuracy: {accuracy:.2f}") # Print accuracy score # Save model using joblib dump(model, "models/iris.joblib") # Load model using joblib model = load("models/iris.joblib")

Slide 21

Slide 21 text

# Get input from the user try: sepal_length = float(input("Enter SepalLengthCm: ")) sepal_width = float(input("Enter SepalWidthCm: ")) petal_length = float(input("Enter PetalLengthCm: ")) petal_width = float(input("Enter PetalWidthCm: ")) except ValueError: print("Invalid input. Please enter numeric values.") exit() # Make a prediction input_data = [[sepal_length, sepal_width, petal_length, petal_width]] result = model.predict(input_data) print("Prediction:", result[0]) # Iris Classification with SVM

Slide 22

Slide 22 text

Live Demo?

Slide 23

Slide 23 text

Visualize # requirements.txt pandas~=2.2.3 scikit-learn~=1.6.1 seaborn~=0.13.2 matplotlib~=3.10.0 # Import Seaborn and Matplotlib for visualizations import seaborn as sns import matplotlib.pyplot as plt # Pairplot to visualize relationships between features sns.pairplot(df, hue="Species", diag_kind="kde") plt.show()

Slide 24

Slide 24 text

No content

Slide 25

Slide 25 text

Django Project

Slide 26

Slide 26 text

Gameplan ● Create new Django project ● Load joblib file ● Forms for users to enter predictions -> see results ● Store user info in the database ● Deployment?

Slide 27

Slide 27 text

New Django Project

Slide 28

Slide 28 text

├── django_project │ ├── __init__.py │ ├── asgi.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── manage.py ├── predict │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ │ └── __init__.py │ ├── models.py │ ├── tests.py │ └── views.py └── templates

Slide 29

Slide 29 text

No content

Slide 30

Slide 30 text

Add our ML Model ├── django_project │ ├── __init__.py │ ├── asgi.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── iris.joblib # hi there! ├── manage.py ├── predict │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ │ └── __init__.py │ ├── models.py │ ├── tests.py

Slide 31

Slide 31 text

Django Under The Hood: Part 1

Slide 32

Slide 32 text

Djangoʼs MVT Architecture ● Model (M) --> Handles database interactions and business logic ● View (V) --> Acts as Controller in MVC, process requests and match template ● Template (T) --> Presentation layer, HTML+ CSS + data

Slide 33

Slide 33 text

Django Under The Hood: Part 2

Slide 34

Slide 34 text

URLs # django_project/urls.py from django.contrib import admin from django.urls import path, include # new urlpatterns = [ path("admin/", admin.site.urls), path("", include("predict.urls")), # new ] # predict/urls.py from django.urls import path from .views import predict urlpatterns = [ path("", predict, name="predict"),

Slide 35

Slide 35 text

View + Template # predict/views.py from django.shortcuts import render def predict(request): return render(request, "predict.html") Hello

Slide 36

Slide 36 text

Runserver (again)

Slide 37

Slide 37 text

# predict/views.py import joblib import numpy as np from django.shortcuts import render # Load the trained model model = joblib.load("iris.joblib") def predict(request): prediction = None if request.method == "POST": try: # Get input values from the form sepal_length = float(request.POST.get("sepal_length")) sepal_width = float(request.POST.get("sepal_width")) petal_length = float(request.POST.get("petal_length")) petal_width = float(request.POST.get("petal_width")) # Make a prediction features = np.array( [[sepal_length, sepal_width, petal_length, petal_width]] ) prediction = model.predict(features)[0] except Exception as e: prediction = f"Error: {str(e)}" return render(request, "predict.html", {"prediction": prediction})

Slide 38

Slide 38 text

Iris Prediction body { font-family: Arial, sans-serif; max-width: 400px; margin: 50px auto; text-align: center; } form { display: flex; flex-direction: column; gap: 10px; } input, button { padding: 10px; font-size: 16px; } button { cursor: pointer; background-color: #4CAF50; color: white; border: none; }

Slide 39

Slide 39 text

...

Iris Species Predictor

{% csrf_token %} Predict {% if prediction %}

Prediction: {{ prediction }}

{% endif %}

Slide 40

Slide 40 text

No content

Slide 41

Slide 41 text

No content

Slide 42

Slide 42 text

Store User Inputs # predict/views.py ... def predict(request): prediction = None form_data = {} # To store user inputs if request.method == "POST": try: # Store form inputs form_data = { "sepal_length": request.POST.get("sepal_length"), "sepal_width": request.POST.get("sepal_width"), "petal_length": request.POST.get("petal_length"), "petal_width": request.POST.get("petal_width"), } # Convert inputs to float and make a prediction features = np.array([[float(v) for v in form_data.values()]]) prediction = model.predict(features)[0] except Exception as e: prediction = f"Error: {str(e)}" return render( request, "predict.html", {"prediction": prediction, "form_data": form_data}

Slide 43

Slide 43 text

Display User Inputs {% if prediction %}

Prediction: {{ prediction }}

Inputs:

  • Sepal Length: {{ form_data.sepal_length }}
  • Sepal Width: {{ form_data.sepal_width }}
  • Petal Length: {{ form_data.petal_length }}
  • Petal Width: {{ form_data.petal_width }}
] {% endif %}

Slide 44

Slide 44 text

No content

Slide 45

Slide 45 text

Add a Model to Store Predictions # predict/models.py from django.db import models class IrisPrediction(models.Model): sepal_length = models.FloatField() sepal_width = models.FloatField() petal_length = models.FloatField() petal_width = models.FloatField() prediction = models.CharField(max_length=100) created_at = models.DateTimeField(auto_now_add=True) def __str__(self): return f"{self.prediction} ({self.created_at.strftime('%Y-%m-%d %H:%M:%S')})"

Slide 46

Slide 46 text

Update View to Save Predictions # predict/views.py import joblib import numpy as np from django.shortcuts import render from .models import IrisPrediction. # new # Load the trained model model = joblib.load("iris.joblib") def predict(request): prediction = None form_data = {} if request.method == "POST": try: ... # Save prediction to database IrisPrediction.objects.create( sepal_length=form_data["sepal_length"], sepal_width=form_data["sepal_width"], petal_length=form_data["petal_length"], petal_width=form_data["petal_width"], prediction=prediction, )

Slide 47

Slide 47 text

Update Admin # predict/admin.py from django.contrib import admin from .models import IrisPrediction admin.site.register(IrisPrediction)

Slide 48

Slide 48 text

DjangoForDataScience.com

Slide 49

Slide 49 text

Deployment...

Slide 50

Slide 50 text

Deployment Checklist ● configure static files and install WhiteNoise ● add environment variables with environs ● create a .env file and update the .gitignore file ● update DEBUG, ALLOWED_HOSTS, SECRET_KEY, and CSRF_TRUSTED_ORIGINS ● update DATABASES to run PostgreSQL in production and install psycopg ● install Gunicorn as a production WSGI server ● create a Procfile ● update the requirements.txt file ● create a new Heroku project, push the code to it, and start a dyno web process

Slide 51

Slide 51 text

LLMs...

Slide 52

Slide 52 text

No content

Slide 53

Slide 53 text

LLM Tips 1. Still fancy autocomplete (not AGI) 2. Be aware of training cut-off dates 3. Context is king 4. Prompt tips 5. Vibe coding...

Slide 54

Slide 54 text

No content

Slide 55

Slide 55 text

Takeaways ✅ Django is great for deploying ML models (web UI + APIs) ✅ Iris dataset is simple but powerful for learning ML deployment ✅ Try deploying a real-world ML model

Slide 56

Slide 56 text

Questions? @wsvincent.bsky.social @wsvincent -- django_irisml & iris_ml fosstodon.org/@wsvincent @william-s-vincent

Slide 57

Slide 57 text

Appendix C More Django architecture diagrams...

Slide 58

Slide 58 text

Django Under The Hood: Part 3

Slide 59

Slide 59 text

Django Under The Hood: Part 4

Slide 60

Slide 60 text

Deployment...

Slide 61

Slide 61 text

1a. Static Files (.venv) $ mkdir static (.venv) $ mkdir static/css (.venv) $ mkdir static/js (.venv) $ mkdir static/img (.venv) $ touch static/css/.keep (.venv) $ touch static/js/.keep (.venv) $ touch static/img/.keep # django_project/settings.py STATIC_URL = "static/" STATICFILES_DIRS = [BASE_DIR / "static"] # new

Slide 62

Slide 62 text

1b. Static Files # django_project/settings.py STATIC_URL = "static/" STATICFILES_DIRS = [BASE_DIR / "static"] STATIC_ROOT = BASE_DIR / "staticfiles" # new (.venv) $ python manage.py collectstatic # staticfiles/ └── admin ├── css ├── img ├── js

Slide 63

Slide 63 text

1c. Static Files {% load static %} ...

Slide 64

Slide 64 text

1d. Static Files INSTALLED_APPS = [ ... "whitenoise.runserver_nostatic", # new "django.contrib.staticfiles", "predict", ] MIDDLEWARE = [ "django.middleware.security.SecurityMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "whitenoise.middleware.WhiteNoiseMiddleware", # new ... ] STORAGES = { "default": { "BACKEND": "django.core.files.storage.FileSystemStorage", }, "staticfiles": {

Slide 65

Slide 65 text

2. Environment Variables # django_project/settings.py from pathlib import Path from environs import Env # new env = Env() # new env.read_env() # new # .gitignore .venv/ __pycache__/ db.sqlite3 .env .idea/