company / app.py
noranisa's picture
Update app.py
23ac9de verified
import os
import base64
import io
from functools import wraps
from datetime import datetime, date, timedelta
import qrcode
from flask import Flask, render_template, request, Response, url_for, send_from_directory
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.orm import joinedload
from flask_admin import Admin, AdminIndexView, expose
from flask_admin.contrib.sqla import ModelView
from flask_admin.form.upload import ImageUploadField
from sqlalchemy import func
# --- KONFIGURASI APLIKASI ---
app = Flask(__name__)
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'default-super-secret-key-for-local-dev')
# --- KONFIGURASI PATH PENYIMPANAN SEMENTARA ---
DATA_DIR = '/tmp'
UPLOAD_DIR = os.path.join(DATA_DIR, 'uploads')
DB_PATH = os.path.join(DATA_DIR, 'database.db')
os.makedirs(UPLOAD_DIR, exist_ok=True)
# --- KONFIGURASI DATABASE ---
app.config['SQLALCHEMY_DATABASE_URI'] = f'sqlite:///{DB_PATH}'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
# --- MODEL DATABASE ---
class Category(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), unique=True, nullable=False)
products = db.relationship('Product', back_populates='category', lazy=True)
def __repr__(self):
return self.name
class Product(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), unique=True, nullable=False)
description = db.Column(db.Text, nullable=False)
price = db.Column(db.String(50), nullable=False)
image = db.Column(db.String(100), nullable=True)
category_id = db.Column(db.Integer, db.ForeignKey('category.id'), nullable=False)
category = db.relationship('Category', back_populates='products')
def __repr__(self):
return f'<Product {self.name}>'
class Setting(db.Model):
id = db.Column(db.Integer, primary_key=True)
key = db.Column(db.String(50), unique=True, nullable=False)
value = db.Column(db.Text, nullable=True)
def __repr__(self):
return f'<Setting {self.key}>'
class Sale(db.Model):
id = db.Column(db.Integer, primary_key=True)
product_name = db.Column(db.String(100), nullable=False)
quantity = db.Column(db.Integer, nullable=False)
total_price = db.Column(db.Float, nullable=False)
sale_date = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
def __repr__(self):
return f'<Sale of {self.product_name}>'
with app.app_context():
db.create_all()
# --- FUNGSI & CONTEXT PROCESSOR ---
def generate_qr_code(data):
qr = qrcode.QRCode(version=1, box_size=10, border=4)
qr.add_data(data)
qr.make(fit=True)
img = qr.make_image(fill_color="black", back_color="white")
buffered = io.BytesIO()
img.save(buffered, format="PNG")
return base64.b64encode(buffered.getvalue()).decode("utf-8")
@app.context_processor
def inject_global_data():
menu_icons = {
'Dashboard': 'fa-solid fa-chart-pie', 'Product': 'fa-solid fa-mug-hot',
'Category': 'fa-solid fa-tags', 'Sale': 'fa-solid fa-cash-register',
'Setting': 'fa-solid fa-sliders'
}
def get_setting_from_db(key, default=''):
setting = Setting.query.filter_by(key=key).first()
return setting.value if setting else default
return dict(menu_icons=menu_icons, get_setting=get_setting_from_db, generate_qr_code=generate_qr_code)
# --- FUNGSI KEAMANAN ---
def check_auth(username, password):
ADMIN_USER = os.environ.get('ADMIN_USER', 'admin')
ADMIN_PASS = os.environ.get('ADMIN_PASS', 'password')
return username == ADMIN_USER and password == ADMIN_PASS
def authenticate():
return Response('Login Required', 401, {'WWW-Authenticate': 'Basic realm="Login Required"'})
def protected(f):
@wraps(f)
def decorated(*args, **kwargs):
auth = request.authorization
if not auth or not check_auth(auth.username, auth.password):
return authenticate()
return f(*args, **kwargs)
return decorated
# --- PENGATURAN ADMIN PANEL ---
class ProductAdminView(ModelView):
@protected
def dispatch_request(self, *args, **kwargs):
return super().dispatch_request(*args, **kwargs)
create_template = 'admin/custom_create.html'
edit_template = 'admin/custom_edit.html'
column_list = ('name', 'category', 'price')
column_filters = ('category',)
form_extra_fields = {
'image': ImageUploadField(
'Gambar Produk', base_path=UPLOAD_DIR, endpoint='get_upload',
namegen=lambda o, f: f"{datetime.now().strftime('%Y%m%d%H%M%S')}_{f.filename}",
allowed_extensions=['jpg', 'jpeg', 'png', 'gif'], max_size=(3*1024*1024, 'Maksimal 3MB')
)
}
form_columns = ('category', 'name', 'description', 'price', 'image')
form_widget_args = {'description': {'rows': 5}}
form_args = {'category': {'label': 'Kategori Produk', 'query_factory': lambda: Category.query.order_by(Category.name).all()}}
class SecureModelView(ModelView):
@protected
def dispatch_request(self, *args, **kwargs):
return super().dispatch_request(*args, **kwargs)
class DashboardView(AdminIndexView):
@protected
def dispatch_request(self, *args, **kwargs):
return super().dispatch_request(*args, **kwargs)
def get_sales_data(self):
today = date.today(); seven_days_ago = today - timedelta(days=6)
sales = db.session.query(func.date(Sale.sale_date).label('date'), func.sum(Sale.total_price).label('total')).filter(Sale.sale_date >= seven_days_ago).group_by(func.date(Sale.sale_date)).order_by(func.date(Sale.sale_date)).all()
sales_dict = {s.date.strftime('%Y-%m-%d'): s.total for s in sales}
labels = [(today - timedelta(days=i)).strftime('%a, %d') for i in range(6, -1, -1)]
data = [sales_dict.get((today - timedelta(days=i)).strftime('%Y-%m-%d'), 0) for i in range(6, -1, -1)]
return labels, data
def get_stats(self):
stats = {}; total_revenue = db.session.query(func.sum(Sale.total_price)).scalar()
stats['total_revenue'] = total_revenue or 0
stats['total_products'] = Product.query.count()
stats['today_sales'] = Sale.query.filter(func.date(Sale.sale_date) == date.today()).count()
return stats
@expose('/')
def index(self):
chart_labels, chart_data = self.get_sales_data(); stats = self.get_stats()
return self.render('admin/dashboard.html', chart_labels=chart_labels, chart_data=chart_data, stats=stats)
admin = Admin(
app,
name='Bit & Bean Dashboard',
template_mode='bootstrap4',
base_template='admin/custom_master.html',
index_view=DashboardView(name='Dashboard', url='/admin')
)
admin.add_view(ProductAdminView(Product, db.session, category="Manajemen"))
admin.add_view(SecureModelView(Category, db.session, category="Manajemen"))
admin.add_view(SecureModelView(Sale, db.session, category='Laporan'))
admin.add_view(SecureModelView(Setting, db.session, category='Pengaturan'))
# --- ROUTE PENYAJI GAMBAR & HALAMAN UTAMA ---
@app.route('/uploads/<path:filename>')
def get_upload(filename):
"""Route untuk menyajikan file yang di-upload dari penyimpanan."""
return send_from_directory(UPLOAD_DIR, filename)
@app.route('/')
def home():
"""Route untuk halaman utama website."""
categories = Category.query.order_by(Category.name).options(joinedload(Category.products)).all()
settings_query = Setting.query.all()
settings = {s.key: s.value for s in settings_query}
return render_template(
'index.html',
categories=categories,
settings=settings
)