Skip to content

Commit

Permalink
Improve sell command and move two_column_view
Browse files Browse the repository at this point in the history
- Move all logic to ctrl.collection.sell_record_wizard()
- Never prompt for price beforehand
- Always show price suggestions and stats
- Fetching all price suggestions takes too much time, provide a
  "relevant-only-fetcher"
- Move rich two_column_view helper from Textual app to ViewCommon and
  use with sell command.
- Leave some unused imports in discogs.py...
  • Loading branch information
JOJ0 committed Nov 21, 2024
1 parent bab36ac commit 46ba905
Show file tree
Hide file tree
Showing 5 changed files with 148 additions and 105 deletions.
52 changes: 11 additions & 41 deletions discodos/cmd23/sell.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
@click.option(
"-p", "--price",
type=float,
prompt="Price (enter 0 to fetch Discogs suggested price)",
default=None,
help="Listing price for the record. Leave blank for suggested price."
)
Expand Down Expand Up @@ -88,44 +87,15 @@ def update_user_interaction_helper(user):
user.conf.discobase, user.conf.musicbrainz_user,
user.conf.musicbrainz_password)

if not coll_ctrl.ONLINE:
log.warning("Online mode is required to list a record for sale.")
return

if not release_id:
found_release = coll_ctrl.search_release(" ".join(query))
# search_release exits program, not required to handle here.
release_id = found_release["id"]

if not price:
suggested_price = coll_ctrl.collection.fetch_price_suggestion(
release_id, condition
)
if suggested_price:
click.echo(
f"Suggested price for condition '{condition}': "
f"{suggested_price.currency} {suggested_price.value}"
)
price = click.prompt(
"Accept?",
type=float,
default=round(suggested_price.value, 2),
)
else:
click.echo("No suggested price available; please enter a price manually.")
price = click.prompt("Price", type=float)

log.info(f"Attempting to list record {release_id} for sale.")
listing_successful = coll_ctrl.collection.list_for_sale(
release_id=release_id,
condition=condition,
sleeve_condition=sleeve_condition,
price=price,
status=status,
location=location,
allow_offers=allow_offers,
comments=comments,
private_comments=private_comments
coll_ctrl.sell_record_wizard(
query,
release_id,
condition,
sleeve_condition,
price,
status,
location,
allow_offers,
comments,
private_comments,
)
if listing_successful:
coll_ctrl.cli.p("Listed for sale.")
56 changes: 56 additions & 0 deletions discodos/ctrl/collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import discogs_client.exceptions as errors
from rich.progress import (BarColumn, MofNCompleteColumn, Progress,
TaskProgressColumn, SpinnerColumn, TimeElapsedColumn)
from rich import print
from rich.prompt import Prompt, FloatPrompt

from discodos.ctrl.common import ControlCommon
from discodos.model import Brainz
Expand Down Expand Up @@ -976,3 +978,57 @@ def tui_ls_releases(self, search_terms):
)
app.run(inline=False)
return

def sell_record_wizard(
self, query, release_id, condition, sleeve_condition, price, status, location,
allow_offers, comments, private_comments,
):
if not self.ONLINE:
log.warning("Online mode is required to list a record for sale.")
return

if not release_id:
found_release = self.search_release(" ".join(query))
# search_release exits program, not required to handle here.
release_id = found_release["id"]

suggested_p = self.collection.fetch_relevant_price_suggestions(release_id)
print("Suggested prices:")
print(self.cli.two_column_view(suggested_p, as_is=True))

stats = self.collection.fetch_marketplace_stats(release_id)
print("Marketplace stats:")
print(self.cli.two_column_view(stats))

print("Currently for sale:")
print(f"https://www.discogs.com/sell/release/{release_id}")

if not price:
recommended_price = suggested_p.get(condition)
if recommended_price:
print(
f"Suggested price for condition '{condition}': "
f"EUR {recommended_price}"
)
price = FloatPrompt.ask(
"Accept?",
default=recommended_price,
)
else:
print("No suggested price available; please enter a price manually.")
price = FloatPrompt.ask("Price")

log.info(f"Attempting to list record {release_id} for sale.")
listing_successful = self.collection.list_for_sale(
release_id=release_id,
condition=condition,
sleeve_condition=sleeve_condition,
price=price,
status=status,
location=location,
allow_offers=allow_offers,
comments=comments,
private_comments=private_comments
)
if listing_successful:
self.cli.p("Listed for sale.")
56 changes: 3 additions & 53 deletions discodos/ctrl/tui.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import logging
from sqlite3 import Row
from datetime import datetime
from rich.table import Table as rich_table
from rich.markdown import Markdown
from textual.app import App
from textual.containers import Horizontal, Vertical, VerticalScroll
from textual.widgets import DataTable, Digits, Footer, Label, Static, RichLog
Expand Down Expand Up @@ -157,7 +155,7 @@ def on_data_table_row_highlighted(self, event):
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.cli.two_column_view(listing, translate_keys=self.key_translation)
)
self._sales_digits_update(listing)
# Stats
Expand All @@ -172,13 +170,13 @@ def on_data_table_row_selected(self, event):
if listing_id:
listing = self.fetch_sales_listing_details(listing_id)
self.left_column_content.update(
self._two_column_view(listing, translate_keys=self.key_translation)
self.cli.two_column_view(listing, translate_keys=self.key_translation)
)
self._sales_digits_update(listing)
# Stats - we fetch always
release_id = self.table.get_cell(row_key, "release_id")
stats = self.fetch_marketplace_stats(release_id)
self.middle_column_content.update(self._two_column_view(stats))
self.middle_column_content.update(self.cli.two_column_view(stats))
rlog.write(
f"Updated price, marketplace stats and details of listing {listing_id} "
"with Discogs data."
Expand All @@ -195,54 +193,6 @@ def _load_rows_into_table(self):
for row_id, row in enumerate(self.rows):
table_widget.add_row(*row.values(), key=row_id)

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)
table.add_column("Field", style="cyan", justify="right")
table.add_column("Value", style="white")
# Display an empty table instead of nothing.
if not details_dict:
return table

# Highlight/fix/replace some values first
values_replaced = {}
for key, value in details_dict.items():
if key in ["d_sales_release_url", "d_sales_url"]:
value = Markdown(f"[View in browser]({value})")
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():
if isinstance(value, Markdown):
table.add_row(f"[bold]{key}[/bold]", value)
continue
# 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 _sales_digits_update(self, listing):
"""A Rich-formatted big digits view of the sales price.
"""
Expand Down
36 changes: 25 additions & 11 deletions discodos/model/discogs.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import time
import logging
from socket import gaierror
from rich.progress import Progress
import discogs_client
import discogs_client.exceptions
from discogs_client import Condition, Status
from discogs_client import Condition, Status, Release
from discogs_client.models import PriceSuggestions
import requests.exceptions
import urllib3.exceptions

Expand Down Expand Up @@ -201,17 +203,29 @@ def fetch_marketplace_stats(self, release_id):
return r if r else None

def fetch_price_suggestion(self, release_id, condition):
release = self.d.release(release_id)
r = {
"M": release.price_suggestions.mint,
"NM": release.price_suggestions.near_mint,
"VG+": release.price_suggestions.very_good_plus,
"VG": release.price_suggestions.very_good,
"G+": release.price_suggestions.good_plus,
"G": release.price_suggestions.good,
"F": release.price_suggestions.fair,
if isinstance(release_id, Release):
r = release_id
else:
r = self.d.release(release_id)

c = {
"M": r.price_suggestions.mint,
"NM": r.price_suggestions.near_mint,
"VG+": r.price_suggestions.very_good_plus,
"VG": r.price_suggestions.very_good,
"G+": r.price_suggestions.good_plus,
"G": r.price_suggestions.good,
"F": r.price_suggestions.fair,
}
return r[condition.upper()] if r else None
return c[condition.upper()] if r else None

def fetch_relevant_price_suggestions(self, release_id):
release = self.d.release(release_id)
suggestions = {}
for cond in ["M", "NM", "VG+", "VG"]:
price = self.fetch_price_suggestion(release, cond)
suggestions[cond] = round(price.value, 2)
return suggestions

def list_for_sale( # pylint: disable=too-many-positional-arguments,too-many-arguments
self,
Expand Down
53 changes: 53 additions & 0 deletions discodos/view/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
from time import time
from tabulate import tabulate as tab
from rich import print # pylint: disable=redefined-builtin
from rich.table import Table as rich_table
from rich.markdown import Markdown

from discodos.utils import is_number, join_sep

Expand Down Expand Up @@ -680,6 +682,57 @@ def edit_ask_details(self, orig_data, edit_questions): # pylint: disable=R0912,
log.debug("CTRL: _edit_ask_details: answers dict: {}".format(answers))
return answers

def two_column_view(self, details_dict, translate_keys=None, as_is=False):
"""A Rich-formatted view of keys and values.
- by default simply capitalizes key names...
- ... or not (set as_is!)
- 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)
table.add_column("Field", style="cyan", justify="right")
table.add_column("Value", style="white")
# Display an empty table instead of nothing.
if not details_dict:
return table

# Highlight/fix/replace some values first
values_replaced = {}
for key, value in details_dict.items():
if key in ["d_sales_release_url", "d_sales_url"]:
value = Markdown(f"[View in browser]({value})")
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 as_is:
final_details = values_replaced
elif 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():
if isinstance(value, Markdown):
table.add_row(f"[bold]{key}[/bold]", value)
continue
# 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 view_tutorial(self):
tutorial_items = [
'\n\nFirst things first: Whenever DiscoDOS asks you a question, '
Expand Down

0 comments on commit 46ba905

Please sign in to comment.