Skip to content

Commit

Permalink
Marketplace listing details from DB or Discogs
Browse files Browse the repository at this point in the history
- Collection object now passed to DiscodosListApp to make DB queries
  possible.
- Rename prettifier function to _two_column_view. It might later
  transform to a ViewCommon* function.
- Marketplace stats are still only available when hitting enter (fetches
  from Discogs). Fixed "lowest price" display.
- Start an initiative to rename actual Discogs fetchers to "fetch_" and
  name DB selects "get_".
- Only log when something was actually fetched with hitting "Enter".
- For now hardcoded self.key_translation map for prettified sales
  listing details and marketplace stats info panel. Usable with DB as
  well as Discogs data. The prerequsite though is equally named
  dictionary keys (use d_sales...)
  • Loading branch information
JOJ0 committed Nov 10, 2024
1 parent 1a44493 commit c4caecc
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 42 deletions.
3 changes: 2 additions & 1 deletion discodos/ctrl/collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -944,7 +944,8 @@ def tui_ls_releases(self, search_terms):
"forsale": "For Sale",
"status": "Status"
},
discogs=self.d
discogs=self.d,
collection=self.collection,
)
app.run(inline=False)
return
111 changes: 87 additions & 24 deletions discodos/ctrl/tui.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import logging
from sqlite3 import Row
from datetime import datetime
from rich.table import Table as rich_table
from textual.app import App
from textual.containers import Horizontal, Vertical, VerticalScroll
Expand All @@ -22,13 +23,14 @@ class DiscodosListApp(App, DiscogsMixin): # pylint: disable=too-many-instance-a
("E", "edit_sale", "Edit sales listing"),
]

def __init__(self, rows, headers, discogs=None):
def __init__(self, rows, headers, discogs=None, collection=None):
super().__init__()
super().discogs_connect(
user_token=None,
app_identifier=None,
discogs=discogs,
)
self.collection = collection
self.table = None
self.rows = rows
self.headers = headers
Expand All @@ -38,7 +40,29 @@ def __init__(self, rows, headers, discogs=None):
self.left_column_content = None
self.middle_column_content = None
self.right_column_content = None
# Content that can be fetched from DB as well as from Discogs
self.sales_price = None
self.sales_listing_details = None
# Content only available from Discogs
self.marketplace_stats = None
# Hardcoded column translations
self.key_translation = {
"d_sales_listing_id": "Listing ID",
"d_sales_release_id": "Release ID",
"d_sales_release_url": "Release URL",
"d_sales_url": "Listing URL",
"d_sales_condition": "Condition",
"d_sales_sleeve_condition": "Sleeve Condition",
"d_sales_price": "Price",
"d_sales_comments": "Comments",
"d_sales_allow_offers": "Allow Offers",
"d_sales_status": "Status",
"d_sales_comments_private": "Comments",
"d_sales_counts_as": "Counts as",
"d_sales_location": "Location Comments",
"d_sales_weight": "Weight",
"d_sales_posted": "Listed on",
}

def action_toggle_dark(self):
self.dark = not self.dark
Expand Down Expand Up @@ -72,42 +96,81 @@ def on_mount(self):
self.sub_title = "Use keystrokes to edit/sell/view details, ..."
self._load_ls_results()

def _two_column_rich_table(self, listing_details):
# Create a rich Table
def _two_column_view(self, details_dict, translate_keys=None):
"""A Rich-formatted view of keys and values.
- by default simply capitalizes key names
- optionally alters key names via a passed translaton table
We use it for Marketplace stats and Marketplace listing details.
"""
# Create a rich Table with two columns.
table = rich_table(box=None)
# Add columns for the table
table.add_column("Field", style="cyan", justify="right")
table.add_column("Value", style="white")
if not listing_details:
# Display an empty table instead of nothing.
if not details_dict:
return table
# Add rows with capitalized keys and their corresponding values
for key, value in listing_details.items():
if key == "status":
if value == "Sold":
table.add_row(
f"[bold]{key.capitalize()}[/bold]",
f"[magenta]{value}[/magenta]"
)
continue
table.add_row(f"[bold]{key.capitalize()}[/bold]", str(value))

# Highlight/fix/replace some values first
values_replaced = {}
for key, value in details_dict.items():
if key == "d_sales_allow_offers":
value = "Yes" if value in [1, True] else "No"
elif key == "status" and value == "Sold":
value = f"[magenta]{value}[/magenta]"
elif key == "d_sales_posted" and isinstance(value, datetime):
value = datetime.strftime(value, "%Y-%m-%d")
values_replaced[key] = value

# Prettify column captions
if translate_keys:
final_details = {
translate_keys.get(k, k): v for k, v in values_replaced.items()
}
else: # Without a tranlation table, fall back to simply capitalizing
final_details = {
k.capitalize(): v for k, v in values_replaced.items()
}

# The final creation of the Rich table
for key, value in final_details.items():
# Format key bold and value normal font (or as we manipulated it above)
table.add_row(f"[bold]{key}[/bold]", str(value))
return table

def on_data_table_row_highlighted(self, event):
"""Get DB listing details and Marketplace stats for highlighted row."""
row_key = event.row_key
# Listing
listing_id = self.table.get_cell(row_key, "forsale")
listing = self.collection.get_sales_listing_details(listing_id)
self.left_column_content.update(
self._two_column_view(listing, translate_keys=self.key_translation)
)
self.sales_price.update(str(listing["d_sales_price"]))
# Stats
self.middle_column_content.update("Press enter to fetch!")

def on_data_table_row_selected(self, event):
"""Fetch Discogs listing details and Marketplace stats for selected row."""
rlog = self.query_one(RichLog)
row_key = event.row_key
# Listing
listing_id = self.table.get_cell(row_key, "forsale")
rlog.write("Fetching Discogs Marketplace listing.")
listing = self.get_sales_listing_details(listing_id)
rlog.write("Done.")
self.left_column_content.update(self._two_column_rich_table(listing))
self.sales_price.update(listing['price'])
listing = self.fetch_sales_listing_details(listing_id)
self.left_column_content.update(
self._two_column_view(listing, translate_keys=self.key_translation)
)
self.sales_price.update(str(listing["d_sales_price"]))
# Stats
release_id = self.table.get_cell(row_key, "release_id")
rlog.write("Fetching Discogs Marketplace stats.")
stats = self.get_marketplace_stats(release_id)
rlog.write("Done.")
self.middle_column_content.update(self._two_column_rich_table(stats))
stats = self.fetch_marketplace_stats(release_id)
self.middle_column_content.update(self._two_column_view(stats))
rlog.write(
f"Updated price, marketplace stats and details of listing {listing_id} "
"with Discogs data."
)

def compose(self):
# The main data widget
Expand Down
29 changes: 29 additions & 0 deletions discodos/model/collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -653,3 +653,32 @@ def key_value_search_releases(
for row in rows
]
return human_readable_rows

def get_sales_listing_details(self, listing_id):
"""Get Marketplace listing details from DB if already imported.
Always returns a dict, not Row.
"""
where = f"d_sales_listing_id == {listing_id}"

rows = self._select_simple(
[
"d_sales_release_id",
"d_sales_release_url",
"d_sales_url",
"d_sales_condition",
"d_sales_sleeve_condition",
"d_sales_price",
"d_sales_comments",
"d_sales_allow_offers",
"d_sales_status",
"d_sales_comments_private",
"d_sales_counts_as",
"d_sales_location",
"d_sales_weight",
"d_sales_posted",
],
"sales",
fetchone=True, condition=where, as_dict=True
)
return rows
34 changes: 17 additions & 17 deletions discodos/model/discogs.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,31 +157,31 @@ def stats_releases_d_collection_online(self):
log.error("%s (Exception)", Exc)
return count

def get_sales_listing_details(self, listing_id):
def fetch_sales_listing_details(self, listing_id):
"""Fetches details like price for a Discogs marketplace listing."""
listing = self.d.listing(listing_id)
l = {
"url": listing.url,
#"release_id": listing.release.id,
#"release_url": listing.release.url,
"condition": listing.condition,
"sleeve_condition": listing.sleeve_condition,
"price": str(listing.price.value),
"comments": listing.comments,
"allow_offers": "Yes" if listing.allow_offers else "No",
"status": listing.status,
"comments_private": listing.external_id,
"counts_as": str(listing.format_quantity),
"location": listing.location,
"weight": str(listing.weight),
"posted": datetime.strftime(listing.posted, "%Y-%m-%d"),
"d_sales_release_id": listing.release.id,
"d_sales_release_url": listing.release.url,
"d_sales_url": listing.url,
"d_sales_condition": listing.condition,
"d_sales_sleeve_condition": listing.sleeve_condition,
"d_sales_price": str(listing.price.value),
"d_sales_comments": listing.comments,
"d_sales_allow_offers": listing.allow_offers,
"d_sales_status": listing.status,
"d_sales_comments_private": listing.external_id,
"d_sales_counts_as": str(listing.format_quantity),
"d_sales_location": listing.location,
"d_sales_weight": str(listing.weight),
"d_sales_posted": listing.posted,
}
return l if l else None

def get_marketplace_stats(self, release_id):
def fetch_marketplace_stats(self, release_id):
release = self.d.release(release_id)
r = {
"lowest_price": str(release.marketplace_stats.lowest_price),
"lowest_price": str(release.marketplace_stats.lowest_price.value),
"num_for_sale": str(release.marketplace_stats.num_for_sale),
"blocked_from_sale": str(release.marketplace_stats.blocked_from_sale),
}
Expand Down

0 comments on commit c4caecc

Please sign in to comment.