modified: README.md
modified: app.py
This commit is contained in:
38
README.md
38
README.md
@@ -10,6 +10,44 @@ A reactive POS inventory system for software engineers with a snack addiction. F
|
||||
- **Secure:** Hashed password authentication via Flask-Login.
|
||||
- **On device scanner:** Add and scan products from within your phone!
|
||||
|
||||
## 📦 Building the Desktop App (.exe)
|
||||
|
||||
If you want to run SekiPOS as a standalone Windows application with its own embedded browser window, you need to compile it using PyInstaller.
|
||||
|
||||
### 1. Prerequisites
|
||||
You **must** use a stable Python release like **Python 3.11** or **3.12**. Pre-release versions (like 3.14) will fail to compile the PyWebView C# dependencies.
|
||||
|
||||
Install the required build tools globally for your stable Python version:
|
||||
```powershell
|
||||
py -3.11 -m pip install -r requirements.txt pyinstaller
|
||||
```
|
||||
|
||||
### 2. Prepare `app.py`
|
||||
Before compiling, scroll to the absolute bottom of `app.py` and ensure the standalone runner is active. It should look like this:
|
||||
```python
|
||||
if __name__ == '__main__':
|
||||
init_db()
|
||||
|
||||
# For standalone desktop app with embedded browser, use
|
||||
run_standalone()
|
||||
|
||||
# For docker or traditional server deployment, comment out run_standalone() and uncomment the line below:
|
||||
# socketio.run(app, host='0.0.0.0', port=5000, debug=True)
|
||||
```
|
||||
|
||||
### 3. Compile
|
||||
Run this exact command in your terminal. It includes the hidden SocketIO threads and bundles your web templates:
|
||||
```powershell
|
||||
py -3.11 -m PyInstaller --noconfirm --onedir --windowed --add-data "templates;templates/" --add-data "static;static/" --hidden-import "engineio.async_drivers.threading" --icon "static/favicon.png" app.py
|
||||
```
|
||||
|
||||
|
||||
### 4. Post-Build
|
||||
Your portable app will be generated inside the `dist\app` folder.
|
||||
* To keep your existing inventory, copy your `db/pos_database.db` and `static/cache/` folders from your source code into the new `dist\app` folder before running the `.exe`.
|
||||
|
||||
---
|
||||
|
||||
## 🐳 Docker Deployment (Server)
|
||||
|
||||
Build and run the central inventory server:
|
||||
|
||||
64
app.py
64
app.py
@@ -1,4 +1,5 @@
|
||||
import os
|
||||
import sys
|
||||
import sqlite3
|
||||
import requests
|
||||
from flask import send_file
|
||||
@@ -12,6 +13,8 @@ import uuid
|
||||
from datetime import datetime
|
||||
import zipfile
|
||||
import io
|
||||
import webview
|
||||
import threading
|
||||
|
||||
# from dotenv import load_dotenv
|
||||
|
||||
@@ -20,16 +23,42 @@ import io
|
||||
# MP_ACCESS_TOKEN = os.getenv('MP_ACCESS_TOKEN')
|
||||
# MP_TERMINAL_ID = os.getenv('MP_TERMINAL_ID')
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config['SECRET_KEY'] = 'seki_super_secret_key_99' # Change this if you have actual friends
|
||||
socketio = SocketIO(app, cors_allowed_origins="*")
|
||||
# --- PATH HELPERS FOR PYINSTALLER ---
|
||||
def get_bundled_path(relative_path):
|
||||
"""Path for read-only files packed inside the .exe (templates, static)"""
|
||||
if getattr(sys, 'frozen', False):
|
||||
base_path = sys._MEIPASS
|
||||
else:
|
||||
base_path = os.path.abspath(os.path.dirname(__file__))
|
||||
return os.path.join(base_path, relative_path)
|
||||
|
||||
# Auth Setup
|
||||
def get_persistent_path(relative_path):
|
||||
"""Path for read/write files that must survive reboots (db, cache)"""
|
||||
if getattr(sys, 'frozen', False):
|
||||
base_path = os.path.dirname(sys.executable)
|
||||
else:
|
||||
base_path = os.path.abspath(os.path.dirname(__file__))
|
||||
return os.path.join(base_path, relative_path)
|
||||
|
||||
# --- FLASK INIT ---
|
||||
app = Flask(
|
||||
__name__,
|
||||
template_folder=get_bundled_path('templates'),
|
||||
static_folder=get_bundled_path('static')
|
||||
)
|
||||
app.config['SECRET_KEY'] = 'seki_super_secret_key_99' # Change this if you have actual friends
|
||||
socketio = SocketIO(app, cors_allowed_origins="*", async_mode='threading')
|
||||
|
||||
# --- AUTH SETUP (Do not delete this) ---
|
||||
login_manager = LoginManager(app)
|
||||
login_manager.login_view = 'login'
|
||||
|
||||
DB_FILE = 'db/pos_database.db'
|
||||
CACHE_DIR = 'static/cache'
|
||||
# --- DIRECTORY SETUP ---
|
||||
DB_DIR = get_persistent_path('db')
|
||||
os.makedirs(DB_DIR, exist_ok=True)
|
||||
DB_FILE = os.path.join(DB_DIR, "pos_database.db")
|
||||
|
||||
CACHE_DIR = get_persistent_path(os.path.join('static', 'cache'))
|
||||
os.makedirs(CACHE_DIR, exist_ok=True)
|
||||
|
||||
# --- MODELS ---
|
||||
@@ -753,6 +782,29 @@ def delete_gasto(gasto_id):
|
||||
# })
|
||||
# return jsonify({"status": "ok"}), 200
|
||||
|
||||
def start_server():
|
||||
# Use socketio.run instead of default app.run
|
||||
socketio.run(app, host='127.0.0.1', port=5000)
|
||||
|
||||
def run_standalone():
|
||||
t = threading.Thread(target=start_server)
|
||||
t.daemon = True
|
||||
t.start()
|
||||
|
||||
# GIVE FLASK 2 SECONDS TO BOOT UP BEFORE OPENING THE BROWSER
|
||||
time.sleep(2)
|
||||
|
||||
webview.create_window('SekiPOS', 'http://127.0.0.1:5000', width=1366, height=768, resizable=True, fullscreen=False, min_size=(800, 600), maximized=True)
|
||||
|
||||
# private_mode=False is the magic flag that allows localStorage to survive.
|
||||
# It saves data to %APPDATA%\pywebview on Windows.
|
||||
webview.start(private_mode=False)
|
||||
|
||||
if __name__ == '__main__':
|
||||
init_db()
|
||||
|
||||
# For standalone desktop app with embedded browser, use
|
||||
#run_standalone()
|
||||
|
||||
# For docker or traditional server deployment, comment out run_standalone() and uncomment the line below:
|
||||
socketio.run(app, host='0.0.0.0', port=5000, debug=True)
|
||||
|
||||
45
app.spec
Normal file
45
app.spec
Normal file
@@ -0,0 +1,45 @@
|
||||
# -*- mode: python ; coding: utf-8 -*-
|
||||
|
||||
|
||||
a = Analysis(
|
||||
['app.py'],
|
||||
pathex=[],
|
||||
binaries=[],
|
||||
datas=[('templates', 'templates/'), ('static', 'static/')],
|
||||
hiddenimports=['engineio.async_drivers.threading'],
|
||||
hookspath=[],
|
||||
hooksconfig={},
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
noarchive=False,
|
||||
optimize=0,
|
||||
)
|
||||
pyz = PYZ(a.pure)
|
||||
|
||||
exe = EXE(
|
||||
pyz,
|
||||
a.scripts,
|
||||
[],
|
||||
exclude_binaries=True,
|
||||
name='app',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
console=True,
|
||||
disable_windowed_traceback=False,
|
||||
argv_emulation=False,
|
||||
target_arch=None,
|
||||
codesign_identity=None,
|
||||
entitlements_file=None,
|
||||
icon=['static\\favicon.png'],
|
||||
)
|
||||
coll = COLLECT(
|
||||
exe,
|
||||
a.binaries,
|
||||
a.datas,
|
||||
strip=False,
|
||||
upx=True,
|
||||
upx_exclude=[],
|
||||
name='app',
|
||||
)
|
||||
2
build/.gitignore
vendored
Normal file
2
build/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
||||
2
dist/.gitignore
vendored
Normal file
2
dist/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
||||
Reference in New Issue
Block a user