company / app.py
noranisa's picture
Update app.py
23ac9de verified
Raw History Blame Contribute Delete
7.66 kB
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
)