공동구매 페이지 소개
공동구매 웹페이지는 로그인된 사용자가 상품을 등록하고, 다른 사용자들과 공동으로 구매할 수 있도록 지원하는 플랫폼이다. 사용자는 상품의 정보, 가격, 구매 마감일 등을 입력하며, 시스템은 실시간으로 데이터를 관리하고 D-Day를 계산해 표시한다. 또한 직관적인 상품 정보를 제공한다.
*pixso 사용

설명: 사용자는 상품명, 가격, 카테고리, 구매 수량, 구매 마감일, 상세 설명 등을 지정하여 상품을 등록할 수 있다. 등록된 정보는 Firebase Realtime Database에 저장된다.

D-Day 계산 : 파이썬에서 현재 시각을 불러오고 .now().date()로 연,월,일 형식으로 사용자가 설정한 구매 마감일을 기준으로 현재 날짜와의 차이를 계산하여 D-0, D+1 등의 형식으로 표시한다.
개당 가격 계산 : 사용자에게 입력받은 상품의 가격과 수량을 정수 계산하여 개당 가격을 계산해 데이터베이스에 넣는다.
판매자 아이디 : 로그인된 사용자의 아이디로 자동 입력된다.
# 공동구매 화면
# 공동구매_상품등록
def insert_gr(self, name, data, img_path, session):
current_date=datetime.now().date()
# 입력된 날짜 (data['date'])를 날짜 객체로 변환
try:
target_date = datetime.strptime(data['date'], "%Y-%m-%d").date()
except ValueError:
print("Invalid date format. Expected 'YYYY-MM-DD'.")
return False
# D-Day 계산
d_day = (target_date - current_date).days
d_day_display = f"D-{d_day}" if d_day > 0 else "D-Day" if d_day == 0 else f"D+{-d_day}"
#개당 개수
per_price=int(data['price'])/int(data['cnt'])
#quantity 기본값
initial_quantity = int(data.get('quantity', 0))
item_info ={
"id": session['id'],
"category": data['category'],
"price": data['price'],
"info": data['info'],
"cnt":int(data['cnt']),
"address": data['address'],
"date":data['date'],
"details": data['details'],
"img_path": img_path,
"d_day":d_day_display,
"per_price":per_price,
"quantity": initial_quantity,
"updated_cnt": data['cnt']
}
self.db.child("gr_item").child(name).set(item_info)
print(data,img_path)
return True
[1] 상품 리스트
해당 상품의 정보를 database에서 불러와 전체화면에 상품별 표시하는 html코드
<div class="product-card">
<h2>{{ product.name }}</h2>
<p>Price: {{ product.price }}</p>
<p>D-Day: {{ product.d_day }}</p>
</div>
[2] 상품 상세 화면

수량 및 총 금액 변경하는 js코드
<script>
document.addEventListener('DOMContentLoaded', function () {
const price = {{data.price}};// 총 가격
const cnt = {{data.cnt}}; // 총 개수
// 개당 가격 계산
const perPrice = Math.floor(price / cnt);
document.getElementById('per-price').textContent = perPrice;
const quantityInput = document.getElementById('quantity');
const totalPriceElement = document.getElementById('total-price');
// 수량 변경 시 총 금액 업데이트
quantityInput.addEventListener('input', function () {
const quantity = parseInt(quantityInput.value) || 0;
const totalPrice = quantity * perPrice;
totalPriceElement.textContent = `총 금액 : ${totalPrice}원`; // 3자리 콤마 추가
});
});
</script>
상세 화면에서 수량에 맞춰 공동구매에 참여하고 수량을 db에 업데이트
# app.py
# 공동구매 수량 db에 등록
@application.route("/gr_quantity", methods=['POST'])
def gr_quantity():
data = {
'name': request.form['name'],
# 'quantity': int(request.form['quantity']),
'cnt': int(request.form['cnt']) # 주문 가능 수량이 필요하면 추가
}
inputCnt = request.form['quantity']
# 데이터 업데이트
DB.update_quantity(data, inputCnt)
# updated_item = DB.db.child("gr_item").child(data['name']).get().val()
return redirect(url_for('grpPurchase'))
# database.py
# 공동구매 수량 등록
def update_quantity(self, data, inputCnt):
#현재 데이터 가져오기
current_item = self.db.child("gr_item").child(data['name']).get().val()
# 남은 수량 계산
updated_quantity = int(current_item['quantity']) + int(inputCnt) #누적 개수
remain_cnt = int(current_item['cnt']) - updated_quantity #남은 개수
updated_item = {
"updated_cnt" : remain_cnt,
"quantity": updated_quantity
}
current_item.update(updated_item)
self.db.child("gr_item").child(data['name']).set(current_item)
return True

[3] 핵심 설명
updated_quantity = int(current_item['quantity']) + int(inputCnt)로 계산하며 사용자들이 입력한 주문 수량을 더하여 데이터를 업데이트한다.remain_cnt = int(current_item['cnt']) - updated_quantity 누적된 주문 수량을 기존의 사용자가 입력한 수량에 빼서 업데이트한다.설명 : 사용자는 상품을 상단의 섹션으로 카테고리별로 탐색할 수 있으며, 좌측 필터를 사용해 진행률(전체 수량 중 다른 사용자가 참여한 수량의 비율) 기준으로 상품을 세부적으로 필터링할 수 있습니다. 위 기능들은 사용자가 원하는 상품을 더 빠르게 찾을 수 있도록 하는 요소이다.
[1] 카테고리(사용자 입력 기준)

해당 html 코드 및 css 코드
<section class="category-section">
<form method="get" action="/grpurchase_ViewAll">
<button type="submit" name="category" value="" class="category-item category-title" style="margin: 0 auto;"><h2 class="category-name">카테고리</h2></button>
<div class="category-container">
{% for category in ["화장품", "의류", "육류 및 해산물", "가공식품", "과일 및 채소", "냉동식품", "전자제품", "기타"] %}
<button
type="submit"
name="category"
value="{{ category }}"
class="category-item {% if current_category == category %}active{% endif %}">
{{ category }}
</button>
{% endfor %}
</div>
</form>
</section>
<style>
.category-item.active:not(.category-title) {
background-color: rgb(0, 70, 42);
color: white;
}
</style>
해당 카테고리 상품 라우팅하는 app.py 코드
def view_product():
page = request.args.get("page", 0, type=int)
category = request.args.get("category", "all")
#화면에서 셀렉트박스 선택한 카테고리 값 받아오기
# 카테고리별로 db에서 데이터 받아오기
if category!="all":
data = DB.get_items_bycategory(category)
item_counts = len(data)
[2] 좌측 필터

해당 html 코드 및 css 코드
<aside class="filter-section">
<a href="/view_grReg" class="open-gr">공동구매 등록</a>
<h3>필터</h3>
<hr>
<form method="get" action="/grpurchase_ViewAll">
<div class="filter-group">
<h3>진행사항</h3>
<div class="filter-item">
<input type="checkbox" id="9" name="progress_filter" value="90">
<label for="9">90%~</label>
</div>
<div class="filter-item">
<input type="checkbox" id="8" name="progress_filter" value="80">
<label for="8">80%~</label>
</div>
<div class="filter-item">
<input type="checkbox" id="7" name="progress_filter" value="70">
<label for="7">70%~</label>
</div>
<div class="filter-item">
<input type="checkbox" id="6" name="progress_filter" value="60">
<label for="6">60%~</label>
</div>
<div class="filter-item">
<input type="checkbox" id="5" name="progress_filter" value="50">
<label for="5">50%~</label>
</div>
<div class="filter-item">
<input type="checkbox" id="4" name="progress_filter" value="0-50">
<label for="4">~50%</label>
</div>
</div>
<button type="submit" class = "open-gr" style="font-size: 14px; float: left; border: none;">필터 적용</button>
</form>
</aside>
해당 카테고리 상품 라우팅하는 app.py 코드
# 진행률 필터
if progress_filter:
filtered_data = {}
for key, item in data.items():
progress = (item['quantity'] / item['cnt']) * 100 if item['cnt'] > 0 else 0
for filter_value in progress_filter:
if filter_value == "0-50":
if progress <= 50:
filtered_data[key] = item
else:
try:
if progress >= int(filter_value):
filtered_data[key] = item
except ValueError:
pass # 예상치 못한 값은 무시
data = filtered_data
[3] 핵심 설명
data = {k: v for k, v in data.items() if v['category'] == category} / 선택된 카테고리에 해당하는 상품만 필터링한다.progress = (item['quantity'] / item['cnt']) * 100 if item['cnt'] > 0 else 0 / 진행률은 '사용자들에 의해 선택된 수량 / 기존의 등록자가 작성한 총 수량'이며, 상품 진행률을 계산 후 조건에 맞는 상품만 필터링한다.import pyrebase
import json
from datetime import datetime
실시간 데이터 동기화를 지원하는 NoSQL 기반의 클라우드 데이터베이스로 데이터는 JSON 트리 형태로 저장되며,경로를 통해 데이터에 접근한다. 공동구매에서는 gr_item/{상품명} 경로에 상품 데이터를 저장한다.
경로를 통해 데이터에 접근하며
# 데이터 가져오기
data = DB.gr_get_items()
접근한 데이터를 data.{데이터명}의 형태로 html코드에서 활용한다.
<div class="a-info-row">
<span class="title">거래 장소</span>
<span class="value">{{data.address}}</span>
</div>
<div class="divider"></div>
<div class="a-info-row">
<span class="title">상품 정보</span>
<span class="value">{{data.details}}</span>
</div>
gr_item/{상품명}으로 저장된 상품예시


#app.py
from flask import Flask, render_template, request, flash, redirect, url_for, session, jsonify
from database import DBhandler
import hashlib
import sys
import random
application = Flask(__name__)
#database.py
import pyrebase


결론 : firebasesms ., $, #, [, ], /와 같은 문자를 허용하지 않는다...따라서 데이터 입력할 때 설정 잘해야한다.