Brandon Cornejo
7 years ago
commit
7825abf11c
13 changed files with 672 additions and 0 deletions
-
3config.py
-
BINdiscworld.db
-
9discworld/__init__.py
-
95discworld/models.py
-
97discworld/shop.py
-
BINdiscworld/static/favicon.ico
-
76discworld/templates/layout.html
-
205discworld/templates/shop_dashboard.html
-
17discworld/templates/shop_dataentry.html
-
119discworld/templates/shop_product_entries.html
-
36discworld/views.py
-
11uwsgi.ini
-
4wsgi.py
@ -0,0 +1,3 @@ |
|||||
|
DEBUG = False |
||||
|
SQLALCHEMY_TRACK_MODIFICATIONS = False |
||||
|
SQLALCHEMY_DATABASE_URI = 'sqlite:///../discworld.db' |
@ -0,0 +1,9 @@ |
|||||
|
from flask import Flask |
||||
|
from flask_sqlalchemy import SQLAlchemy |
||||
|
|
||||
|
app = Flask(__name__) |
||||
|
app.config.from_object('config') |
||||
|
|
||||
|
db = SQLAlchemy(app) |
||||
|
|
||||
|
from . import views |
@ -0,0 +1,95 @@ |
|||||
|
from datetime import datetime |
||||
|
|
||||
|
from . import ( |
||||
|
db, |
||||
|
shop |
||||
|
) |
||||
|
|
||||
|
|
||||
|
class ShopProduct(db.Model): |
||||
|
id = db.Column(db.Integer, primary_key = True) |
||||
|
name = db.Column(db.String(100), unique=True, nullable=False) |
||||
|
total_listed = db.Column(db.Integer) |
||||
|
total_sold = db.Column(db.Integer) |
||||
|
entries = db.relationship('ShopEntry', backref='product', lazy=True, |
||||
|
order_by="desc(ShopEntry.date)") |
||||
|
|
||||
|
def __repr__(self): |
||||
|
return '<ShopProduct "{}">'.format(self.name) |
||||
|
|
||||
|
@property |
||||
|
def latest_entry(self): |
||||
|
return ShopEntry.query.filter_by( |
||||
|
product_id=self.id).order_by(ShopEntry.date.desc()).first() |
||||
|
|
||||
|
@property |
||||
|
def total_stocked(self): |
||||
|
entries = ShopEntry.query.filter_by(product_id=self.id).order_by( |
||||
|
ShopEntry.date.asc()) |
||||
|
|
||||
|
last_stock = 0 |
||||
|
stocked_count = 0 |
||||
|
for entry in entries: |
||||
|
diff = last_stock - entry.stock |
||||
|
if diff < 0: |
||||
|
stocked_count += abs(diff) |
||||
|
last_stock = entry.stock |
||||
|
return stocked_count |
||||
|
|
||||
|
@property |
||||
|
def total_sold(self): |
||||
|
entries = ShopEntry.query.filter_by(product_id=self.id).order_by( |
||||
|
ShopEntry.date.asc()) |
||||
|
|
||||
|
last_stock = 0 |
||||
|
sold_count = 0 |
||||
|
for entry in entries: |
||||
|
diff = last_stock - entry.stock |
||||
|
if diff > 0: |
||||
|
sold_count += diff |
||||
|
last_stock = entry.stock |
||||
|
return sold_count |
||||
|
|
||||
|
@property |
||||
|
def total_earned(self): |
||||
|
entries = ShopEntry.query.filter_by(product_id=self.id).order_by( |
||||
|
ShopEntry.date.asc()) |
||||
|
|
||||
|
last_stock = 0 |
||||
|
earned_count = 0.00 |
||||
|
for entry in entries: |
||||
|
diff = last_stock - entry.stock |
||||
|
if diff > 0: |
||||
|
earned_count += (entry.price * diff) |
||||
|
last_stock = entry.stock |
||||
|
return earned_count |
||||
|
|
||||
|
|
||||
|
class ShopEntry(db.Model): |
||||
|
id = db.Column(db.Integer, primary_key=True) |
||||
|
stock = db.Column(db.Integer, unique=False, nullable=False) |
||||
|
raw_price = db.Column(db.String(20), unique=False, nullable=False) |
||||
|
raw_stock = db.Column(db.String(2), unique=False, nullable=False) |
||||
|
date = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) |
||||
|
product_id = db.Column(db.Integer, db.ForeignKey('shop_product.id'), |
||||
|
nullable=False) |
||||
|
|
||||
|
def __repr__(self): |
||||
|
return '<ShopEntry "{}" {}>'.format( |
||||
|
self.product.name, |
||||
|
self.id |
||||
|
) |
||||
|
|
||||
|
@property |
||||
|
def stock(self): |
||||
|
stock_map = { |
||||
|
'zero': 0, 'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5, |
||||
|
'six': 6, 'seven': 7, 'eight': 8, 'nine': 9, 'ten': 10, |
||||
|
'eleven': 11, 'twelve': 12, 'thirteen': 13, 'fourteen': 14 |
||||
|
} |
||||
|
return stock_map[self.raw_stock] |
||||
|
|
||||
|
@property |
||||
|
def price(self): |
||||
|
brass = shop.convert_lancre_to_brass(self.raw_price) |
||||
|
return shop.convert_brass_to_am(brass) |
@ -0,0 +1,97 @@ |
|||||
|
import re |
||||
|
|
||||
|
from . import ( |
||||
|
db, |
||||
|
models |
||||
|
) |
||||
|
|
||||
|
|
||||
|
def convert_lancre_to_brass(raw_price): |
||||
|
coins = { |
||||
|
'penny': 0, |
||||
|
'shilling': 0, |
||||
|
'crown': 0, |
||||
|
'sovereign': 0, |
||||
|
'hedgehog': 0 |
||||
|
} |
||||
|
|
||||
|
def noz(num): |
||||
|
if num and num is not '-': |
||||
|
return int(num) |
||||
|
return 0 |
||||
|
|
||||
|
# Figure out which special notation we have |
||||
|
if "LC" in raw_price: |
||||
|
pattern = r'^LC (\d+)\|(\d+|-)\|(\d+|-)$' |
||||
|
match = re.match(pattern, raw_price) |
||||
|
groups = match.groups() |
||||
|
|
||||
|
coins['penny'] = noz(groups[2]) |
||||
|
coins['shilling'] = noz(groups[1]) |
||||
|
coins['crown'] = noz(groups[0]) |
||||
|
elif "LSov" in raw_price: |
||||
|
pattern = r'^LSov (\d+)\|(\d+|-)\|(\d+|-)\|(\d+|-)$' |
||||
|
match = re.match(pattern, raw_price) |
||||
|
groups = match.groups() |
||||
|
|
||||
|
coins['penny'] = noz(groups[3]) |
||||
|
coins['shilling'] = noz(groups[2]) |
||||
|
coins['crown'] = noz(groups[1]) |
||||
|
coins['sovereign'] = noz(groups[0]) |
||||
|
elif "LH" in raw_price: |
||||
|
pattern = r'^LH (\d+)\|(\d+|-)\|(\d+|-)\|(\d+|-)\|(\d+|-)$' |
||||
|
match = re.match(pattern, raw_price) |
||||
|
groups = match.groups() |
||||
|
|
||||
|
coins['penny'] = noz(groups[4]) |
||||
|
coins['shilling'] = noz(groups[3]) |
||||
|
coins['crown'] = noz(groups[2]) |
||||
|
coins['sovereign'] = noz(groups[1]) |
||||
|
coins['hedgehog'] = noz(groups[0]) |
||||
|
|
||||
|
# Convert to brass |
||||
|
brass_coins = ( |
||||
|
(coins['hedgehog'] * 248832) + |
||||
|
(coins['sovereign'] * 20736) + |
||||
|
(coins['crown'] * 1728) + |
||||
|
(coins['shilling'] * 144) + |
||||
|
(coins['penny'] * 12) |
||||
|
) |
||||
|
return brass_coins |
||||
|
|
||||
|
def convert_brass_to_am(brass_price): |
||||
|
# 12 brass coins to am pennies |
||||
|
pennies = brass_price / 4 |
||||
|
return (pennies/100) |
||||
|
|
||||
|
def parse_shop_output(data): |
||||
|
pattern = r'^\s{3}?\w{2}\)\sAn*\s([\w\s-]+) for (L\w{1,3} [\d\-|]+);\s(\w+)\sleft\.$' |
||||
|
matches = [m.groups() for m in re.finditer(pattern, data, re.MULTILINE)] |
||||
|
|
||||
|
if not matches: |
||||
|
return |
||||
|
|
||||
|
# Iterate over each product line in the data |
||||
|
seen_products = [] |
||||
|
for m in matches: |
||||
|
product = models.ShopProduct.query.filter_by(name=m[0]).first() |
||||
|
if not product: |
||||
|
# If we didn't find a product, create it |
||||
|
product = models.ShopProduct(name=m[0]) |
||||
|
db.session.add(product) |
||||
|
# Add a ShopEntry for this row only if stock has changed |
||||
|
if not product.latest_entry or product.latest_entry.raw_stock != m[2]: |
||||
|
entry = models.ShopEntry(raw_price=m[1], raw_stock=m[2], product=product) |
||||
|
db.session.add(entry) |
||||
|
seen_products.append(product) |
||||
|
|
||||
|
# Check all products against seen, record sellouts |
||||
|
products = models.ShopProduct.query.all() |
||||
|
for product in products: |
||||
|
if product not in seen_products and product.latest_entry.stock != 0: |
||||
|
entry = models.ShopEntry( |
||||
|
raw_price=product.latest_entry.raw_price, |
||||
|
raw_stock='zero', product=product |
||||
|
) |
||||
|
db.session.add(entry) |
||||
|
db.session.commit() |
After Width: 16 | Height: 16 | Size: 785 B |
@ -0,0 +1,76 @@ |
|||||
|
<!doctype html> |
||||
|
<html> |
||||
|
<head> |
||||
|
<title>Ramtops Remedies and Reagents</title> |
||||
|
|
||||
|
<meta charset="utf-8"> |
||||
|
<meta name="viewport" content="width=device-width, initial-scale=1"> |
||||
|
|
||||
|
<!-- UIkit CSS --> |
||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/uikit/3.0.0-beta.35/css/uikit.min.css" /> |
||||
|
|
||||
|
<!-- UIkit JS --> |
||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/uikit/3.0.0-beta.35/js/uikit.min.js"></script> |
||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/uikit/3.0.0-beta.35/js/uikit-icons.min.js"></script> |
||||
|
|
||||
|
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}"> |
||||
|
<style> |
||||
|
</style> |
||||
|
</head> |
||||
|
<body> |
||||
|
<div class="uk-container uk-container-large"> |
||||
|
<!-- Title --> |
||||
|
<h3 class="uk-text-center uk-heading-line uk-margin-small-top"> |
||||
|
<a href="/" class="">Ramtops Remedies and Reagents</a> |
||||
|
</h3> |
||||
|
|
||||
|
<!-- Top Bar --> |
||||
|
<div class="" uk-grid> |
||||
|
<div class="uk-width-1-3"> {% block left_top_bar %} {% endblock %} </div> |
||||
|
<div class="uk-width-1-3 uk-text-center"> {% block center_top_bar %} {% endblock %} </div> |
||||
|
<div class="uk-width-1-3"> |
||||
|
<a href="/shop/data" class="uk-button uk-button-primary uk-button-small uk-align-right uk-border-rounded"> |
||||
|
Report Stock |
||||
|
</a> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- Page Content --> |
||||
|
{% block content %} {% endblock %} |
||||
|
|
||||
|
<!-- Footer --> |
||||
|
<footer class="uk-text-center uk-margin-large-top uk-margin-small-bottom"> |
||||
|
© 2017 by <a href="http://binaryatrocity.name">Ruhsbaar</a> |
||||
|
| |
||||
|
Created for Ramtops Remedies and Reagents |
||||
|
| |
||||
|
<a href="http://discworld.starturtle.net">DiscworldMUD</a> |
||||
|
</footer> |
||||
|
</div> |
||||
|
</body> |
||||
|
{% block pagescripts %} {% endblock %} |
||||
|
<script> |
||||
|
function change_date_strings() { |
||||
|
var times = document.querySelectorAll('table > tbody > tr > td:last-child'); |
||||
|
for(var time of times) { |
||||
|
var local = new Date(time.textContent + 'Z'); |
||||
|
|
||||
|
var options = { |
||||
|
weekday: 'short', |
||||
|
year: 'numeric', |
||||
|
month: 'short', |
||||
|
day: 'numeric', |
||||
|
hour: 'numeric', |
||||
|
minute: 'numeric', |
||||
|
timeZoneName: 'short' |
||||
|
}; |
||||
|
time.innerText = local.toLocaleString('en-US', options); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* On Page Ready */ |
||||
|
document.addEventListener("DOMContentLoaded", function(event) { |
||||
|
change_date_strings(); |
||||
|
}); |
||||
|
</script> |
||||
|
</html> |
@ -0,0 +1,205 @@ |
|||||
|
{% extends "layout.html" %} |
||||
|
|
||||
|
{% block left_top_bar %} |
||||
|
<button class="uk-button uk-button-default uk-button-small uk-border-rounded" |
||||
|
uk-toggle="target: #charts-container">Show/Hide Charts</button> |
||||
|
{% endblock %} |
||||
|
|
||||
|
{% block center_top_bar %} |
||||
|
Total Funds Earned: |
||||
|
<span id="total_funds_earned" class="uk-text-primary"></span> |
||||
|
{% endblock %} |
||||
|
|
||||
|
{% block content %} |
||||
|
<div id="charts-container" uk-grid hidden> |
||||
|
<div class="uk-width-1-2"> |
||||
|
<canvas id="sales-chart"></canvas> |
||||
|
</div> |
||||
|
<div class="uk-width-1-2"> |
||||
|
<canvas id="stock-chart"></canvas> |
||||
|
</div> |
||||
|
</div> |
||||
|
<table id="products" class="uk-table uk-table-small uk-table-striped uk-table-hover"> |
||||
|
<thead> |
||||
|
<tr> |
||||
|
<th class="uk-table-expand">Product Name</th> |
||||
|
<th>Current Stock</th> |
||||
|
<th>Total Stock</th> |
||||
|
<th>Total Sold</th> |
||||
|
<th>Total Earned</th> |
||||
|
<th>Latest Price</th> |
||||
|
<th>Last Update</th> |
||||
|
</tr> |
||||
|
</thead> |
||||
|
<tbody> |
||||
|
{% for product in products %} |
||||
|
{% if product.latest_entry != None %} |
||||
|
<tr> |
||||
|
<td class="uk-table-link"> |
||||
|
<a href="/shop/product/{{ product.id }}"> |
||||
|
{{ product.name }} |
||||
|
</a> |
||||
|
</td> |
||||
|
<td>{{ product.latest_entry.stock }}</td> |
||||
|
<td>{{ product.total_stocked }}</td> |
||||
|
<td>{{ product.total_sold }}</td> |
||||
|
<td>A${{ product.total_earned }}</td> |
||||
|
<td> |
||||
|
<span title="{{ product.latest_entry.raw_price}}"> |
||||
|
A${{ product.latest_entry.price }} |
||||
|
</span> |
||||
|
</td> |
||||
|
<td class="uk-text-nowrap">{{ product.latest_entry.date.isoformat() }}</td> |
||||
|
</tr> |
||||
|
{% endif %} |
||||
|
{% endfor %} |
||||
|
</tbody> |
||||
|
</table> |
||||
|
{% endblock %} |
||||
|
|
||||
|
{% block pagescripts %} |
||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.1/Chart.js"></script> |
||||
|
<script> |
||||
|
function highlight_empty_stock() { |
||||
|
var rows = document.querySelectorAll('table#products > tbody > tr > td:nth-child(2)'); |
||||
|
for(var row of rows) { |
||||
|
if(parseInt(row.textContent) == 0) { |
||||
|
row.parentElement.classList.add('uk-text-danger'); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function calculate_total_earnings() { |
||||
|
var total_earnings = 0.00; |
||||
|
var earnings = document.querySelectorAll('table#products > tbody > tr > td:nth-child(5)'); |
||||
|
for(var earning of earnings) { |
||||
|
total_earnings += parseFloat(earning.textContent.substring(2)) |
||||
|
} |
||||
|
document.getElementById('total_funds_earned').textContent = "A$" + total_earnings.toFixed(2); |
||||
|
} |
||||
|
|
||||
|
/* Chart.js */ |
||||
|
var item_labels = [ |
||||
|
{% for product in products %} |
||||
|
"{{ product.name }}", |
||||
|
{% endfor %} |
||||
|
]; |
||||
|
var sale_data = [ |
||||
|
{% for product in products %} |
||||
|
{{ product.total_sold }}, |
||||
|
{% endfor %} |
||||
|
]; |
||||
|
var earnings_data = [ |
||||
|
{% for product in products %} |
||||
|
{{ product.total_earned }}, |
||||
|
{% endfor %} |
||||
|
]; |
||||
|
var total_stock_data = [ |
||||
|
{% for product in products %} |
||||
|
{{ product.total_stocked }}, |
||||
|
{% endfor %} |
||||
|
]; |
||||
|
var current_stock_data = [ |
||||
|
{% for product in products %} |
||||
|
{{ product.latest_entry.stock }}, |
||||
|
{% endfor %} |
||||
|
]; |
||||
|
|
||||
|
function init_sales_chart(labels, sold_data, earned_data) { |
||||
|
var ctx = document.getElementById("sales-chart").getContext('2d'); |
||||
|
var chart = new Chart(ctx, { |
||||
|
type: 'horizontalBar', |
||||
|
data: { |
||||
|
labels: labels, |
||||
|
datasets: [ |
||||
|
{ |
||||
|
label: 'Sold', |
||||
|
backgroundColor: 'rgb(93, 173, 226)', |
||||
|
borderColor: 'rgb(89, 131, 227)', |
||||
|
data: sold_data, |
||||
|
xAxisID: 'quantity' |
||||
|
}, |
||||
|
{ |
||||
|
label: 'Earned', |
||||
|
backgroundColor: 'rgb(173, 93, 226)', |
||||
|
borderColor: 'rgb(131, 89, 227)', |
||||
|
data: earned_data, |
||||
|
xAxisID: 'currency' |
||||
|
} |
||||
|
], |
||||
|
}, |
||||
|
options: { |
||||
|
elements: { rectangle: { borderWidth: 2, } }, |
||||
|
responsive: true, |
||||
|
legend: { position: 'bottom', }, |
||||
|
title: { display: true, text: 'Sales & Earnings'}, |
||||
|
scales: { |
||||
|
xAxes: [ |
||||
|
{ |
||||
|
id: 'quantity', |
||||
|
type: 'linear', |
||||
|
position: 'top' |
||||
|
}, |
||||
|
{ |
||||
|
id: 'currency', |
||||
|
type: 'linear', |
||||
|
position: 'bottom' |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function init_stock_chart(labels, total_data, current_data) { |
||||
|
var ctx = document.getElementById("stock-chart").getContext('2d'); |
||||
|
var chart = new Chart(ctx, { |
||||
|
type: 'horizontalBar', |
||||
|
data: { |
||||
|
labels: labels, |
||||
|
datasets: [ |
||||
|
{ |
||||
|
label: 'Total', |
||||
|
backgroundColor: 'rgb(54, 84, 126)', |
||||
|
data: total_data, |
||||
|
xAxisID: 'quantity' |
||||
|
}, |
||||
|
{ |
||||
|
label: 'Current', |
||||
|
backgroundColor: 'rgb(73, 133, 226)', |
||||
|
data: current_data, |
||||
|
xAxisID: 'big_quantity' |
||||
|
} |
||||
|
], |
||||
|
}, |
||||
|
options: { |
||||
|
responsive: true, |
||||
|
legend: { position: 'bottom', }, |
||||
|
title: { display: true, text: 'Total/Current Stock'}, |
||||
|
scales: { |
||||
|
xAxes: [ |
||||
|
{ |
||||
|
id: 'quantity', |
||||
|
type: 'linear', |
||||
|
position: 'top' |
||||
|
}, |
||||
|
{ |
||||
|
id: 'big_quantity', |
||||
|
type: 'linear', |
||||
|
position: 'bottom' |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/* On Page Ready */ |
||||
|
document.addEventListener("DOMContentLoaded", function(event) { |
||||
|
highlight_empty_stock(); |
||||
|
calculate_total_earnings(); |
||||
|
init_sales_chart(item_labels, sale_data, earnings_data); |
||||
|
init_stock_chart(item_labels, total_stock_data, current_stock_data); |
||||
|
}); |
||||
|
</script> |
||||
|
{% endblock %} |
@ -0,0 +1,17 @@ |
|||||
|
{% extends "layout.html" %} |
||||
|
{% block content %} |
||||
|
<form id="mission-form" action="{{ url_for('shop_dataentry') }}" method="post" |
||||
|
class="uk-margin-large-left"> |
||||
|
<label for="mission-data">Enter shops "Mission Item" output:</label> <br/> |
||||
|
<textarea class="uk-textarea uk-form-small uk-form-width-large uk-margin-medium-bottom" |
||||
|
autofocus=true rows=15 name="mission-data"></textarea> |
||||
|
|
||||
|
<br/> |
||||
|
<label for="password">Password</label> |
||||
|
<input type="password" name="password" |
||||
|
class="uk-input uk-form-small uk-form-width-small uk-margin-medium-left"/> |
||||
|
<br/> |
||||
|
<input type="submit" value="Parse Entries" |
||||
|
class="uk-button uk-button-primary uk-margin-small-top uk-border-rounded"/> |
||||
|
</form> |
||||
|
{% endblock %} |
@ -0,0 +1,119 @@ |
|||||
|
{% extends "layout.html" %} |
||||
|
|
||||
|
{% block left_top_bar %} |
||||
|
<button class="uk-button uk-button-default uk-button-small uk-border-rounded" |
||||
|
uk-toggle="target: #charts-container">Show/Hide Charts</button> |
||||
|
{% endblock %} |
||||
|
|
||||
|
{% block center_top_bar %} |
||||
|
<h4 class="uk-text-center">{{ product.name }}</h4> |
||||
|
{% endblock %} |
||||
|
|
||||
|
{% block content %} |
||||
|
<div id="charts-container" uk-grid hidden> |
||||
|
<div class="uk-width-1-1"> |
||||
|
<canvas id="time-chart"></canvas> |
||||
|
</div> |
||||
|
</div> |
||||
|
<table id="products" class="uk-table uk-table-small uk-table-striped uk-table-hover uk-margin-large-left"> |
||||
|
<thead> |
||||
|
<tr> |
||||
|
<th>Stock</th> |
||||
|
<th>Price (Raw)</th> |
||||
|
<th>Price (Cvt)</th> |
||||
|
<th>Update</th> |
||||
|
</tr> |
||||
|
</thead> |
||||
|
<tbody> |
||||
|
{% for entry in product.entries %} |
||||
|
<tr> |
||||
|
<td>{{ entry.stock }}</td> |
||||
|
<td>{{ entry.raw_price }}</td> |
||||
|
<td>A${{ entry.price }}</td> |
||||
|
<td>{{ entry.date.isoformat() }}</td> |
||||
|
</tr> |
||||
|
{% endfor %} |
||||
|
</tbody> |
||||
|
<caption class="uk-margin-small-bottom">{{ product.entries | length }} total entries.</caption> |
||||
|
</table> |
||||
|
{% endblock %} |
||||
|
|
||||
|
{% block pagescripts %} |
||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.19.3/moment.min.js"></script> |
||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.1/Chart.js"></script> |
||||
|
<script> |
||||
|
/* Chart.js */ |
||||
|
var item_labels = [ |
||||
|
"{{ product.name }}", |
||||
|
]; |
||||
|
var time_data = [ |
||||
|
{% for entry in product.entries %} |
||||
|
{x: "{{ entry.date.isoformat() }}", y: {{ entry.stock }}}, |
||||
|
{% endfor %} |
||||
|
]; |
||||
|
|
||||
|
function init_time_chart(labels, timedata) { |
||||
|
var ctx = document.getElementById("time-chart").getContext('2d'); |
||||
|
var chart = new Chart(ctx, { |
||||
|
type: 'line', |
||||
|
data: { |
||||
|
labels: labels, |
||||
|
datasets: [ |
||||
|
{ |
||||
|
label: 'Entries', |
||||
|
backgroundColor: 'rgb(93, 173, 226)', |
||||
|
borderColor: 'rgb(89, 131, 227)', |
||||
|
fill: false, |
||||
|
data: timedata, |
||||
|
} |
||||
|
], |
||||
|
}, |
||||
|
options: { |
||||
|
responsive: true, |
||||
|
title: { display: true, text: 'Entries'}, |
||||
|
scales: { |
||||
|
xAxes: [ |
||||
|
{ |
||||
|
type: 'time', |
||||
|
distribution: 'series', |
||||
|
display: true, |
||||
|
scaleLabel: { |
||||
|
display: true, |
||||
|
labelString: 'Date' |
||||
|
}, |
||||
|
time: { |
||||
|
unit: 'day', |
||||
|
unitStepSize: 1, |
||||
|
displayFormats: { |
||||
|
day: 'MMM D YYYY h:mm a' |
||||
|
} |
||||
|
}, |
||||
|
ticks: { |
||||
|
major: { |
||||
|
fontStyle: "bold", |
||||
|
fontColor: "#FF0000" |
||||
|
}, |
||||
|
source: 'data' |
||||
|
} |
||||
|
} |
||||
|
], |
||||
|
yAxes: [ |
||||
|
{ |
||||
|
display: true, |
||||
|
scaleLabel: { |
||||
|
display: true, |
||||
|
labelString: 'Stock' |
||||
|
} |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/* On Page Ready */ |
||||
|
document.addEventListener("DOMContentLoaded", function(event) { |
||||
|
init_time_chart(item_labels, time_data); |
||||
|
}); |
||||
|
</script> |
||||
|
{% endblock %} |
@ -0,0 +1,36 @@ |
|||||
|
import re |
||||
|
|
||||
|
from flask import ( |
||||
|
request, |
||||
|
url_for, |
||||
|
redirect, |
||||
|
render_template |
||||
|
) |
||||
|
|
||||
|
from . import ( |
||||
|
db, |
||||
|
app, |
||||
|
models, |
||||
|
shop |
||||
|
) |
||||
|
|
||||
|
|
||||
|
@app.route('/') |
||||
|
def shop_dashboard(): |
||||
|
return render_template('shop_dashboard.html', |
||||
|
products=models.ShopProduct.query.all()) |
||||
|
|
||||
|
@app.route('/shop/data', methods=['POST','GET']) |
||||
|
def shop_dataentry(): |
||||
|
if request.method == 'POST': |
||||
|
if request.form.get('password') == 'r3m3di3s' and request.form.get('mission-data'): |
||||
|
data = request.form['mission-data'] |
||||
|
clean_data = data.replace('\r\n', '\n') |
||||
|
shop.parse_shop_output(clean_data) |
||||
|
return redirect(url_for('shop_dashboard')) |
||||
|
return render_template('shop_dataentry.html') |
||||
|
|
||||
|
@app.route('/shop/product/<int:product_id>') |
||||
|
def shop_product_entries(product_id): |
||||
|
product = models.ShopProduct.query.filter_by(id=product_id).first_or_404() |
||||
|
return render_template('shop_product_entries.html', product=product) |
@ -0,0 +1,11 @@ |
|||||
|
[uwsgi] |
||||
|
module = wsgi:application |
||||
|
|
||||
|
master = true |
||||
|
processes = 4 |
||||
|
|
||||
|
socket = discworld.sock |
||||
|
chmod-scoket = 660 |
||||
|
vacuum = true |
||||
|
|
||||
|
die-on-term = true |
@ -0,0 +1,4 @@ |
|||||
|
from discworld import app as application |
||||
|
|
||||
|
if __name__ == "__main__": |
||||
|
application.run() |
Write
Preview
Loading…
Cancel
Save
Reference in new issue