Compare commits

..

6 Commits

12 changed files with 1670 additions and 113 deletions

View File

@ -11,5 +11,5 @@ router.register(r'products', GtinProductViewSet)
urlpatterns = [ urlpatterns = [
path('', include(router.urls)), path('', include(router.urls)),
path('api-auth/', include('rest_framework.urls', namespace='rest_framework')) path('auth/', include('rest_framework.urls', namespace='rest_framework'))
] ]

View File

@ -1,6 +1,9 @@
from django.shortcuts import render from rest_framework import viewsets, status
from rest_framework import viewsets
from rest_framework import permissions from rest_framework import permissions
from rest_framework.decorators import action
from rest_framework.response import Response
import asset.gtin_service
from api.serializers import ContainerSerializer, ContainerTypeSerializer, AssetSerializer, GtinProductSerializer from api.serializers import ContainerSerializer, ContainerTypeSerializer, AssetSerializer, GtinProductSerializer
from container.models import Container, ContainerType from container.models import Container, ContainerType
from asset.models import Asset, GtinProduct from asset.models import Asset, GtinProduct
@ -36,7 +39,6 @@ class AssetViewSet(viewsets.ModelViewSet):
filterset_fields = ['named_id', 'quantity'] filterset_fields = ['named_id', 'quantity']
class GtinProductViewSet(viewsets.ModelViewSet): class GtinProductViewSet(viewsets.ModelViewSet):
""" """
API endpoint that allows containers to be viewed or edited. API endpoint that allows containers to be viewed or edited.
@ -46,3 +48,25 @@ class GtinProductViewSet(viewsets.ModelViewSet):
permission_classes = [permissions.IsAuthenticated] permission_classes = [permissions.IsAuthenticated]
filterset_fields = ['gtin'] filterset_fields = ['gtin']
def retrieve(self, request, pk=None):
if pk:
item = GtinProduct.objects.get(id=pk)
serializer = GtinProductSerializer(item)
return Response({"status": "success", "data": serializer.data}, status=status.HTTP_200_OK)
items = GtinProduct.objects.all()
serializer = GtinProductSerializer(items, many=True)
return Response({"status": "success", "data": serializer.data}, status=status.HTTP_200_OK)
@action(detail=True, methods=['GET'])
def by_gtin(self, request, gtin=None):
if gtin:
item = asset.gtin_service.get_by_gtin(gtin=gtin)
if item:
serializer = GtinProductSerializer(item)
return Response({"status": "success", "data": serializer.data}, status=status.HTTP_200_OK)
return Response(
{"status": "failed", "reason": "Could not find a product for given GTIN", "gtin": gtin},
status=status.HTTP_404_NOT_FOUND)
return Response({"status": "failed", "reason": "No GTIN given, please provide a valid GTIN"},
status=status.HTTP_400_BAD_REQUEST)

View File

@ -0,0 +1,226 @@
{% extends "base.html" %}
{% load static %}
{% block additional_css %}
<link rel="preload" as="style" onload="this.rel='stylesheet';this.onload=null" href="https://unpkg.com/normalize.css@8.0.0/normalize.css">
<link rel="preload" as="style" onload="this.rel='stylesheet';this.onload=null" href="https://unpkg.com/milligram@1.3.0/dist/milligram.min.css">
{% endblock %}
{% block title %}Smartphone Scanner{% endblock %}
{% block content %}
<div class="container" >
<div class="row-mb8" id="video-button-content">
<a class="button" id="startButton">Start</a>
<a class="button-outline" id="resetButton">Reset</a>
</div>
<div class="row" id="video-content">
<div class="col-6">
<video id="video" width="450" height="320" style="border: 1px solid gray"></video>
</div>
<div class="col-2" id="sourceSelectPanel" style="display:none">
<label for="sourceSelect">Change video source:</label>
<select id="sourceSelect" style="max-width:400px">
</select>
</div>
<div class="col-2">
<label>Result:</label>
<pre><code id="result"></code></pre>
<ul id="barcode-list"></ul>
</div>
</div>
</div>
<script type="text/javascript">
function beep() {
var audio = new Audio("{% static 'sound/beep.mp3' %}");
audio.volume = 0.1
console.log("Audio play {% static 'sound/beep.mp3' %}")
audio.play();
}
// check compatibility
if (!('BarcodeDetector' in window)) {
console.log('Barcode Detector is not supported by this browser.');
} else {
console.log('Barcode Detector supported!');
// create new detector
// var barcodeDetector = new BarcodeDetector({formats: ['code_128', 'code_39', 'code_93', 'codabar', 'data_matrix', 'ean_13', 'ean_8', 'qr_code', 'upc_a', 'upc_e']});
var barcodeDetector = new BarcodeDetector({formats: ['code_128', 'ean_13']});
console.log('Supported code formats:');
BarcodeDetector.getSupportedFormats().then(supportedFormats => {
supportedFormats.forEach(format => console.log(format));
});
console.log('end-of-format-list');
}
async function detect() {
const mediaStream = await navigator.mediaDevices.getUserMedia({
video: {facingMode: 'environment'}
});
const video = document.getElementById('video');
// const video = document.createElement('video');
let itemsFound = [];
video.srcObject = mediaStream;
video.autoplay = true;
const list = document.getElementById('barcode-list');
// list.before(video);
function render() {
barcodeDetector
.detect(video)
.then(barcodes => {
barcodes.forEach(barcode => {
if (!itemsFound.includes(barcode.rawValue)) {
itemsFound.push(barcode.rawValue);
const li = document.createElement('li');
li.innerHTML = barcode.rawValue;
list.appendChild(li);
console.log('Got scan ' + barcode.rawValue);
}
});
// beep();
})
.catch(console.error);
}
(function renderLoop() {
requestAnimationFrame(renderLoop);
render();
})();
}
window.addEventListener('load', function () {
detect();
document.getElementById('resetButton').addEventListener('click', () => {
document.getElementById('result').textContent = '';
codeReader.reset();
console.log('Reset.')
});
});
</script>
<hr />
<div class="row mb-3">
<div class="col-8">
<div class="input-group">
<input class="form-control" type="text" maxlength="40" id="container_id" placeholder="Container ID">
<input class="form-control" type="text" maxlength="40" id="asset_id" placeholder="Asset ID">
</div>
</div>
<div class="col-2">
<div class="input-group">
<input class="btn btn-primary" id="book-in-button" type="button" value="+Book">
<input class="btn btn-primary" id="book-out-button" type="button" value="-Book">
</div>
</div>
<div class="col-8">
<div class="input-group">
<textarea class="form-control" readonly="readonly" rows="4" id="container_details"></textarea>
<textarea class="form-control" readonly="readonly" rows="4" id="asset_details"></textarea>
</div>
</div>
</div>
<div class="row">
<div class="input-group">
<textarea class="form-control mb-2" id="chat-log" cols="100" rows="10"></textarea>
<input class="btn btn-secondary" id="clear-chat-area" type="button" value="Clear">
</div>
</div>
<script>
var containerIsValid = false;
var assetIsValid = false;
async function getContainerInfo(containerId) {
document.querySelector('#container_id').value = containerId;
url = '/api/containers?named_id=' + containerId;
document.querySelector('#container_details').value = 'Trying ' + url;
const response = await fetch(url);
const container = await response.json();
if (container.count >= 1) {
document.querySelector('#container_details').value = container.results[0].description;
containerIsValid = true;
} else {
document.querySelector('#container_details').value = document.querySelector('#container_details').value + "\nERROR: No result\n";
console.log(response);
containerIsValid = false;
}
}
async function getAssetInfo(assetId) {
document.querySelector('#asset_id').value = assetId;
url = '/api/assets?named_id=' + assetId;
document.querySelector('#asset_details').value = 'Trying ' + url;
const response = await fetch(url);
const asset = await response.json();
if (asset.count >= 1) {
document.querySelector('#asset_details').value = asset.results[0].description;
assetIsValid = true;
} else {
document.querySelector('#asset_details').value = document.querySelector('#asset_details').value + "\nERROR: No result\n";
console.log(response);
assetIsValid = false;
}
}
function useScannedText(msg) {
// const data = JSON.parse(e.data);
// msg = String(data.message);
document.querySelector('#chat-log').value += (msg + '\n');
if (msg.startsWith('C-')) {
getContainerInfo(data.message);
// document.querySelector('#container_id').value = msg;
} else if (msg.startsWith('A-')) {
getAssetInfo(data.message);
// document.querySelector('#asset_id').value = msg;
} else {
assetId = 'A-' + msg;
document.querySelector('#asset_id').value = assetId;
getAssetInfo(assetId);
}
};
document.querySelector('#clear-chat-area').onclick = function(e) {
document.querySelector('#chat-log').value = "cleared\n";
}
document.querySelector('#book-in-button').onclick = function(e) {
if (!containerIsValid) {
msg = 'Cannot book-in as container is invalid';
console.log(msg);
document.querySelector('#chat-log').value += ('ERROR: ' + msg + '\n');
return;
}
if (!assetIsValid) {
msg = 'Cannot book-in as asset is invalid';
console.log(msg);
document.querySelector('#chat-log').value += ('ERROR: ' + msg + '\n');
return;
}
}
document.querySelector('#book-out-button').onclick = function(e) {
if (!containerIsValid) {
msg = 'Cannot book-out as container is invalid';
console.log(msg);
document.querySelector('#chat-log').value += ('ERROR: ' + msg + '\n');
return;
}
if (!assetIsValid) {
msg = 'Cannot book-out as asset is invalid';
console.log(msg);
document.querySelector('#chat-log').value += ('ERROR: ' + msg + '\n');
return;
}
}
</script>
{% endblock %}

View File

@ -56,7 +56,8 @@
</div> </div>
<div class="row"> <div class="row">
<h1>Use your smartphone</h1> <h1>Use your smartphone</h1>
<p>Use your <a class="navbar-brand" href="{% url 'booker:smartphone_scanner' %}">smartphone</a> to scan.</p> <p>Use your <a href="{% url 'booker:smartphone_scanner' %}">smartphone</a> to scan.</p>
<p>Use your <a href="{% url 'booker:chrome_scanner' %}">chrome Barcode-Scanner to scan.</p>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -2,118 +2,223 @@
{% load static %} {% load static %}
{% block additional_css %} {% block additional_css %}
<link rel="preload" as="style" onload="this.rel='stylesheet';this.onload=null" href="https://unpkg.com/normalize.css@8.0.0/normalize.css">
<link rel="preload" as="style" onload="this.rel='stylesheet';this.onload=null" href="https://unpkg.com/milligram@1.3.0/dist/milligram.min.css">
{% endblock %} {% endblock %}
{% block title %}Smartphone Scanner{% endblock %} {% block title %}Smartphone Scanner{% endblock %}
{% block content %} {% block content %}
<div class="container" > <style>
#interactive canvas { position: absolute; left: 0; }
</style>
<div class="row-mb8" id="video-button-content"> <div class="container" >
<a class="button" id="startButton">Start</a> <div class="row" id="video-button-content">
<a class="button-outline" id="resetButton">Reset</a> <div class="col-1">
<a class="btn btn-primary" id="startButton">Start</a>
</div>
<div class="col-1">
<a class="btn btn-outline-primary" id="resetButton">Reset</a>
</div>
<div class="col-1">
<a class="btn btn-outline-primary" id="clearOverlayButton">Clear</a>
</div>
</div> </div>
<div class="row" id="video-content"> <div class="row" id="video-content">
<div class="col-6"> <div id="interactive" class="viewport col-12" style="position: relative;">
<video id="video" width="450" height="320" style="border: 1px solid gray"></video> <!-- QuaggaJS will populate this element -->
</div> </div>
<!--
<div class="col-2" id="sourceSelectPanel" style="display:none"> <div class="col-8">
<label for="sourceSelect">Change video source:</label> <video id="video" autoplay="true" width="640" height="480" style="border: 1px solid gray"></video>
<select id="sourceSelect" style="max-width:400px"> </div>-->
</select> </div>
<div class="row" id="control-content">
<div class="col-6" id="sourceSelectPanel" style="display:none">
<div>
<label for="sourceSelect">Change video source:</label>
<select id="sourceSelect" style="max-width:400px"></select>
</div>
</div> </div>
<div class="col-6" id="scan-result-panel" style="display:none">
<div class="col-2"> <div>
<label>Result:</label> <label>Scan:</label>
<pre><code id="result"></code></pre> <pre><code id="result"></code></pre>
</div>
</div> </div>
</div> </div>
</div> </div>
<script type="text/javascript" src="https://unpkg.com/@zxing/library@latest"></script>
<script type="text/javascript"> <script type="text/javascript" src="{% static 'quagga/dist/quagga.js' %}"></script>
function beep() { <script type="text/javascript">
var audio = new Audio("{% static 'sound/beep.mp3' %}"); function beep() {
audio.volume = 0.1 var audio = new Audio("{% static 'sound/beep.mp3' %}");
console.log("Audio play {% static 'sound/beep.mp3' %}") audio.volume = 0.02;
audio.play(); console.log("Audio play {% static 'sound/beep.mp3' %}");
audio.play();
}
function clearCanvas() {
canvas = document.querySelector('#interactive .drawingBuffer');
drawing = (canvas != null) ? canvas.getContext('2d') : null;
console.log('Clearing canvas, drawing=' + drawing + ', canvas=' + canvas);
if (drawing != null) {
drawing.clearRect(0, 0, canvas.width, canvas.height);
console.log('Clearing done');
}
}
console.log('detected HW concurrency: ' + self.navigator.hardwareConcurrency);
window.addEventListener('load', function () {
let selectedDeviceId;
let lastScanResult = '';
let config = {
inputStream: {
name: "Live",
type: "LiveStream",
constraints: {
facingMode: "environment",
},
target: document.getElementById('interactive'),
/*area: {
top: '0%',
bottom: '0%',
left: '0%',
right: '0%'
},*/
},
//numOfWorkers: 4,
locate: true,
debug: true,
frequency: 20,
decoder: {
readers: ["code_128_reader"],
debug: {
drawScanline: false,
drawBoundingBox: false,
showPattern: false,
showFrequency: false
},
multiple: false
},
locator: {
halfSample: false,
patchSize: "x-large", // x-small, small, medium, large, x-large
debug: {
/*showCanvas: true,*/
showPatches: false,
showFoundPatches: false,
showSkeleton: false,
showLabels: false,
showPatchLabels: false,
showRemainingPatchLabels: false,
boxFromPatches: {
showTransformed: false,
showTransformedBox: false,
showBB: true
}
}
}
};
function quagga_init() {
Quagga.init(config, function(err) {
if (err) {
console.log(err);
return;
}
console.log("Initialization finished. Ready to start");
Quagga.start();
Quagga.onDetected(function(data) {
code = data.codeResult.code;
console.log('Got scan: ' + code);
if (code && lastScanResult !== code) {
lastScanResult = code;
console.log('Got scan (cont.): ' + lastScanResult);
document.querySelector('#result').textContent = lastScanResult;
// document.getElementById('result').textContent = lastScanResult
beep();
useScannedText(lastScanResult);
console.log(data);
canvas = document.querySelector('#interactive .drawingBuffer');
drawing = canvas.getContext('2d');
drawing.clearRect(0, 0, canvas.width, canvas.height);
drawing.beginPath();
drawing.moveTo(data.line[0].x, data.line[0].y);
drawing.lineTo(data.line[1].x, data.line[1].y);
drawing.lineWidth = 2;
drawing.strokeStyle = 'yellow';
drawing.closePath();
drawing.stroke();
setTimeout(clearCanvas, 3000);
} else if (code) {
console.log('... scan again');
}
});
/*
Quagga.onProcessed(function(data) {
canvas = document.querySelector('#interactive .drawingBuffer');
if (canvas != null) {
context = canvas.getContext('2d');
if (context) {
context.clearRect(0, 0, canvas.width, canvas.height);
}
}
});*/
});
} }
window.addEventListener('load', function () { quagga_init();
let selectedDeviceId;
let lastScanResult = '';
// const codeReader = new ZXing.BrowserBarcodeReader()
const codeReader = new ZXing.BrowserMultiFormatReader()
console.log('ZXing code reader initialized')
codeReader.getVideoInputDevices()
.then((videoInputDevices) => {
const sourceSelect = document.getElementById('sourceSelect')
selectedDeviceId = videoInputDevices[0].deviceId
if (videoInputDevices.length > 1) {
videoInputDevices.forEach((element) => {
const sourceOption = document.createElement('option')
sourceOption.text = element.label
sourceOption.value = element.deviceId
sourceSelect.appendChild(sourceOption)
})
sourceSelect.onchange = () => { if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) {
selectedDeviceId = sourceSelect.value; console.log("enumerateDevices() not supported.");
} return;
}
const sourceSelectPanel = document.getElementById('sourceSelectPanel') Quagga.CameraAccess.enumerateVideoDevices()
sourceSelectPanel.style.display = 'block' .then(function(devices) {
const sourceSelect = document.getElementById('sourceSelect');
selectedDeviceId = devices[0].deviceId; // default
if (devices.length > 1) {
devices.forEach(function(device) {
console.log(device.kind + ": " + device.label + " id=" + device.deviceId);
const sourceOption = document.createElement('option');
sourceOption.text = device.label;
sourceOption.value = device.deviceId;
sourceSelect.appendChild(sourceOption);
});
sourceSelect.onchange = () => {
selectedDeviceId = sourceSelect.value;
config.inputStream.constraints.deviceId = selectedDeviceId;
Quagga.stop();
quagga_init();
} }
const sourceSelectPanel = document.getElementById('sourceSelectPanel');
document.getElementById('startButton').addEventListener('click', () => { sourceSelectPanel.style.display = 'block';
beep() }
codeReader.decodeFromInputVideoDeviceContinuously(selectedDeviceId, 'video', (result) => { document.getElementById('startButton').addEventListener('click', () => {
if (result && lastScanResult !== result.text) { beep();
lastScanResult = result.text Quagga.start();
console.log('Got scan (cont.): ' + result) console.log(`Started scan from camera with id ${selectedDeviceId}`)
document.getElementById('result').textContent = result.text
beep()
useScannedText(result.text)
} else if (result) {
console.log('... scan again')
}
});
/*
codeReader.decodeOnceFromVideoDevice(selectedDeviceId, 'video').then((result) => {
if (lastScanResult !== result.text) {
lastScanResult = result.text
console.log('Got scan: ' + result.text)
document.getElementById('result').textContent = result.text
useScannedText(result.text)
} else {
console.log('... scan again')
}
}).catch((err) => {
console.error(err)
document.getElementById('result').textContent = err
})
*/
console.log(`Started continous decode from camera with id ${selectedDeviceId}`)
})
document.getElementById('resetButton').addEventListener('click', () => {
document.getElementById('result').textContent = '';
codeReader.reset();
console.log('Reset.')
})
}) })
.catch((err) => {
console.error(err)
})
})
</script>
document.getElementById('resetButton').addEventListener('click', () => {
document.getElementById('result').textContent = '';
Quagga.stop();
console.log('Reset/stopped.')
})
document.getElementById('clearOverlayButton').addEventListener('click', () => {
clearCanvas();
})
})
.catch(function(err) {
console.log(err);
});
});
</script>
<hr /> <hr />
<div class="row mb-3"> <div class="row mb-3">
<div class="col-8"> <div class="col-8">
@ -145,14 +250,22 @@
var containerIsValid = false; var containerIsValid = false;
var assetIsValid = false; var assetIsValid = false;
async function getObjectInfo(url) {
const response = await fetch(url);
const object_json = response.json();
return object_json;
}
async function getContainerInfo(containerId) { async function getContainerInfo(containerId) {
document.querySelector('#container_id').value = containerId; document.querySelector('#container_id').value = containerId;
url = '/api/containers?named_id=' + containerId; url = '/api/containers?named_id=' + containerId;
document.querySelector('#container_details').value = 'Trying ' + url; document.querySelector('#container_details').value = 'Trying ' + url;
const response = await fetch(url); const container = await getObjectInfo(url);
const container = await response.json();
if (container.count >= 1) { if (container.count >= 1) {
document.querySelector('#container_details').value = container.results[0].description; c = container.results[0];
console.log(c);
containerType = await getObjectInfo(c.container_type);
document.querySelector('#container_details').value = c.description + "\n" + c.named_id + ': ' + c.color + "\n" + containerType.description;
containerIsValid = true; containerIsValid = true;
} else { } else {
document.querySelector('#container_details').value = document.querySelector('#container_details').value + "\nERROR: No result\n"; document.querySelector('#container_details').value = document.querySelector('#container_details').value + "\nERROR: No result\n";
@ -178,15 +291,11 @@
} }
function useScannedText(msg) { function useScannedText(msg) {
// const data = JSON.parse(e.data);
// msg = String(data.message);
document.querySelector('#chat-log').value += (msg + '\n'); document.querySelector('#chat-log').value += (msg + '\n');
if (msg.startsWith('C-')) { if (msg.startsWith('C-')) {
getContainerInfo(data.message); getContainerInfo(msg);
// document.querySelector('#container_id').value = msg;
} else if (msg.startsWith('A-')) { } else if (msg.startsWith('A-')) {
getAssetInfo(data.message); getAssetInfo(msg);
// document.querySelector('#asset_id').value = msg;
} else { } else {
assetId = 'A-' + msg; assetId = 'A-' + msg;
document.querySelector('#asset_id').value = assetId; document.querySelector('#asset_id').value = assetId;

View File

@ -6,5 +6,7 @@ app_name = 'booker'
urlpatterns = [ urlpatterns = [
path('', views.IndexView.as_view(), name='index'), path('', views.IndexView.as_view(), name='index'),
path('smartphone_scanner/', views.SmartphoneScannerView.as_view(), name='smartphone_scanner'), path('smartphone_scanner/', views.SmartphoneScannerView.as_view(), name='smartphone_scanner'),
path('<str:scanner_id>/', views.scanner, name='scanner') path('chrome_scanner/', views.ChromeScannerView.as_view(), name='chrome_scanner'),
path('<str:scanner_id>/', views.scanner_view, name='scanner'),
path('webscanner/<str:scanner_id>/', views.web_scanner_view, name='web_scanner'),
] ]

View File

@ -1,6 +1,18 @@
from datetime import datetime
from channels.layers import get_channel_layer
from django.shortcuts import render from django.shortcuts import render
from django.views.decorators.csrf import csrf_exempt
from django.views.generic import ListView, TemplateView from django.views.generic import ListView, TemplateView
from gmqtt import Client
from rest_framework.utils import json
from homelog import settings
from homelog.system_user import get_system_user
from .models import Scanner from .models import Scanner
from django.http import HttpResponse
import logging
LOGGER = logging.getLogger(__name__)
class IndexView(ListView): class IndexView(ListView):
@ -8,10 +20,53 @@ class IndexView(ListView):
template_name = 'booker/index.html' template_name = 'booker/index.html'
def scanner(request, scanner_id): def scanner_view(request, scanner_id):
return render(request, 'booker/scanner.html', { return render(request, 'booker/scanner.html', {
'scanner_id': scanner_id 'scanner_id': scanner_id
}) })
class SmartphoneScannerView(TemplateView): class SmartphoneScannerView(TemplateView):
template_name = 'booker/smartphone_scanner.html' template_name = 'booker/smartphone_scanner.html'
class ChromeScannerView(TemplateView):
template_name = 'booker/chrome_scanner.html'
@csrf_exempt
def web_scanner_view(request, scanner_id):
def get_or_create_scanner(named_id):
scanner, created = Scanner.objects.get_or_create(named_id=named_id, defaults={
# 'lwt_topic': topic,
'description': 'WebAPI scanner',
'last_online_ts': datetime.now(),
'created_by': get_system_user(),
'changed_by': get_system_user()
})
if created:
LOGGER.debug(f"Created new scanner entry for {named_id}")
else:
LOGGER.debug(f"Updated scanner entry for {named_id}")
return scanner
print(f"Got {request.POST['scan']} from scanner {scanner_id}")
get_or_create_scanner(scanner_id)
topic = f"barcodescanner/{scanner_id}/scan"
'''
client = Client('homelog-webscanner')
client.set_auth_credentials(username=settings.MQTT_USER, password=settings.MQTT_PASSWORD)
client.connect(settings.MQTT_HOST, 1883, keepalive=60, version=settings.MQTT_VERSION)
client.publish(topic, request.POST['scan'])
client.disconnect()
# MqttConsumer().mqtt_publish({'topic': topic, 'payload': request.POST['scan'], 'retain': False})
'''
channel_layer = get_channel_layer()
channel_layer.group_send(
'mqtt', # scanner_id,
{"type": "distribute", "text": json.dumps({'message': request.POST['scan'], 'topic': topic})}
)
'''
'''
return HttpResponse(f"<html><body><p>OK, got {request.POST['scan']} from {scanner_id}</p></body></html>")

View File

@ -26,7 +26,7 @@ SECRET_KEY = 'django-insecure-!&87^_vnfn^am1f!w7-a)rrxcx(&5i7)h=bcl4)$8882c2-$na
# SECURITY WARNING: don't run with debug turned on in production! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True DEBUG = True
ALLOWED_HOSTS = [] ALLOWED_HOSTS = ['192.168.89.45', '127.0.0.1', 'localhost']
# Application definition # Application definition
@ -38,6 +38,7 @@ INSTALLED_APPS = [
'booker', 'booker',
'api', 'api',
'rest_framework', 'rest_framework',
'django_filters',
'django.contrib.admin', 'django.contrib.admin',
'django.contrib.auth', 'django.contrib.auth',
'django.contrib.contenttypes', 'django.contrib.contenttypes',
@ -186,5 +187,6 @@ REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': [ 'DEFAULT_RENDERER_CLASSES': [
'rest_framework.renderers.JSONRenderer', 'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.BrowsableAPIRenderer', 'rest_framework.renderers.BrowsableAPIRenderer',
] ],
'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend']
} }

View File

@ -1,6 +1,6 @@
{% load static i18n %}<!DOCTYPE html> {% load static i18n %}<!DOCTYPE html>
{% get_current_language as LANGUAGE_CODE %} {% get_current_language as LANGUAGE_CODE %}
<html lang="{{ LANGUAGE_CODE }}"> <html lang="{{ LANGUAGE_CODE }}" xmlns="http://www.w3.org/1999/html">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge"> <meta http-equiv="x-ua-compatible" content="ie=edge">
@ -13,7 +13,7 @@
{% block css %} {% block css %}
<!-- Latest compiled and minified Bootstrap CSS --> <!-- Latest compiled and minified Bootstrap CSS -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.1.3/css/bootstrap.min.css" integrity="sha512-GQGU0fMMi238uA+a/bdWJfpUGKUkBdgfFdgBm72SUQ6BeyWjoY/ton0tEjH+OSH9iP4Dfh+7HM0I9f5eR0L/4w==" crossorigin="anonymous" referrerpolicy="no-referrer" /> <link rel="stylesheet" href="{% static 'bootstrap/dist/css/bootstrap.min.css' %}" />
<!-- Your stuff: Third-party CSS libraries go here --> <!-- Your stuff: Third-party CSS libraries go here -->
<!-- This file stores project-specific CSS --> <!-- This file stores project-specific CSS -->
@ -26,7 +26,7 @@
{# Placed at the top of the document so pages load faster with defer #} {# Placed at the top of the document so pages load faster with defer #}
{% block javascript %} {% block javascript %}
<!-- Bootstrap JS --> <!-- Bootstrap JS -->
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.1.3/js/bootstrap.min.js" integrity="sha512-OvBgP9A2JBgiRad/mM36mkzXSXaJE9BEIENnVEmeZdITvwT09xnxLtT4twkCa8m/loMbPHsvPl0T8lRGVBwjlQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script> <script defer src="{% static 'bootstrap/dist/js/bootstrap.min.js' %}" ></script>
<!-- Your stuff: Third-party javascript libraries go here --> <!-- Your stuff: Third-party javascript libraries go here -->
<!-- place project specific Javascript in this file --> <!-- place project specific Javascript in this file -->

1092
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

18
package.json Normal file
View File

@ -0,0 +1,18 @@
{
"name": "homelog",
"version": "1.0.0",
"description": "Home Logistics -- manage assets and containers and their locations",
"main": "index.js",
"directories": {
"doc": "doc"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Dirk Jahnke",
"license": "ISC",
"dependencies": {
"bootstrap": "^5.1.3",
"quagga": "^0.12.1"
}
}

28
requirements.txt Normal file
View File

@ -0,0 +1,28 @@
asgiref==3.5.2
attrs==21.4.0
autobahn==22.4.2
Automat==20.2.0
cffi==1.15.0
channels==3.0.4
constantly==15.1.0
cryptography==37.0.2
daphne==3.0.2
Django==4.0.4
django-filter==21.1
django-npm==1.0.0
djangorestframework==3.13.1
hyperlink==21.0.0
idna==3.3
incremental==21.3.0
pyasn1==0.4.8
pyasn1-modules==0.2.8
pycparser==2.21
pyOpenSSL==22.0.0
pytz==2022.1
service-identity==21.1.0
six==1.16.0
sqlparse==0.4.2
Twisted==22.4.0
txaio==22.2.1
typing_extensions==4.2.0
zope.interface==5.4.0