Improve price information
This commit is contained in:
parent
4ec808eeec
commit
6f62889e92
|
@ -11,6 +11,8 @@ class GamesConfig(AppConfig):
|
||||||
name = "games"
|
name = "games"
|
||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
|
import games.signals # noqa: F401
|
||||||
|
|
||||||
post_migrate.connect(schedule_tasks, sender=self)
|
post_migrate.connect(schedule_tasks, sender=self)
|
||||||
|
|
||||||
|
|
||||||
|
@ -26,6 +28,14 @@ def schedule_tasks(sender, **kwargs):
|
||||||
next_run=now() + timedelta(seconds=30),
|
next_run=now() + timedelta(seconds=30),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if not Schedule.objects.filter(name="Update price per game").exists():
|
||||||
|
schedule(
|
||||||
|
"games.tasks.calculate_price_per_game",
|
||||||
|
name="Update price per game",
|
||||||
|
schedule_type=Schedule.MINUTES,
|
||||||
|
next_run=now() + timedelta(seconds=30),
|
||||||
|
)
|
||||||
|
|
||||||
from games.models import ExchangeRate
|
from games.models import ExchangeRate
|
||||||
|
|
||||||
if not ExchangeRate.objects.exists():
|
if not ExchangeRate.objects.exists():
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 5.1.5 on 2025-01-30 11:04
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('games', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='purchase',
|
||||||
|
name='price_per_game',
|
||||||
|
field=models.FloatField(null=True),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 5.1.5 on 2025-01-30 11:12
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('games', '0002_purchase_price_per_game'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='purchase',
|
||||||
|
name='updated_at',
|
||||||
|
field=models.DateTimeField(auto_now=True),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,28 @@
|
||||||
|
# Generated by Django 5.1.5 on 2025-01-30 11:57
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
from django.db.models import Count
|
||||||
|
|
||||||
|
|
||||||
|
def initialize_num_purchases(apps, schema_editor):
|
||||||
|
Purchase = apps.get_model("games", "Purchase")
|
||||||
|
purchases = Purchase.objects.annotate(num_games=Count("games"))
|
||||||
|
|
||||||
|
for purchase in purchases:
|
||||||
|
purchase.num_purchases = purchase.num_games
|
||||||
|
purchase.save(update_fields=["num_purchases"])
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("games", "0003_purchase_updated_at"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="purchase",
|
||||||
|
name="num_purchases",
|
||||||
|
field=models.IntegerField(default=0),
|
||||||
|
),
|
||||||
|
migrations.RunPython(initialize_num_purchases),
|
||||||
|
]
|
|
@ -116,6 +116,8 @@ class Purchase(models.Model):
|
||||||
price_currency = models.CharField(max_length=3, default="USD")
|
price_currency = models.CharField(max_length=3, default="USD")
|
||||||
converted_price = models.FloatField(null=True)
|
converted_price = models.FloatField(null=True)
|
||||||
converted_currency = models.CharField(max_length=3, null=True)
|
converted_currency = models.CharField(max_length=3, null=True)
|
||||||
|
price_per_game = models.FloatField(null=True)
|
||||||
|
num_purchases = models.IntegerField(default=0)
|
||||||
ownership_type = models.CharField(
|
ownership_type = models.CharField(
|
||||||
max_length=2, choices=OWNERSHIP_TYPES, default=DIGITAL
|
max_length=2, choices=OWNERSHIP_TYPES, default=DIGITAL
|
||||||
)
|
)
|
||||||
|
@ -130,19 +132,16 @@ class Purchase(models.Model):
|
||||||
related_name="related_purchases",
|
related_name="related_purchases",
|
||||||
)
|
)
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def standardized_price(self):
|
def standardized_price(self):
|
||||||
return (
|
return (
|
||||||
f"{floatformat(self.converted_price)} {self.converted_currency}"
|
f"{floatformat(self.converted_price, 0)} {self.converted_currency}"
|
||||||
if self.converted_price
|
if self.converted_price
|
||||||
else None
|
else None
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
|
||||||
def num_purchases(self):
|
|
||||||
return self.games.count()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def has_one_item(self):
|
def has_one_item(self):
|
||||||
return self.games.count() == 1
|
return self.games.count() == 1
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
from django.db.models.signals import m2m_changed
|
||||||
|
from django.dispatch import receiver
|
||||||
|
from django.utils.timezone import now
|
||||||
|
|
||||||
|
from games.models import Purchase
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(m2m_changed, sender=Purchase.games.through)
|
||||||
|
def update_num_purchases(sender, instance, **kwargs):
|
||||||
|
instance.num_purchases = instance.games.count()
|
||||||
|
instance.updated_at = now()
|
||||||
|
instance.save(update_fields=["num_purchases"])
|
|
@ -1,4 +1,8 @@
|
||||||
import requests
|
import requests
|
||||||
|
from django.db.models import ExpressionWrapper, F, FloatField, Q
|
||||||
|
from django.template.defaultfilters import floatformat
|
||||||
|
from django.utils.timezone import now
|
||||||
|
from django_q.models import Task
|
||||||
|
|
||||||
from games.models import ExchangeRate, Purchase
|
from games.models import ExchangeRate, Purchase
|
||||||
|
|
||||||
|
@ -51,7 +55,7 @@ def convert_prices():
|
||||||
currency_from=currency_from,
|
currency_from=currency_from,
|
||||||
currency_to=currency_to,
|
currency_to=currency_to,
|
||||||
year=year,
|
year=year,
|
||||||
rate=rate,
|
rate=floatformat(rate, 2),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
print("Could not get an exchange rate.")
|
print("Could not get an exchange rate.")
|
||||||
|
@ -61,5 +65,24 @@ def convert_prices():
|
||||||
)
|
)
|
||||||
if exchange_rate:
|
if exchange_rate:
|
||||||
save_converted_info(
|
save_converted_info(
|
||||||
purchase, purchase.price * exchange_rate.rate, currency_to
|
purchase,
|
||||||
|
floatformat(purchase.price * exchange_rate.rate, 0),
|
||||||
|
currency_to,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_price_per_game():
|
||||||
|
try:
|
||||||
|
last_task = Task.objects.filter(group="Update price per game").first()
|
||||||
|
last_run = last_task.started
|
||||||
|
except Task.DoesNotExist or AttributeError:
|
||||||
|
last_run = now()
|
||||||
|
purchases = Purchase.objects.filter(converted_price__isnull=False).filter(
|
||||||
|
Q(updated_at__gte=last_run) | Q(price_per_game__isnull=True)
|
||||||
|
)
|
||||||
|
print(f"Updating {purchases.count()} purchases.")
|
||||||
|
purchases.update(
|
||||||
|
price_per_game=ExpressionWrapper(
|
||||||
|
F("converted_price") / F("num_purchases"), output_field=FloatField()
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
<span title="Price is a result of conversion and rounding." class="decoration-dotted underline">{{ slot }}</span>
|
|
@ -20,13 +20,12 @@
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
<p>
|
||||||
Price:
|
Price:
|
||||||
{% if purchase.converted_price %}
|
<c-price-converted>{{ purchase.standardized_price }}</c-price-converted>
|
||||||
{{ purchase.converted_price | floatformat }} {{ purchase.converted_currency }}
|
({{ purchase.price | floatformat:2 }} {{ purchase.price_currency }})
|
||||||
{% else %}
|
</p>
|
||||||
None
|
<p>Price per game: <c-price-converted>{{ purchase.price_per_game | floatformat:0 }} {{ purchase.converted_currency }}</c-price-converted> </p>
|
||||||
{% endif %}
|
|
||||||
({{ purchase.price | floatformat }} {{ purchase.price_currency }})
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2 class="text-base">Items:</h2>
|
<h2 class="text-base">Items:</h2>
|
||||||
|
|
|
@ -47,7 +47,7 @@ INSTALLED_APPS = [
|
||||||
|
|
||||||
Q_CLUSTER = {
|
Q_CLUSTER = {
|
||||||
"name": "DjangoQ",
|
"name": "DjangoQ",
|
||||||
"workers": 4,
|
"workers": 1,
|
||||||
"recycle": 500,
|
"recycle": 500,
|
||||||
"timeout": 60,
|
"timeout": 60,
|
||||||
"retry": 120,
|
"retry": 120,
|
||||||
|
|
Loading…
Reference in New Issue