@@ -98,6 +98,7 @@ class CampaignDB: | |||
except ValueError: | |||
raise RuntimeError("Invalid kill_date=%s for kill_id=%d" % ( | |||
kill["killTime"], kill["killID"])) | |||
# ... Ensure IDs are all ints | |||
query = """INSERT OR REPLACE INTO kill ( | |||
kill_id, kill_date, kill_system, kill_victim_shipid, | |||
@@ -135,3 +136,34 @@ class CampaignDB: | |||
WHERE ok_campaign = ? AND ok_operation = ?""" | |||
res = self._conn.execute(query, (campaign, operation)).fetchall() | |||
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: | |||
logger.debug("Using cache (age=%d) for campaign=%s " | |||
"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 [] | |||
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): | |||
"""Return the correct form of the unit tracked by the given operation.""" | |||
@@ -1,5 +1,6 @@ | |||
Flask==0.11.1 | |||
Flask-Mako==0.4 | |||
humanize==0.5.1 | |||
PyYAML==3.12 | |||
requests==2.12.4 | |||
uWSGI==2.0.14 | |||
@@ -353,11 +353,11 @@ h2 .disabled { | |||
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 { | |||
@@ -1,10 +1,15 @@ | |||
<%! import humanize %> | |||
<%inherit file="../_default.mako"/> | |||
<%namespace file="renderers.mako" import="render_summary"/> | |||
<%block name="title"> | |||
${self.maketitle(campaign["title"], "Campaigns")} | |||
</%block> | |||
<h2> | |||
<span class="understate">Campaign:</span> | |||
<span${"" if enabled else ' class="disabled"'}>${campaign["title"] | h}</span> | |||
% if not enabled: | |||
<abbr class="disabled-info" title="Campaign inactive">✘</abbr> | |||
% endif | |||
</h2> | |||
<% mod = g.config.modules.campaigns %> | |||
<div id="operations"> | |||
@@ -15,8 +20,10 @@ | |||
<% | |||
operation = campaign["operations"][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" | |||
punit = mod.get_unit(operation, primary) | |||
sunit = mod.get_unit(operation, secondary, primary=False) | |||
%> | |||
<div class="operation"> | |||
<h3> | |||
@@ -25,21 +32,19 @@ | |||
<div class="overview"> | |||
<div class="primary"> | |||
<span class="num ${klass}">${"{:,}".format(primary)}</span> | |||
<div class="unit">${mod.get_unit(operation, primary)}</div> | |||
<div class="unit">${punit}</div> | |||
</div> | |||
% if secondary is not None: | |||
<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> | |||
% endif | |||
</div> | |||
% if summary: | |||
<ul class="summary"> | |||
% for item in summary: | |||
<li>${item}</li> | |||
% endfor | |||
</ul> | |||
${render_summary(renderer, summary)} | |||
% endif | |||
</div> | |||
% 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: | |||
<li> | |||
<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> | |||
% if member.roles: | |||
<span>${member.name}<em>${member.roles}</em></span> | |||