@@ -98,6 +98,7 @@ class CampaignDB: | |||||
except ValueError: | except ValueError: | ||||
raise RuntimeError("Invalid kill_date=%s for kill_id=%d" % ( | raise RuntimeError("Invalid kill_date=%s for kill_id=%d" % ( | ||||
kill["killTime"], kill["killID"])) | kill["killTime"], kill["killID"])) | ||||
# ... Ensure IDs are all ints | |||||
query = """INSERT OR REPLACE INTO kill ( | query = """INSERT OR REPLACE INTO kill ( | ||||
kill_id, kill_date, kill_system, kill_victim_shipid, | kill_id, kill_date, kill_system, kill_victim_shipid, | ||||
@@ -135,3 +136,34 @@ class CampaignDB: | |||||
WHERE ok_campaign = ? AND ok_operation = ?""" | WHERE ok_campaign = ? AND ok_operation = ?""" | ||||
res = self._conn.execute(query, (campaign, operation)).fetchall() | res = self._conn.execute(query, (campaign, operation)).fetchall() | ||||
return tuple(res[0]) | return tuple(res[0]) | ||||
def get_associated_kills(self, campaign, operation, limit=5, offset=0): | |||||
"""Return a list of kills associated with a campaign/operation. | |||||
Kills are returned as dictionaries most recent first, up to a limit. | |||||
Use -1 for no limit. | |||||
""" | |||||
if not isinstance(limit, int): | |||||
raise ValueError(limit) | |||||
if not isinstance(offset, int): | |||||
raise ValueError(offset) | |||||
query = """SELECT kill_id, kill_date, kill_system, kill_victim_shipid, | |||||
kill_victim_charid, kill_victim_corpid, kill_victim_allianceid, | |||||
kill_victim_factionid, kill_value | |||||
FROM oper_kill | |||||
JOIN kill ON ok_killid = kill_id | |||||
WHERE ok_campaign = ? AND ok_operation = ? | |||||
ORDER BY ok_killid DESC LIMIT {} OFFSET {}""" | |||||
qform = query.format(limit, offset) | |||||
res = self._conn.execute(qform, (campaign, operation)).fetchall() | |||||
return [{ | |||||
"id": row[0], | |||||
"date": datetime.strptime(row[1], "%Y-%m-%d %H:%M:%S"), | |||||
"system": row[2], | |||||
"victim": { | |||||
"ship_id": row[3], "char_id": row[4], "corp_id": row[5], | |||||
"alliance_id": row[6], "faction_id": row[7]}, | |||||
"value": row[8] | |||||
} for row in res] |
@@ -53,12 +53,19 @@ def get_overview(cname, opname): | |||||
else: | else: | ||||
logger.debug("Using cache (age=%d) for campaign=%s " | logger.debug("Using cache (age=%d) for campaign=%s " | ||||
"operation=%s", age, cname, opname) | "operation=%s", age, cname, opname) | ||||
return g.campaign_db.get_overview(cname, opname) | |||||
return g.campaign_db.get_overview(cname, opname) | |||||
def get_summary(name, opname, limit=5): | |||||
def get_summary(cname, opname, limit=5): | |||||
"""Return a sample fraction of results for the given campaign/operation.""" | """Return a sample fraction of results for the given campaign/operation.""" | ||||
... | |||||
return [] | |||||
optype = config["campaigns"][cname]["operations"][opname]["type"] | |||||
if optype == "killboard": | |||||
kills = g.campaign_db.get_associated_kills(cname, opname, limit=limit) | |||||
return kills, "killboard_recent" | |||||
elif optype == "collection": | |||||
... | |||||
return [], None | |||||
else: | |||||
raise RuntimeError("Unknown operation type: %s" % optype) | |||||
def get_unit(operation, num, primary=True): | def get_unit(operation, num, primary=True): | ||||
"""Return the correct form of the unit tracked by the given operation.""" | """Return the correct form of the unit tracked by the given operation.""" | ||||
@@ -1,5 +1,6 @@ | |||||
Flask==0.11.1 | Flask==0.11.1 | ||||
Flask-Mako==0.4 | Flask-Mako==0.4 | ||||
humanize==0.5.1 | |||||
PyYAML==3.12 | PyYAML==3.12 | ||||
requests==2.12.4 | requests==2.12.4 | ||||
uWSGI==2.0.14 | uWSGI==2.0.14 | ||||
@@ -353,11 +353,11 @@ h2 .disabled { | |||||
text-decoration: line-through; | text-decoration: line-through; | ||||
} | } | ||||
h2 .disabled::after { | |||||
display: inline-block; | |||||
padding-left: 0.75em; | |||||
font-size: 75%; | |||||
content: "✘"; | |||||
h2 .disabled-info { | |||||
margin-left: 0.5em; | |||||
color: #989898; | |||||
font-size: 85%; | |||||
font-variant: none; | |||||
} | } | ||||
#operations { | #operations { | ||||
@@ -1,10 +1,15 @@ | |||||
<%! import humanize %> | |||||
<%inherit file="../_default.mako"/> | <%inherit file="../_default.mako"/> | ||||
<%namespace file="renderers.mako" import="render_summary"/> | |||||
<%block name="title"> | <%block name="title"> | ||||
${self.maketitle(campaign["title"], "Campaigns")} | ${self.maketitle(campaign["title"], "Campaigns")} | ||||
</%block> | </%block> | ||||
<h2> | <h2> | ||||
<span class="understate">Campaign:</span> | <span class="understate">Campaign:</span> | ||||
<span${"" if enabled else ' class="disabled"'}>${campaign["title"] | h}</span> | <span${"" if enabled else ' class="disabled"'}>${campaign["title"] | h}</span> | ||||
% if not enabled: | |||||
<abbr class="disabled-info" title="Campaign inactive">✘</abbr> | |||||
% endif | |||||
</h2> | </h2> | ||||
<% mod = g.config.modules.campaigns %> | <% mod = g.config.modules.campaigns %> | ||||
<div id="operations"> | <div id="operations"> | ||||
@@ -15,8 +20,10 @@ | |||||
<% | <% | ||||
operation = campaign["operations"][opname] | operation = campaign["operations"][opname] | ||||
primary, secondary = mod.get_overview(name, opname) | primary, secondary = mod.get_overview(name, opname) | ||||
summary = mod.get_summary(name, opname, limit=5) | |||||
summary, renderer = mod.get_summary(name, opname, limit=5) | |||||
klass = "big" if primary < 1000 else "medium" if primary < 1000000 else "small" | klass = "big" if primary < 1000 else "medium" if primary < 1000000 else "small" | ||||
punit = mod.get_unit(operation, primary) | |||||
sunit = mod.get_unit(operation, secondary, primary=False) | |||||
%> | %> | ||||
<div class="operation"> | <div class="operation"> | ||||
<h3> | <h3> | ||||
@@ -25,21 +32,19 @@ | |||||
<div class="overview"> | <div class="overview"> | ||||
<div class="primary"> | <div class="primary"> | ||||
<span class="num ${klass}">${"{:,}".format(primary)}</span> | <span class="num ${klass}">${"{:,}".format(primary)}</span> | ||||
<div class="unit">${mod.get_unit(operation, primary)}</div> | |||||
<div class="unit">${punit}</div> | |||||
</div> | </div> | ||||
% if secondary is not None: | % if secondary is not None: | ||||
<div class="secondary"> | <div class="secondary"> | ||||
<span class="num">${"{:,.2f}".format(secondary)}</span> | |||||
<span class="unit">${mod.get_unit(operation, secondary, primary=False)}</span> | |||||
<abbr title="${"{:,.2f}".format(secondary)} ${sunit}"> | |||||
<span class="num">${humanize.intword(secondary) | h}</span> | |||||
<span class="unit">${sunit}</span> | |||||
</abbr> | |||||
</div> | </div> | ||||
% endif | % endif | ||||
</div> | </div> | ||||
% if summary: | % if summary: | ||||
<ul class="summary"> | |||||
% for item in summary: | |||||
<li>${item}</li> | |||||
% endfor | |||||
</ul> | |||||
${render_summary(renderer, summary)} | |||||
% endif | % endif | ||||
</div> | </div> | ||||
% endfor | % endfor | ||||
@@ -0,0 +1,25 @@ | |||||
<%! import humanize %> | |||||
<%def name="_killboard_recent(summary)"> | |||||
<ul class="summary"> | |||||
% for kill in summary: | |||||
<li> | |||||
<a href="https://zkillboard.com/kill/${kill['id']}/">${kill["id"]}</a> | |||||
${kill["system"]} | |||||
<abbr title="${kill["date"].strftime("%Y-%m-%d %H:%M")}">${humanize.naturaltime(kill["date"]) | h}</abbr> | |||||
<img src="${g.eve.image.render(kill["victim"]["ship_id"], 128)}"/> | |||||
<img src="${g.eve.image.character(kill["victim"]["char_id"], 128)}"/> | |||||
<img src="${g.eve.image.corp(kill["victim"]["corp_id"], 128)}"/> | |||||
<img src="${g.eve.image.alliance(kill["victim"]["alliance_id"], 128)}"/> | |||||
<img src="${g.eve.image.faction(kill["victim"]["faction_id"], 128)}"/> | |||||
<abbr title="${"{:,.2f}".format(kill["value"])} ISK">${humanize.intword(kill["value"]) | h} ISK</abbr> | |||||
</li> | |||||
% endfor | |||||
</ul> | |||||
</%def> | |||||
<%def name="render_summary(renderer, summary)"><% | |||||
if renderer == "killboard_recent": | |||||
return _killboard_recent(summary) | |||||
else: | |||||
raise RuntimeError("Unknown renderer: %s" % renderer) | |||||
%></%def> |
@@ -8,7 +8,7 @@ | |||||
% for member in members: | % for member in members: | ||||
<li> | <li> | ||||
<a href="${g.eve.image.character(member.id, 1024)}"> | <a href="${g.eve.image.character(member.id, 1024)}"> | ||||
<img class="styled-border" title="${member.name}" alt="${member.name}'s Portrait" src="${g.eve.image.character(member.id, 256)}"/> | |||||
<img class="styled-border" title="${member.name}" alt="${member.name}'s Portrait" src="${g.eve.image.character(member.id, 128)}"/> | |||||
</a> | </a> | ||||
% if member.roles: | % if member.roles: | ||||
<span>${member.name}<em>${member.roles}</em></span> | <span>${member.name}<em>${member.roles}</em></span> | ||||