How to Build an Online Document Scanner with Python Django and Dynamic Web TWAIN
Django is a popular Python framework for web development. In this tutorial, we will create a web application using Django and Dynamic Web TWAIN.
What you’ll build: A Django web application that acquires documents from a TWAIN-compatible scanner in the browser and uploads the scanned images to the server — powered by the Dynamic Web TWAIN JavaScript SDK.
Key Takeaways
- Dynamic Web TWAIN integrates into a Django project by loading its JavaScript resources as static files and initializing the scanner via
Dynamsoft.DWT.Load(). - Django’s template engine and
{% static %}tag dynamically resolve the SDK’s JS paths, while{% csrf_token %}secures the upload POST request. - Scanned documents are uploaded to the Django back end using
HTTPUploadThroughPostEx, with CSRF protection handled by injecting the token viaSetHTTPFormField. - This approach works on Windows, Linux, and macOS with any TWAIN/WIA/SANE-compatible scanner.
Common Developer Questions
- How do I integrate document scanning into a Django web application?
- How do I upload scanned images from the browser to a Django server with CSRF protection?
- What is the fastest way to add TWAIN scanner support to a Python web project?
This article is Part 4 in a 5-Part Series.
- Part 1 - How to Implement Web Document Scanning in ASP.NET Core MVC with Dynamic Web TWAIN
- Part 2 - How to Upload Scanned Documents in Node.js with Dynamic Web TWAIN
- Part 3 - How to Upload Scanned Documents to a Go Server with Dynamic Web TWAIN
- Part 4 - How to Build an Online Document Scanner with Python Django and Dynamic Web TWAIN
- Part 5 - Integrate a Document Scanner API in a Laravel PHP Project
Prerequisites
- Python
-
Django
python -m pip install Django python -m django --version - A Dynamic Web TWAIN license key. Get a 30-day free trial license for development and testing.
Integration Overview
To integrate Django with Dynamic Web TWAIN, follow these steps:
- Create a Django project.
- Inside the project, create a Django app.
- Develop an HTML template to load the Dynamic Web TWAIN library, utilizing the template syntax to dynamically generate the library path.
- Configure the static resource files’ path.
Create a New Django Project
Open your terminal to initiate a Django project using the following command (applicable to Windows, Linux, and macOS):
python -m django startproject djangodwt
Upon completion, you’ll find the newly created project folder within your working directory.

Next, navigate into the djangodwt directory and launch the app using the command below:
cd djangodwt
python manage.py runserver
Once the server starts successfully, open http://127.0.0.1:8000 in your web browser.

With these steps, you’ve successfully set up a simple Django project.
Integrate Dynamic Web TWAIN into the Django Project
Create the Django App
To develop your web scanner application, you should first create an app within your Django project.
python manage.py startapp dwt
Note: In Django, “project” and “app” have distinct meanings. An app is a web application that performs specific functions, whereas a project is a collection of apps that together comprise a particular website.
After this step, your project structure will look like this:
djangodwt
- djangodwt
- __pycache__
- asgi.py
- settings.py
- urls.py
- wsgi.py
- __init__.py
- dwt
- migrations
- __init__.py
- admin.py
- apps.py
- models.py
- tests.py
- views.py
- __init__.py
- db.sqlite3
- manage.py
Create the Scanner View with a Django Template
To develop our view, we will utilize a template. Conventionally, your template files should reside in {project_folder/templates/{app_named_folder}/}. Let’s create one named index.html.
<!DOCTYPE html>
<head>
<title>Dynamic Web Twain</title>
<meta charset="utf-8">
{% load static %}
{% csrf_token %}
<script type="text/javascript" src="{% static 'dynamsoft.webtwain.initiate.js' %}"></script>
<script type="text/javascript" src="{% static 'dynamsoft.webtwain.config.js' %}"></script>
<script type="text/javascript" src="{% static 'jquery-3.6.0.min.js' %}"></script>
</head>
<body>
<div id="app">
<div id="dwtcontrolContainer"></div>
<button onclick="scan()">Scan</button>
</div>
<script type="text/javascript">
var dwtObjct;
window.onload = function () {
if (Dynamsoft) {
Dynamsoft.DWT.AutoLoad = false;
Dynamsoft.DWT.UseLocalService = true;
Dynamsoft.DWT.Containers = [{ ContainerId: 'dwtcontrolContainer', Width: '640px', Height: '640px' }];
Dynamsoft.DWT.RegisterEvent('OnWebTwainReady', Dynamsoft_OnReady);
// https://www.dynamsoft.com/customer/license/trialLicense/?product=dcv&package=cross-platform
Dynamsoft.DWT.ProductKey = 'LICENSE-KEY';
Dynamsoft.DWT.ResourcesPath = 'static';
Dynamsoft.DWT.Load();
}
};
function Dynamsoft_OnReady() {
dwtObjct = Dynamsoft.DWT.GetWebTwain('dwtcontrolContainer');
}
function scan() {
if (dwtObjct) {
dwtObjct.SelectSourceAsync().then(function () {
return dwtObjct.AcquireImageAsync({
IfCloseSourceAfterAcquire: true // Scanner source will be closed automatically after the scan.
});
}).catch(function (exp) {
alert(exp.message);
});
}
}
</script>
</body>
Note that you’ll need a valid license key. Make sure to replace LICENSE-KEY with your own to activate the scanner API. The ResourcesPath should be the path to the dynamsoft.webtwain.initiate.js and dynamsoft.webtwain.config.js files, which we’ll address later.
Next, let’s edit the file dwt/views.py by adding the following Python code:
from django.http import HttpResponse, request
from django import template
from django.shortcuts import render
import os
def index(request):
return render(request, 'dwt/index.html')
Within the app folder {project_folder}/dwt, we create a file named urls.py and insert the code below:
from django.urls import path
from . import views
urlpatterns = [
path('', views.index, name='index')
]
Then, we navigate to the project’s urls.py file located in {project_folder}/{project_name} and include the newly defined URL rules:
from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('dwt.urls'))
]
Lastly, let’s configure the templates directory in settings.py by specifying the template DIR as follows:
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')], # this field
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
Import Dynamic Web TWAIN Static Resources
It’s now time to integrate Dynamic Web TWAIN’s resource files into this project.
Steps:
- Under the project root, create a
staticfolder. - Within the
staticfolder, create adwtfolder. - From
<Dynamic Web TWAIN SDK PATH>/Resources, copy the resource files to thestatic/directory.
Next, add the following code to settings.py to ensure the static/ directory is accessible:
STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, "static")
]
With these steps, your web document scanning application using Dynamic Web TWAIN and Django should now be operational.


Upload Scanned Documents to the Django Server
Let’s incorporate the front-end code to enable the uploading of scanned documents to the server.
function upload() {
dwtObjct.HTTPPort = 8000;
var CurrentPathName = unescape(location.pathname); // get current PathName in plain ASCII
var CurrentPath = CurrentPathName.substring(0, CurrentPathName.lastIndexOf("/") + 1);
var strActionPage = CurrentPath + "upload";
var strHostIP = "127.0.0.1";
var OnSuccess = function (httpResponse) {
alert("Succesfully uploaded");
};
var OnFailure = function (errorCode, errorString, httpResponse) {
alert(httpResponse);
};
var date = new Date();
var csrftoken = getCookie('csrftoken');
dwtObjct.SetHTTPFormField('csrfmiddlewaretoken', csrftoken);
dwtObjct.HTTPUploadThroughPostEx(
strHostIP,
dwtObjct.CurrentImageIndexInBuffer,
strActionPage,
date.getTime() + ".jpg",
1, // JPEG
OnSuccess, OnFailure
);
}
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie != '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = jQuery.trim(cookies[i]);
if (cookie.substring(0, name.length + 1) == (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
The upload function is responsible for sending the scanned image to the server. It utilizes the HTTPUploadThroughPostEx method for the upload process. The SetHTTPFormField method is designated to set the form field for the CSRF token, while the getCookie function retrieves the CSRF token from the cookie.
On the server side, uploaded documents can be managed in dwt/views.py:
from django.http import HttpResponse, request
from django import template
from django.shortcuts import render
import os
from .models import Image
def index(request):
return render(request, 'dwt/index.html')
def upload(request):
if request.method == 'POST':
image = Image()
image.name = request.FILES['RemoteFile'].name
image.data = request.FILES['RemoteFile']
image.save()
return HttpResponse("Successful")
return HttpResponse("Failed")
def handle_uploaded_file(file, filename):
if not os.path.exists('upload/'):
os.mkdir('upload/')
with open('upload/' + filename, 'wb+') as destination:
for chunk in file.chunks():
destination.write(chunk)
return destination
Common Issues and Edge Cases
- CSRF verification failed (403 Forbidden): Django rejects POST requests without a valid CSRF token. Ensure you include
{% csrf_token %}in your template and pass the token toSetHTTPFormField('csrfmiddlewaretoken', csrftoken)before callingHTTPUploadThroughPostEx. If you still get 403 errors, verify that the cookie domain matches your dev server host. - Scanner not detected in the browser: Dynamic Web TWAIN requires the Dynamsoft Service to be installed locally for TWAIN/WIA/SANE access. If
SelectSourceAsync()returns an empty list, confirm the service is running and that your browser hasn’t blocked the localhost WebSocket connection. - Static files return 404 in development: Django’s development server only serves static files when
DEBUG = TrueandSTATICFILES_DIRSis configured correctly. Double-check that the Dynamic Web TWAIN resource files are inside thestatic/folder and thatSTATIC_URLmatches theResourcesPathvalue set in JavaScript.
Source Code
https://github.com/yushulx/web-twain-document-scan-management/blob/main/examples/python_upload