noranisa commited on
Commit
23ac9de
·
verified ·
1 Parent(s): c577326

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +37 -48
app.py CHANGED
@@ -18,12 +18,9 @@ app = Flask(__name__)
18
  app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'default-super-secret-key-for-local-dev')
19
 
20
  # --- KONFIGURASI PATH PENYIMPANAN SEMENTARA ---
21
- # Database dan Upload akan disimpan di /tmp yang dijamin bisa ditulisi
22
  DATA_DIR = '/tmp'
23
  UPLOAD_DIR = os.path.join(DATA_DIR, 'uploads')
24
  DB_PATH = os.path.join(DATA_DIR, 'database.db')
25
-
26
- # Pastikan direktori-direktori ini ada saat aplikasi dimulai
27
  os.makedirs(UPLOAD_DIR, exist_ok=True)
28
 
29
  # --- KONFIGURASI DATABASE ---
@@ -32,7 +29,7 @@ app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
32
  db = SQLAlchemy(app)
33
 
34
 
35
- # --- MODEL DATABASE (STRUKTUR TABEL) ---
36
  class Category(db.Model):
37
  id = db.Column(db.Integer, primary_key=True)
38
  name = db.Column(db.String(100), unique=True, nullable=False)
@@ -67,13 +64,11 @@ class Sale(db.Model):
67
  def __repr__(self):
68
  return f'<Sale of {self.product_name}>'
69
 
70
-
71
- # --- INISIALISASI DATABASE OTOMATIS ---
72
  with app.app_context():
73
  db.create_all()
74
 
75
 
76
- # --- FUNGSI QR CODE ---
77
  def generate_qr_code(data):
78
  qr = qrcode.QRCode(version=1, box_size=10, border=4)
79
  qr.add_data(data)
@@ -81,17 +76,13 @@ def generate_qr_code(data):
81
  img = qr.make_image(fill_color="black", back_color="white")
82
  buffered = io.BytesIO()
83
  img.save(buffered, format="PNG")
84
- img_str = base64.b64encode(buffered.getvalue()).decode("utf-8")
85
- return img_str
86
 
87
- # --- CONTEXT PROCESSOR UNTUK TEMPLATE ---
88
  @app.context_processor
89
  def inject_global_data():
90
  menu_icons = {
91
- 'Dashboard': 'fa-solid fa-chart-pie',
92
- 'Product': 'fa-solid fa-mug-hot',
93
- 'Category': 'fa-solid fa-tags',
94
- 'Sale': 'fa-solid fa-cash-register',
95
  'Setting': 'fa-solid fa-sliders'
96
  }
97
  def get_setting_from_db(key, default=''):
@@ -109,58 +100,57 @@ def check_auth(username, password):
109
  def authenticate():
110
  return Response('Login Required', 401, {'WWW-Authenticate': 'Basic realm="Login Required"'})
111
 
112
-
113
- # --- PENGATURAN ADMIN PANEL ---
114
- class AuthMixin:
115
- def is_accessible(self):
116
  auth = request.authorization
117
- return auth and check_auth(auth.username, auth.password)
118
- def inaccessible_callback(self, name, **kwargs):
119
- return authenticate()
 
120
 
121
- class ProductAdminView(AuthMixin, ModelView):
 
 
 
 
 
 
122
  create_template = 'admin/custom_create.html'
123
  edit_template = 'admin/custom_edit.html'
124
  column_list = ('name', 'category', 'price')
125
  column_filters = ('category',)
126
  form_extra_fields = {
127
  'image': ImageUploadField(
128
- 'Gambar Produk',
129
- base_path=UPLOAD_DIR,
130
- endpoint='get_upload',
131
- namegen=lambda obj, file_data: f"{datetime.now().strftime('%Y%m%d%H%M%S')}_{file_data.filename}",
132
- allowed_extensions=['jpg', 'jpeg', 'png', 'gif'],
133
- max_size=(3 * 1024 * 1024, 'Ukuran file maksimal 3MB')
134
  )
135
  }
136
  form_columns = ('category', 'name', 'description', 'price', 'image')
137
- form_args = {
138
- 'category': {
139
- 'label': 'Kategori Produk',
140
- 'query_factory': lambda: Category.query.order_by(Category.name).all()
141
- },
142
- 'description': {'rows': 5}
143
- }
144
 
145
- class SecureModelView(AuthMixin, ModelView):
146
- pass
 
 
147
 
148
- class DashboardView(AuthMixin, AdminIndexView):
149
  def get_sales_data(self):
150
- today = date.today()
151
- seven_days_ago = today - timedelta(days=6)
152
- sales = db.session.query(
153
- func.date(Sale.sale_date).label('date'),
154
- func.sum(Sale.total_price).label('total')
155
- ).filter(Sale.sale_date >= seven_days_ago).group_by(func.date(Sale.sale_date)).order_by(func.date(Sale.sale_date)).all()
156
  sales_dict = {s.date.strftime('%Y-%m-%d'): s.total for s in sales}
157
  labels = [(today - timedelta(days=i)).strftime('%a, %d') for i in range(6, -1, -1)]
158
  data = [sales_dict.get((today - timedelta(days=i)).strftime('%Y-%m-%d'), 0) for i in range(6, -1, -1)]
159
  return labels, data
160
 
161
  def get_stats(self):
162
- stats = {}
163
- total_revenue = db.session.query(func.sum(Sale.total_price)).scalar()
164
  stats['total_revenue'] = total_revenue or 0
165
  stats['total_products'] = Product.query.count()
166
  stats['today_sales'] = Sale.query.filter(func.date(Sale.sale_date) == date.today()).count()
@@ -168,8 +158,7 @@ class DashboardView(AuthMixin, AdminIndexView):
168
 
169
  @expose('/')
170
  def index(self):
171
- chart_labels, chart_data = self.get_sales_data()
172
- stats = self.get_stats()
173
  return self.render('admin/dashboard.html', chart_labels=chart_labels, chart_data=chart_data, stats=stats)
174
 
175
  admin = Admin(
 
18
  app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'default-super-secret-key-for-local-dev')
19
 
20
  # --- KONFIGURASI PATH PENYIMPANAN SEMENTARA ---
 
21
  DATA_DIR = '/tmp'
22
  UPLOAD_DIR = os.path.join(DATA_DIR, 'uploads')
23
  DB_PATH = os.path.join(DATA_DIR, 'database.db')
 
 
24
  os.makedirs(UPLOAD_DIR, exist_ok=True)
25
 
26
  # --- KONFIGURASI DATABASE ---
 
29
  db = SQLAlchemy(app)
30
 
31
 
32
+ # --- MODEL DATABASE ---
33
  class Category(db.Model):
34
  id = db.Column(db.Integer, primary_key=True)
35
  name = db.Column(db.String(100), unique=True, nullable=False)
 
64
  def __repr__(self):
65
  return f'<Sale of {self.product_name}>'
66
 
 
 
67
  with app.app_context():
68
  db.create_all()
69
 
70
 
71
+ # --- FUNGSI & CONTEXT PROCESSOR ---
72
  def generate_qr_code(data):
73
  qr = qrcode.QRCode(version=1, box_size=10, border=4)
74
  qr.add_data(data)
 
76
  img = qr.make_image(fill_color="black", back_color="white")
77
  buffered = io.BytesIO()
78
  img.save(buffered, format="PNG")
79
+ return base64.b64encode(buffered.getvalue()).decode("utf-8")
 
80
 
 
81
  @app.context_processor
82
  def inject_global_data():
83
  menu_icons = {
84
+ 'Dashboard': 'fa-solid fa-chart-pie', 'Product': 'fa-solid fa-mug-hot',
85
+ 'Category': 'fa-solid fa-tags', 'Sale': 'fa-solid fa-cash-register',
 
 
86
  'Setting': 'fa-solid fa-sliders'
87
  }
88
  def get_setting_from_db(key, default=''):
 
100
  def authenticate():
101
  return Response('Login Required', 401, {'WWW-Authenticate': 'Basic realm="Login Required"'})
102
 
103
+ def protected(f):
104
+ @wraps(f)
105
+ def decorated(*args, **kwargs):
 
106
  auth = request.authorization
107
+ if not auth or not check_auth(auth.username, auth.password):
108
+ return authenticate()
109
+ return f(*args, **kwargs)
110
+ return decorated
111
 
112
+
113
+ # --- PENGATURAN ADMIN PANEL ---
114
+ class ProductAdminView(ModelView):
115
+ @protected
116
+ def dispatch_request(self, *args, **kwargs):
117
+ return super().dispatch_request(*args, **kwargs)
118
+
119
  create_template = 'admin/custom_create.html'
120
  edit_template = 'admin/custom_edit.html'
121
  column_list = ('name', 'category', 'price')
122
  column_filters = ('category',)
123
  form_extra_fields = {
124
  'image': ImageUploadField(
125
+ 'Gambar Produk', base_path=UPLOAD_DIR, endpoint='get_upload',
126
+ namegen=lambda o, f: f"{datetime.now().strftime('%Y%m%d%H%M%S')}_{f.filename}",
127
+ allowed_extensions=['jpg', 'jpeg', 'png', 'gif'], max_size=(3*1024*1024, 'Maksimal 3MB')
 
 
 
128
  )
129
  }
130
  form_columns = ('category', 'name', 'description', 'price', 'image')
131
+ form_widget_args = {'description': {'rows': 5}}
132
+ form_args = {'category': {'label': 'Kategori Produk', 'query_factory': lambda: Category.query.order_by(Category.name).all()}}
133
+
134
+ class SecureModelView(ModelView):
135
+ @protected
136
+ def dispatch_request(self, *args, **kwargs):
137
+ return super().dispatch_request(*args, **kwargs)
138
 
139
+ class DashboardView(AdminIndexView):
140
+ @protected
141
+ def dispatch_request(self, *args, **kwargs):
142
+ return super().dispatch_request(*args, **kwargs)
143
 
 
144
  def get_sales_data(self):
145
+ today = date.today(); seven_days_ago = today - timedelta(days=6)
146
+ 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()
 
 
 
 
147
  sales_dict = {s.date.strftime('%Y-%m-%d'): s.total for s in sales}
148
  labels = [(today - timedelta(days=i)).strftime('%a, %d') for i in range(6, -1, -1)]
149
  data = [sales_dict.get((today - timedelta(days=i)).strftime('%Y-%m-%d'), 0) for i in range(6, -1, -1)]
150
  return labels, data
151
 
152
  def get_stats(self):
153
+ stats = {}; total_revenue = db.session.query(func.sum(Sale.total_price)).scalar()
 
154
  stats['total_revenue'] = total_revenue or 0
155
  stats['total_products'] = Product.query.count()
156
  stats['today_sales'] = Sale.query.filter(func.date(Sale.sale_date) == date.today()).count()
 
158
 
159
  @expose('/')
160
  def index(self):
161
+ chart_labels, chart_data = self.get_sales_data(); stats = self.get_stats()
 
162
  return self.render('admin/dashboard.html', chart_labels=chart_labels, chart_data=chart_data, stats=stats)
163
 
164
  admin = Admin(