Download app.py from BitBean/company: direct link, hf CLI and curl.
- Browser
- Download file 7.66 kB
-
https://huggingface.co/spaces/BitBean/company/resolve/main/app.py
- Command line
-
hf download hf://spaces/BitBean/company/app.py
-
curl -L -o app.py https://huggingface.co/spaces/BitBean/company/resolve/main/app.py
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") | |
| 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): | |
| 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): | |
| 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): | |
| def dispatch_request(self, *args, **kwargs): | |
| return super().dispatch_request(*args, **kwargs) | |
| class DashboardView(AdminIndexView): | |
| 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 | |
| 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 --- | |
| def get_upload(filename): | |
| """Route untuk menyajikan file yang di-upload dari penyimpanan.""" | |
| return send_from_directory(UPLOAD_DIR, filename) | |
| 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 | |
| ) |