From 9bd51d92674f806588760ea9f9036c2971163157 Mon Sep 17 00:00:00 2001 From: Evgeniy Alekseev Date: Sun, 8 Sep 2019 03:06:31 +0300 Subject: [PATCH] some html implementation --- src/service/api/routes.py | 15 +++- src/service/api/views/api/player.py | 2 +- src/service/api/views/common/player_base.py | 2 +- src/service/api/views/html/bis.py | 80 +++++++++++++++++++++ src/service/api/views/html/index.py | 18 +++++ src/service/api/views/html/player.py | 66 +++++++++++++++++ src/service/api/web.py | 4 ++ src/service/models/piece.py | 12 +++- src/service/models/player.py | 35 ++++++--- templates/bis.jinja2 | 67 +++++++++++++++++ templates/error.jinja2 | 3 + templates/export_to_csv.jinja2 | 35 +++++++++ templates/index.jinja2 | 13 ++++ templates/party.jinja2 | 53 ++++++++++++++ templates/root.jinja2 | 1 + templates/search.jinja2 | 23 ++++++ templates/search_line.jinja2 | 3 + templates/style.jinja2 | 6 ++ 18 files changed, 425 insertions(+), 13 deletions(-) create mode 100644 src/service/api/views/html/bis.py create mode 100644 src/service/api/views/html/index.py create mode 100644 src/service/api/views/html/player.py create mode 100644 templates/bis.jinja2 create mode 100644 templates/error.jinja2 create mode 100644 templates/export_to_csv.jinja2 create mode 100644 templates/index.jinja2 create mode 100644 templates/party.jinja2 create mode 100644 templates/root.jinja2 create mode 100644 templates/search.jinja2 create mode 100644 templates/search_line.jinja2 create mode 100644 templates/style.jinja2 diff --git a/src/service/api/routes.py b/src/service/api/routes.py index 9bf9957..c0bc1b9 100644 --- a/src/service/api/routes.py +++ b/src/service/api/routes.py @@ -11,9 +11,13 @@ from aiohttp.web import Application from service.api.views.api.bis import BiSView from service.api.views.api.loot import LootView from service.api.views.api.player import PlayerView +from service.api.views.html.bis import BiSHtmlView +from service.api.views.html.index import IndexHtmlView +from service.api.views.html.player import PlayerHtmlView def setup_routes(app: Application) -> None: + # api routes app.router.add_get('/api/v1/party', PlayerView) app.router.add_post('/api/v1/party', PlayerView) @@ -23,4 +27,13 @@ def setup_routes(app: Application) -> None: app.router.add_get('/api/v1/party/loot', LootView) app.router.add_post('/api/v1/party/loot', LootView) - app.router.add_put('/api/v1/party/loot', LootView) \ No newline at end of file + app.router.add_put('/api/v1/party/loot', LootView) + + # html routes + app.router.add_get('/', IndexHtmlView) + + app.router.add_get('/party', PlayerHtmlView) + app.router.add_post('/party', PlayerHtmlView) + + app.router.add_get('/bis', BiSHtmlView) + app.router.add_post('/bis', BiSHtmlView) diff --git a/src/service/api/views/api/player.py b/src/service/api/views/api/player.py index 7fc4dfd..89c1986 100644 --- a/src/service/api/views/api/player.py +++ b/src/service/api/views/api/player.py @@ -21,7 +21,7 @@ class PlayerView(PlayerBaseView): party = self.player_get(self.request.query.getone('nick', None)) except Exception as e: - self.request.app.logger.exception('could not get loot') + self.request.app.logger.exception('could not get party') return wrap_exception(e, self.request.query) return wrap_json(party, self.request.query) diff --git a/src/service/api/views/common/player_base.py b/src/service/api/views/common/player_base.py index f07af31..361707e 100644 --- a/src/service/api/views/common/player_base.py +++ b/src/service/api/views/common/player_base.py @@ -22,7 +22,7 @@ class PlayerBaseView(View): player_id = player.player_id self.request.app['party'].set_player(player) - if link is not None: + if link: parser = AriyalaParser(self.request.app['config']) items = parser.get(link) for piece in items: diff --git a/src/service/api/views/html/bis.py b/src/service/api/views/html/bis.py new file mode 100644 index 0000000..6944415 --- /dev/null +++ b/src/service/api/views/html/bis.py @@ -0,0 +1,80 @@ +# +# Copyright (c) 2019 Evgeniy Alekseev. +# +# This file is part of ffxivbis +# (see https://github.com/arcan1s/ffxivbis). +# +# License: 3-clause BSD, see https://opensource.org/licenses/BSD-3-Clause +# +from aiohttp.web import HTTPFound, Response +from aiohttp_jinja2 import template +from typing import Any, Dict, List + +from service.models.job import Job +from service.models.piece import Piece +from service.models.player import PlayerId + +from service.api.utils import wrap_exception, wrap_invalid_param, wrap_json +from service.api.views.common.bis_base import BiSBaseView +from service.api.views.common.player_base import PlayerBaseView + + +class BiSHtmlView(BiSBaseView, PlayerBaseView): + + @template('bis.jinja2') + async def get(self) -> Dict[str, Any]: + items: List[Dict[str, str]] = [] + error = None + + try: + players = self.player_get(None) + items = [ + { + 'player': player.player_id.pretty_name, + 'piece': piece.name, + 'is_tome': 'yes' if piece.is_tome else 'no' + } + for player in players + for piece in player.bis.pieces + ] + + except Exception as e: + self.request.app.logger.exception('could not get bis') + error = repr(e) + + return { + 'pieces': Piece.available(), + 'players': items, + 'request_error': error + } + + async def post(self) -> Response: + data = await self.request.post() + + required = ['method', 'player'] + if any(param not in data for param in required): + return wrap_invalid_param(required, data) + + try: + method = data.get('method') + player_id = PlayerId.from_pretty_name(data.get('player')) + + if method == 'post': + required = ['action', 'piece'] + if any(param not in data for param in required): + return wrap_invalid_param(required, data) + self.bis_post(data.get('action'), player_id, + Piece.get({'piece': data.get('piece'), 'is_tome': data.get('is_tome', False)})) + + elif method == 'put': + required = ['bis'] + if any(param not in data for param in required): + return wrap_invalid_param(required, data) + + self.bis_put(player_id, data.get('bis')) + + except Exception as e: + self.request.app.logger.exception('could not manage bis') + return wrap_exception(e, data) + + return HTTPFound(self.request.url) \ No newline at end of file diff --git a/src/service/api/views/html/index.py b/src/service/api/views/html/index.py new file mode 100644 index 0000000..76a73b0 --- /dev/null +++ b/src/service/api/views/html/index.py @@ -0,0 +1,18 @@ +# +# Copyright (c) 2019 Evgeniy Alekseev. +# +# This file is part of ffxivbis +# (see https://github.com/arcan1s/ffxivbis). +# +# License: 3-clause BSD, see https://opensource.org/licenses/BSD-3-Clause +# +from aiohttp.web import View +from aiohttp_jinja2 import template +from typing import Any, Dict + + +class IndexHtmlView(View): + + @template('index.jinja2') + async def get(self) -> Dict[str, Any]: + return {} diff --git a/src/service/api/views/html/player.py b/src/service/api/views/html/player.py new file mode 100644 index 0000000..8310456 --- /dev/null +++ b/src/service/api/views/html/player.py @@ -0,0 +1,66 @@ +# +# Copyright (c) 2019 Evgeniy Alekseev. +# +# This file is part of ffxivbis +# (see https://github.com/arcan1s/ffxivbis). +# +# License: 3-clause BSD, see https://opensource.org/licenses/BSD-3-Clause +# +from aiohttp.web import HTTPFound, Response +from aiohttp_jinja2 import template +from typing import Any, Dict, List + +from service.models.job import Job +from service.models.player import PlayerIdWithCounters + +from service.api.utils import wrap_exception, wrap_invalid_param +from service.api.views.common.player_base import PlayerBaseView + + +class PlayerHtmlView(PlayerBaseView): + + @template('party.jinja2') + async def get(self) -> Dict[str, Any]: + counters: List[PlayerIdWithCounters] = [] + error = None + + try: + party = self.player_get(None) + counters = [player.player_id_with_counters(None) for player in party] + + except Exception as e: + self.request.app.logger.exception('could not get party') + error = repr(e) + + return { + 'players': [ + { + 'job': player.job.name, + 'nick': player.nick, + 'loot_count_bis': player.loot_count_bis, + 'loot_count': player.loot_count, + 'priority': player.priority + } + for player in counters + ], + 'request_error': error + } + + async def post(self) -> Response: + data = await self.request.post() + + required = ['action', 'job', 'nick'] + if any(param not in data for param in required): + return wrap_invalid_param(required, data) + + try: + action = data.get('action') + priority = data.get('priority', 0) + link = data.get('bis', None) + self.player_post(action, Job[data['job'].upper()], data['nick'], link, priority) + + except Exception as e: + self.request.app.logger.exception('could not manage players') + return wrap_exception(e, data) + + return HTTPFound(self.request.url) \ No newline at end of file diff --git a/src/service/api/web.py b/src/service/api/web.py index 7ea7ebf..682577b 100644 --- a/src/service/api/web.py +++ b/src/service/api/web.py @@ -6,6 +6,8 @@ # # License: 3-clause BSD, see https://opensource.org/licenses/BSD-3-Clause # +import aiohttp_jinja2 +import jinja2 import logging from aiohttp import web @@ -38,6 +40,8 @@ def setup_service(config: Configuration, database: Database, loot: LootSelector, # routes app.logger.info('setup routes') setup_routes(app) + if config.has_option('web', 'templates'): + aiohttp_jinja2.setup(app, loader=jinja2.FileSystemLoader(config.get('web', 'templates'))) app.logger.info('setup configuration') app['config'] = config diff --git a/src/service/models/piece.py b/src/service/models/piece.py index 7068085..f5a6f63 100644 --- a/src/service/models/piece.py +++ b/src/service/models/piece.py @@ -9,7 +9,7 @@ from __future__ import annotations from dataclasses import dataclass -from typing import Any, Mapping, Type, Union +from typing import Any, List, Mapping, Type, Union from .upgrade import Upgrade @@ -33,11 +33,19 @@ class Piece: return Upgrade.GearUpgrade return Upgrade.NoUpgrade + @staticmethod + def available() -> List[str]: + return [ + 'weapon', + 'head', 'body', 'hands', 'waist', 'legs', 'feet', + 'ears', 'neck', 'wrist', 'left_ring', 'right_ring' + ] + @classmethod def get(cls: Type[Piece], data: Mapping[str, Any]) -> Union[Piece, Upgrade]: try: piece_type = data['piece'] - is_tome = bool(data['is_tome']) + is_tome = data['is_tome'] in ('yes', 'on', '1', 1, True) except KeyError: raise InvalidDataRow(data) if piece_type.lower() == 'weapon': diff --git a/src/service/models/player.py b/src/service/models/player.py index 9979feb..07f8ec1 100644 --- a/src/service/models/player.py +++ b/src/service/models/player.py @@ -6,8 +6,12 @@ # # License: 3-clause BSD, see https://opensource.org/licenses/BSD-3-Clause # +from __future__ import annotations + +import re + from dataclasses import dataclass -from typing import List, Optional, Union +from typing import List, Optional, Type, Union from .bis import BiS from .job import Job @@ -20,12 +24,22 @@ class PlayerId: job: Job nick: str + @property + def pretty_name(self) -> str: + return '{} ({})'.format(self.nick, self.job.name) + + @classmethod + def from_pretty_name(cls: Type[PlayerId], value: str) -> PlayerId: + matches = re.search('^(?P.*) \((?P[A-Z]+)\)$', value) + return PlayerId(Job[matches.group('job')], matches.group('nick')) + def __hash__(self) -> int: return hash(str(self)) @dataclass class PlayerIdWithCounters(PlayerId): + priority: int loot_count: int loot_count_bis: int loot_count_total: int @@ -50,12 +64,15 @@ class Player: def player_id(self) -> PlayerId: return PlayerId(self.job, self.nick) - def player_id_with_counters(self, piece: Union[Piece, Upgrade]) -> PlayerIdWithCounters: - return PlayerIdWithCounters(self.job, self.nick, self.loot_count(piece), + def player_id_with_counters(self, piece: Union[Piece, Upgrade, None]) -> PlayerIdWithCounters: + return PlayerIdWithCounters(self.job, self.nick, self.priority, self.loot_count(piece), self.loot_count_bis(piece), self.loot_count_total(piece)) # ordering methods - def is_required(self, piece: Union[Piece, Upgrade]) -> bool: + def is_required(self, piece: Union[Piece, Upgrade, None]) -> bool: + if piece is None: + return False + # lets check if it is even in bis if not self.bis.has_piece(piece): return False @@ -68,14 +85,16 @@ class Player: return self.bis.upgrades_required[piece] > self.loot_count(piece) return False - def loot_count(self, piece: Union[Piece, Upgrade]) -> int: + def loot_count(self, piece: Union[Piece, Upgrade, None]) -> int: + if piece is None: + return len(self.loot) return self.loot.count(piece) - def loot_count_bis(self, _: Union[Piece, Upgrade]) -> int: + def loot_count_bis(self, _: Union[Piece, Upgrade, None]) -> int: return len([piece for piece in self.loot if self.bis.has_piece(piece)]) - def loot_count_total(self, _: Union[Piece, Upgrade]) -> int: + def loot_count_total(self, _: Union[Piece, Upgrade, None]) -> int: return len(self.loot) - def loot_priority(self, _: Union[Piece, Upgrade]) -> int: + def loot_priority(self, _: Union[Piece, Upgrade, None]) -> int: return self.priority diff --git a/templates/bis.jinja2 b/templates/bis.jinja2 new file mode 100644 index 0000000..2ea7dcf --- /dev/null +++ b/templates/bis.jinja2 @@ -0,0 +1,67 @@ + + + Best in slot + + {% include "style.jinja2" %} + + +

best in slot

+ + {% include "error.jinja2" %} + {% include "search_line.jinja2" %} + +
+ + + + + + +
+
+ + + + +
+ + + + + + + + + + {% for player in players %} + + + + + + + {% endfor %} +
playerpieceis_tome
{{ player.is_tome|e }} +
+ + +
+
+ + {% include "export_to_csv.jinja2" %} + {% include "root.jinja2" %} + {% include "search.jinja2" %} + + diff --git a/templates/error.jinja2 b/templates/error.jinja2 new file mode 100644 index 0000000..40716b8 --- /dev/null +++ b/templates/error.jinja2 @@ -0,0 +1,3 @@ +{% if request_error is defined and request_error is not none %} +

Error occurs: {{ request_error|e }}

+{% endif %} diff --git a/templates/export_to_csv.jinja2 b/templates/export_to_csv.jinja2 new file mode 100644 index 0000000..9087d5d --- /dev/null +++ b/templates/export_to_csv.jinja2 @@ -0,0 +1,35 @@ + + + diff --git a/templates/index.jinja2 b/templates/index.jinja2 new file mode 100644 index 0000000..a5bce83 --- /dev/null +++ b/templates/index.jinja2 @@ -0,0 +1,13 @@ + + + FFXIV loot helper + + + +
+

party

+

bis

+

loot

+
+ + diff --git a/templates/party.jinja2 b/templates/party.jinja2 new file mode 100644 index 0000000..b91758d --- /dev/null +++ b/templates/party.jinja2 @@ -0,0 +1,53 @@ + + + Party + + {% include "style.jinja2" %} + + +

party

+ + {% include "error.jinja2" %} + {% include "search_line.jinja2" %} + +
+ + + + + + +
+ + + + + + + + + + + + {% for player in players %} + + + + + + + + + {% endfor %} +
nickjobbis pieces lootedtotal pieces lootedpriority
{{ player.loot_count_bis|e }}{{ player.loot_count|e }}{{ player.priority|e }} +
+ + +
+
+ + {% include "export_to_csv.jinja2" %} + {% include "root.jinja2" %} + {% include "search.jinja2" %} + + diff --git a/templates/root.jinja2 b/templates/root.jinja2 new file mode 100644 index 0000000..4d63855 --- /dev/null +++ b/templates/root.jinja2 @@ -0,0 +1 @@ +

root

\ No newline at end of file diff --git a/templates/search.jinja2 b/templates/search.jinja2 new file mode 100644 index 0000000..4fd7810 --- /dev/null +++ b/templates/search.jinja2 @@ -0,0 +1,23 @@ + diff --git a/templates/search_line.jinja2 b/templates/search_line.jinja2 new file mode 100644 index 0000000..ccf4288 --- /dev/null +++ b/templates/search_line.jinja2 @@ -0,0 +1,3 @@ +
+ +
diff --git a/templates/style.jinja2 b/templates/style.jinja2 new file mode 100644 index 0000000..30da561 --- /dev/null +++ b/templates/style.jinja2 @@ -0,0 +1,6 @@ +