| @@ -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> | |||