noranisa commited on
Commit
678de73
·
verified ·
1 Parent(s): 947a5ad

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +62 -16
app.py CHANGED
@@ -7,7 +7,7 @@ from datetime import datetime, date, timedelta
7
  import qrcode
8
  from flask import Flask, render_template, request, Response, url_for
9
  from flask_sqlalchemy import SQLAlchemy
10
- from flask_admin import Admin, AdminIndexView, expose # <-- 1. Perubahan di sini
11
  from flask_admin.contrib.sqla import ModelView
12
  from sqlalchemy import func
13
 
@@ -16,13 +16,18 @@ app = Flask(__name__)
16
  app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'default-super-secret-key-for-local-dev')
17
 
18
  # --- KONFIGURASI DATABASE ---
 
19
  DATA_DIR = '/tmp'
 
20
  os.makedirs(DATA_DIR, exist_ok=True)
 
 
21
  db_path = os.path.join(DATA_DIR, 'database.db')
22
  app.config['SQLALCHEMY_DATABASE_URI'] = f'sqlite:///{db_path}'
23
  app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
24
  db = SQLAlchemy(app)
25
 
 
26
  # --- MODEL DATABASE (STRUKTUR TABEL) ---
27
  class Product(db.Model):
28
  id = db.Column(db.Integer, primary_key=True)
@@ -30,13 +35,17 @@ class Product(db.Model):
30
  description = db.Column(db.Text, nullable=False)
31
  price = db.Column(db.String(50), nullable=False)
32
  image = db.Column(db.String(100), nullable=True, default='default.jpg')
33
- def __repr__(self): return f'<Product {self.name}>'
 
 
34
 
35
  class Setting(db.Model):
36
  id = db.Column(db.Integer, primary_key=True)
37
  key = db.Column(db.String(50), unique=True, nullable=False)
38
  value = db.Column(db.Text, nullable=True)
39
- def __repr__(self): return f'<Setting {self.key}>'
 
 
40
 
41
  class Sale(db.Model):
42
  id = db.Column(db.Integer, primary_key=True)
@@ -44,12 +53,36 @@ class Sale(db.Model):
44
  quantity = db.Column(db.Integer, nullable=False)
45
  total_price = db.Column(db.Float, nullable=False)
46
  sale_date = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
47
- def __repr__(self): return f'<Sale of {self.product_name}>'
 
 
 
48
 
49
  # --- INISIALISASI DATABASE OTOMATIS ---
50
  with app.app_context():
51
  db.create_all()
52
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  # --- FUNGSI KEAMANAN UNTUK ADMIN PANEL ---
54
  def check_auth(username, password):
55
  ADMIN_USER = os.environ.get('ADMIN_USER', 'admin')
@@ -71,13 +104,11 @@ def protected(f):
71
  return f(*args, **kwargs)
72
  return decorated
73
 
 
74
  # --- PENGATURAN ADMIN PANEL ---
75
  class DashboardView(AdminIndexView):
76
- def get_setting(self, key, default=''):
77
- setting = Setting.query.filter_by(key=key).first()
78
- return setting.value if setting else default
79
-
80
  def get_sales_data(self):
 
81
  today = date.today()
82
  seven_days_ago = today - timedelta(days=6)
83
  sales = db.session.query(
@@ -89,22 +120,34 @@ class DashboardView(AdminIndexView):
89
  data = [sales_dict.get((today - timedelta(days=i)).strftime('%Y-%m-%d'), 0) for i in range(6, -1, -1)]
90
  return labels, data
91
 
92
- @expose('/') # <-- 2. Perubahan di sini
 
 
 
 
 
 
 
 
 
 
93
  @protected
94
  def index(self):
95
- cafe_name = self.get_setting('cafe_name', 'Bit & Bean Coffee')
96
- logo_url = self.get_setting('logo_url', url_for('static', filename='img/logo.png'))
97
  chart_labels, chart_data = self.get_sales_data()
 
 
98
  return self.render('admin/dashboard.html',
99
- cafe_name=cafe_name, logo_url=logo_url,
100
- chart_labels=chart_labels, chart_data=chart_data)
 
101
 
102
  class SecureModelView(ModelView):
103
  @protected
104
  def dispatch_request(self, *args, **kwargs):
105
  return super(SecureModelView, self).dispatch_request(*args, **kwargs)
106
 
107
- admin = Admin(app, name='Bit & Bean Dashboard', template_mode='bootstrap4',
 
108
  index_view=DashboardView(name='Dashboard', url='/admin'))
109
 
110
  admin.add_view(SecureModelView(Product, db.session))
@@ -130,8 +173,11 @@ def home():
130
  for product in products_from_db:
131
  product_url = f"https://BitBean-company.hf.space/product/{product.id}"
132
  product_data = {
133
- "id": product.id, "name": product.name, "description": product.description,
134
- "price": product.price, "image": product.image,
 
 
 
135
  "qr_code": generate_qr_code(product_url)
136
  }
137
  products_with_qr.append(product_data)
 
7
  import qrcode
8
  from flask import Flask, render_template, request, Response, url_for
9
  from flask_sqlalchemy import SQLAlchemy
10
+ from flask_admin import Admin, AdminIndexView, expose
11
  from flask_admin.contrib.sqla import ModelView
12
  from sqlalchemy import func
13
 
 
16
  app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'default-super-secret-key-for-local-dev')
17
 
18
  # --- KONFIGURASI DATABASE ---
19
+ # Gunakan direktori /tmp yang dijamin bisa ditulisi di semua lingkungan container.
20
  DATA_DIR = '/tmp'
21
+ # Pastikan direktori ada, buat jika belum ada.
22
  os.makedirs(DATA_DIR, exist_ok=True)
23
+
24
+ # Tentukan path lengkap ke file database di dalam direktori /tmp
25
  db_path = os.path.join(DATA_DIR, 'database.db')
26
  app.config['SQLALCHEMY_DATABASE_URI'] = f'sqlite:///{db_path}'
27
  app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
28
  db = SQLAlchemy(app)
29
 
30
+
31
  # --- MODEL DATABASE (STRUKTUR TABEL) ---
32
  class Product(db.Model):
33
  id = db.Column(db.Integer, primary_key=True)
 
35
  description = db.Column(db.Text, nullable=False)
36
  price = db.Column(db.String(50), nullable=False)
37
  image = db.Column(db.String(100), nullable=True, default='default.jpg')
38
+
39
+ def __repr__(self):
40
+ return f'<Product {self.name}>'
41
 
42
  class Setting(db.Model):
43
  id = db.Column(db.Integer, primary_key=True)
44
  key = db.Column(db.String(50), unique=True, nullable=False)
45
  value = db.Column(db.Text, nullable=True)
46
+
47
+ def __repr__(self):
48
+ return f'<Setting {self.key}>'
49
 
50
  class Sale(db.Model):
51
  id = db.Column(db.Integer, primary_key=True)
 
53
  quantity = db.Column(db.Integer, nullable=False)
54
  total_price = db.Column(db.Float, nullable=False)
55
  sale_date = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
56
+
57
+ def __repr__(self):
58
+ return f'<Sale of {self.product_name}>'
59
+
60
 
61
  # --- INISIALISASI DATABASE OTOMATIS ---
62
  with app.app_context():
63
  db.create_all()
64
 
65
+
66
+ # --- CONTEXT PROCESSOR UNTUK ADMIN TEMPLATE ---
67
+ @app.context_processor
68
+ def inject_admin_data():
69
+ """Menyediakan data global ke semua template admin."""
70
+ # Mapping nama menu ke kelas ikon Font Awesome untuk sidebar
71
+ menu_icons = {
72
+ 'Dashboard': 'fa-solid fa-chart-pie',
73
+ 'Product': 'fa-solid fa-mug-hot',
74
+ 'Sale': 'fa-solid fa-cash-register',
75
+ 'Setting': 'fa-solid fa-sliders'
76
+ }
77
+
78
+ # Fungsi helper agar bisa dipanggil dari master layout
79
+ def get_setting_from_db(key, default=''):
80
+ setting = Setting.query.filter_by(key=key).first()
81
+ return setting.value if setting else default
82
+
83
+ return dict(menu_icons=menu_icons, get_setting=get_setting_from_db)
84
+
85
+
86
  # --- FUNGSI KEAMANAN UNTUK ADMIN PANEL ---
87
  def check_auth(username, password):
88
  ADMIN_USER = os.environ.get('ADMIN_USER', 'admin')
 
104
  return f(*args, **kwargs)
105
  return decorated
106
 
107
+
108
  # --- PENGATURAN ADMIN PANEL ---
109
  class DashboardView(AdminIndexView):
 
 
 
 
110
  def get_sales_data(self):
111
+ """Query data penjualan untuk 7 hari terakhir."""
112
  today = date.today()
113
  seven_days_ago = today - timedelta(days=6)
114
  sales = db.session.query(
 
120
  data = [sales_dict.get((today - timedelta(days=i)).strftime('%Y-%m-%d'), 0) for i in range(6, -1, -1)]
121
  return labels, data
122
 
123
+ def get_stats(self):
124
+ """Menghitung statistik utama untuk dashboard."""
125
+ stats = {}
126
+ total_revenue = db.session.query(func.sum(Sale.total_price)).scalar()
127
+ stats['total_revenue'] = total_revenue or 0
128
+ stats['total_products'] = Product.query.count()
129
+ today_sales = Sale.query.filter(func.date(Sale.sale_date) == date.today()).count()
130
+ stats['today_sales'] = today_sales
131
+ return stats
132
+
133
+ @expose('/')
134
  @protected
135
  def index(self):
 
 
136
  chart_labels, chart_data = self.get_sales_data()
137
+ stats = self.get_stats()
138
+ # Nama Kafe dan Logo sekarang diambil oleh context processor secara global
139
  return self.render('admin/dashboard.html',
140
+ chart_labels=chart_labels,
141
+ chart_data=chart_data,
142
+ stats=stats)
143
 
144
  class SecureModelView(ModelView):
145
  @protected
146
  def dispatch_request(self, *args, **kwargs):
147
  return super(SecureModelView, self).dispatch_request(*args, **kwargs)
148
 
149
+ admin = Admin(app, name='Bit & Bean Dashboard', template_mode='bootstrap4',
150
+ base_template='admin/custom_master.html',
151
  index_view=DashboardView(name='Dashboard', url='/admin'))
152
 
153
  admin.add_view(SecureModelView(Product, db.session))
 
173
  for product in products_from_db:
174
  product_url = f"https://BitBean-company.hf.space/product/{product.id}"
175
  product_data = {
176
+ "id": product.id,
177
+ "name": product.name,
178
+ "description": product.description,
179
+ "price": product.price,
180
+ "image": product.image,
181
  "qr_code": generate_qr_code(product_url)
182
  }
183
  products_with_qr.append(product_data)