Inital simple hand-made ver

This commit is contained in:
KenwoodFox 2025-12-03 12:47:17 -05:00
commit 20b5086800
14 changed files with 553 additions and 0 deletions

60
.gitignore vendored Normal file
View File

@ -0,0 +1,60 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# Virtual environments
venv/
env/
ENV/
.venv
# Django
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
/media
/staticfiles
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
.DS_Store
# Environment
.env
.env.local
# Testing
.coverage
.pytest_cache/
htmlcov/
.tox/
# Docker
.dockerignore

11
Dockerfile Normal file
View File

@ -0,0 +1,11 @@
FROM python:3.11-slim
WORKDIR /app
COPY . /app
RUN pip install --upgrade pip
RUN pip install --upgrade gunicorn
RUN pip install --no-cache-dir -r requirements.txt
CMD ["gunicorn", "seduttomachineworks_project.wsgi:application", "--bind", "0.0.0.0:8000"]

12
Pipfile Normal file
View File

@ -0,0 +1,12 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
django = "<5.0,>=4.2"
[dev-packages]
[requires]
python_version = "3.11"

46
Pipfile.lock generated Normal file
View File

@ -0,0 +1,46 @@
{
"_meta": {
"hash": {
"sha256": "bd4cb91114d0c833ac47abdad00410896c666f4553053870c3c7655342c703a0"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.11"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {
"asgiref": {
"hashes": [
"sha256:aef8a81283a34d0ab31630c9b7dfe70c812c95eba78171367ca8745e88124734",
"sha256:d89f2d8cd8b56dada7d52fa7dc8075baa08fb836560710d38c292a7a3f78c04e"
],
"markers": "python_version >= '3.9'",
"version": "==3.10.0"
},
"django": {
"hashes": [
"sha256:9398e487bcb55e3f142cb56d19fbd9a83e15bb03a97edc31f408361ee76d9d7a",
"sha256:c96e64fc3c359d051a6306871bd26243db1bd02317472a62ffdbe6c3cae14280"
],
"index": "pypi",
"markers": "python_version >= '3.8'",
"version": "==4.2.26"
},
"sqlparse": {
"hashes": [
"sha256:09f67787f56a0b16ecdbde1bfc7f5d9c3371ca683cfeaa8e6ff60b4807ec9272",
"sha256:cf2196ed3418f3ba5de6af7e82c694a9fbdbfecccdfc72e281548517081f16ca"
],
"markers": "python_version >= '3.8'",
"version": "==0.5.3"
}
},
"develop": {}
}

24
README.md Normal file
View File

@ -0,0 +1,24 @@
# Qoute Upload Box
More could actually go in here! But for now this is *just* an advanced upload box.
Configurable with some server-side storage (a little) and an email system.
Allows somewhat large uploads, some text for qoutes, drawings, etc. And keeps things
simple-ish!
uses sqlite3 stored alongside the customer files.
## Compose Example
```yaml
services:
web_upload:
volumes:
- /var/seduttomachineworks/data:/app/store
ports:
- "8000:8000"
environment:
- SECRET_KEY=your-very-secret-production-key
- ALLOWED_HOSTS=yourdomain.com,localhost,127.0.0.1
restart: always
```

23
manage.py Normal file
View File

@ -0,0 +1,23 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
"""Run administrative tasks."""
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'seduttomachineworks_project.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()

2
requirements.txt Normal file
View File

@ -0,0 +1,2 @@
Django>=4.2,<5.0

View File

View File

@ -0,0 +1,17 @@
"""
ASGI config for seduttomotorsports project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/
"""
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'seduttomachineworks_project.settings')
application = get_asgi_application()

View File

@ -0,0 +1,100 @@
"""
Django settings for seduttomotorsports project.
"""
from pathlib import Path
import os
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.environ.get("SECRET_KEY", "django-insecure-change-this-in-production")
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = os.environ.get("DEBUG", "True") == "True"
ALLOWED_HOSTS = os.environ.get("ALLOWED_HOSTS", "*").split(",")
# Application definition
INSTALLED_APPS = [
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
]
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]
ROOT_URLCONF = "seduttomachineworks_project.urls"
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [BASE_DIR / "templates"],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.debug",
"django.template.context_processors.request",
"django.contrib.messages.context_processors.messages",
],
},
},
]
WSGI_APPLICATION = "seduttomachineworks_project.wsgi.application"
# Database
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": BASE_DIR / "db.sqlite3",
}
}
# Internationalization
# https://docs.djangoproject.com/en/4.2/topics/i18n/
LANGUAGE_CODE = "en-us"
TIME_ZONE = "UTC"
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.2/howto/static-files/
STATIC_URL = "static/"
STATIC_ROOT = BASE_DIR / "staticfiles"
MEDIA_URL = "media/"
MEDIA_ROOT = BASE_DIR / "media"
# Default primary key field type
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
# Allow iframe embedding (for the quoting upload box)
# Note: We'll handle this per-view using @xframe_options_exempt decorator

View File

@ -0,0 +1,16 @@
"""
URL configuration for seduttomotorsports project.
"""
from django.urls import path
from . import views
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
path('', views.quote_upload, name='quote_upload'),
]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

View File

@ -0,0 +1,11 @@
from django.shortcuts import render
from django.views.decorators.clickjacking import xframe_options_exempt
@xframe_options_exempt
def quote_upload(request):
"""
Simple embeddable quote upload box.
TODO: Implement file upload and email integration
"""
return render(request, 'quote_upload.html')

View File

@ -0,0 +1,17 @@
"""
WSGI config for seduttomotorsports project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/4.2/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'seduttomachineworks_project.settings')
application = get_wsgi_application()

214
templates/quote_upload.html Normal file
View File

@ -0,0 +1,214 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Request a Quote</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html,
body {
height: 100%;
}
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
background: transparent;
}
.upload-box {
width: 100%;
min-height: 100vh;
background: white;
padding: 16px;
display: flex;
flex-direction: column;
}
h2 {
margin-bottom: 20px;
color: #333;
}
.drop-area {
border: 2px dashed #ccc;
border-radius: 4px;
padding: 24px;
text-align: center;
cursor: pointer;
transition: all 0.3s;
}
.drop-area:hover {
border-color: #666;
background: #f9f9f9;
}
.drop-area.dragover {
border-color: #0066cc;
background: #e6f2ff;
}
input[type="file"] {
display: none;
}
.file-list {
margin-top: 20px;
display: none;
}
.file-list.show {
display: block;
}
.file-item {
padding: 10px;
background: #f9f9f9;
margin-bottom: 5px;
border-radius: 4px;
}
button {
margin-top: 20px;
padding: 12px 24px;
background: #0066cc;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
width: 100%;
}
button:hover {
background: #0052a3;
}
button:disabled {
background: #ccc;
cursor: not-allowed;
}
.todo {
margin-top: 20px;
padding: 10px;
background: #fff3cd;
border: 1px solid #ffc107;
border-radius: 4px;
font-size: 14px;
color: #856404;
}
footer {
margin-top: auto;
text-align: center;
font-size: 12px;
color: #888;
}
footer a {
color: #666;
text-decoration: none;
}
footer a:hover {
text-decoration: underline;
}
</style>
</head>
<body>
<div class="upload-box">
<h2>Request a Quote</h2>
<div class="drop-area" id="dropArea">
<p>Drag & drop files here</p>
<p style="font-size: 14px; color: #666; margin-top: 10px;">or click to browse</p>
<input type="file" id="fileInput" multiple>
</div>
<div class="file-list" id="fileList"></div>
<button id="submitBtn" disabled>Submit</button>
<div class="todo">
<strong>TODO:</strong> Implement file upload functionality
</div>
<footer>
Hosted by <a href="https://kitsunehosting.net" target="_blank"
rel="noopener noreferrer">kitsunehosting.net</a>
</footer>
</div>
<script>
const dropArea = document.getElementById('dropArea');
const fileInput = document.getElementById('fileInput');
const fileList = document.getElementById('fileList');
const submitBtn = document.getElementById('submitBtn');
let selectedFiles = [];
dropArea.addEventListener('click', () => fileInput.click());
dropArea.addEventListener('dragover', (e) => {
e.preventDefault();
dropArea.classList.add('dragover');
});
dropArea.addEventListener('dragleave', () => {
dropArea.classList.remove('dragover');
});
dropArea.addEventListener('drop', (e) => {
e.preventDefault();
dropArea.classList.remove('dragover');
handleFiles(e.dataTransfer.files);
});
fileInput.addEventListener('change', (e) => {
handleFiles(e.target.files);
});
function handleFiles(files) {
selectedFiles = Array.from(files);
displayFiles();
submitBtn.disabled = selectedFiles.length === 0;
}
function displayFiles() {
if (selectedFiles.length === 0) {
fileList.classList.remove('show');
return;
}
fileList.classList.add('show');
fileList.innerHTML = selectedFiles.map(file =>
`<div class="file-item">${file.name} (${(file.size / 1024).toFixed(1)} KB)</div>`
).join('');
}
submitBtn.addEventListener('click', () => {
// TODO: Submit files to backend
alert('Upload functionality - TODO');
});
// Attempt to auto-resize the iframe height (if parent allows)
function postHeight() {
const height = document.documentElement.scrollHeight;
try {
parent.postMessage({ type: 'sedutto-ifr-height', height }, '*');
} catch (e) {
// ignore if cross-origin restrictions apply
}
}
window.addEventListener('load', postHeight);
window.addEventListener('resize', postHeight);
const mo = new MutationObserver(postHeight);
mo.observe(document.body, { childList: true, subtree: true });
</script>
</body>
</html>