mirror of
https://github.com/wahyd4/links.git
synced 2026-08-08 21:04:53 +10:00
Update price monitor settings
This commit is contained in:
+9
-1
@@ -4,7 +4,15 @@ from pricemon.models import PriceAlert, PriceSnapshot, PriceWatcher
|
||||
|
||||
@admin.register(PriceWatcher)
|
||||
class PriceWatcherAdmin(admin.ModelAdmin):
|
||||
list_display = ['name', 'url', 'last_price', 'last_checked_at', 'enabled', 'check_interval_hours']
|
||||
list_display = [
|
||||
'name',
|
||||
'url',
|
||||
'initial_price',
|
||||
'last_price',
|
||||
'last_checked_at',
|
||||
'enabled',
|
||||
'check_interval_hours',
|
||||
]
|
||||
list_filter = ['enabled', 'check_interval_hours']
|
||||
search_fields = ['name', 'url']
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
def backfill_initial_price(apps, schema_editor):
|
||||
PriceWatcher = apps.get_model('pricemon', 'PriceWatcher')
|
||||
PriceSnapshot = apps.get_model('pricemon', 'PriceSnapshot')
|
||||
|
||||
for watcher in PriceWatcher.objects.all():
|
||||
if watcher.initial_price is not None:
|
||||
continue
|
||||
|
||||
snapshot = (
|
||||
PriceSnapshot.objects.filter(watcher_id=watcher.pk, price__isnull=False)
|
||||
.order_by('checked_at', 'pk')
|
||||
.first()
|
||||
)
|
||||
if snapshot is None:
|
||||
continue
|
||||
|
||||
watcher.initial_price = snapshot.price
|
||||
watcher.save(update_fields=['initial_price'])
|
||||
|
||||
|
||||
def noop_reverse(apps, schema_editor):
|
||||
pass
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('pricemon', '0004_alert_price_threshold_recurring'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='pricewatcher',
|
||||
name='initial_price',
|
||||
field=models.DecimalField(blank=True, decimal_places=2, max_digits=12, null=True),
|
||||
),
|
||||
migrations.RunPython(backfill_initial_price, noop_reverse),
|
||||
]
|
||||
+39
-2
@@ -1,3 +1,5 @@
|
||||
from decimal import Decimal, ROUND_HALF_UP
|
||||
|
||||
from django.db import models
|
||||
|
||||
|
||||
@@ -19,15 +21,22 @@ class PriceWatcher(models.Model):
|
||||
)
|
||||
check_interval_hours = models.IntegerField(choices=INTERVAL_CHOICES, default=24)
|
||||
alert_price_threshold = models.DecimalField(
|
||||
max_digits=12, decimal_places=2, null=True, blank=True,
|
||||
max_digits=12,
|
||||
decimal_places=2,
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text='Send an alert when the detected price is at or below this value. Leave blank to disable alerts.',
|
||||
)
|
||||
recurring_notification = models.BooleanField(
|
||||
default=True,
|
||||
help_text='Keep notifying on every check while the price meets the threshold. Disable to notify only once (until you dismiss the alert).',
|
||||
help_text=(
|
||||
'Keep notifying on every check while the price meets the threshold. '
|
||||
'Disable to notify only once (until you dismiss the alert).'
|
||||
),
|
||||
)
|
||||
enabled = models.BooleanField(default=True)
|
||||
last_checked_at = models.DateTimeField(null=True, blank=True)
|
||||
initial_price = models.DecimalField(max_digits=12, decimal_places=2, null=True, blank=True)
|
||||
last_price = models.DecimalField(max_digits=12, decimal_places=2, null=True, blank=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
@@ -37,6 +46,34 @@ class PriceWatcher(models.Model):
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
@property
|
||||
def price_change_direction(self):
|
||||
if self.initial_price is None or self.last_price is None:
|
||||
return None
|
||||
if self.last_price > self.initial_price:
|
||||
return 'up'
|
||||
if self.last_price < self.initial_price:
|
||||
return 'down'
|
||||
return 'flat'
|
||||
|
||||
@property
|
||||
def price_change_pct(self):
|
||||
if self.initial_price is None or self.last_price is None or self.initial_price == 0:
|
||||
return None
|
||||
pct = ((self.last_price - self.initial_price) / self.initial_price) * Decimal('100')
|
||||
return abs(pct).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
|
||||
|
||||
@property
|
||||
def price_change_symbol(self):
|
||||
direction = self.price_change_direction
|
||||
if direction == 'up':
|
||||
return '↑'
|
||||
if direction == 'down':
|
||||
return '↓'
|
||||
if direction == 'flat':
|
||||
return '→'
|
||||
return ''
|
||||
|
||||
|
||||
class PriceSnapshot(models.Model):
|
||||
watcher = models.ForeignKey(PriceWatcher, on_delete=models.CASCADE, related_name='snapshots')
|
||||
|
||||
@@ -53,6 +53,9 @@ def check_watcher(watcher_pk: int) -> None:
|
||||
|
||||
update_fields = ['last_checked_at']
|
||||
if price is not None:
|
||||
if watcher.initial_price is None:
|
||||
watcher.initial_price = price
|
||||
update_fields.append('initial_price')
|
||||
watcher.last_price = price
|
||||
update_fields.append('last_price')
|
||||
watcher.last_checked_at = now()
|
||||
|
||||
@@ -92,6 +92,29 @@
|
||||
{% else %}
|
||||
<span class="text-gray-400 text-lg">—</span>
|
||||
{% endif %}
|
||||
<div class="mt-1 text-xs text-gray-500">
|
||||
{% if watcher.initial_price %}
|
||||
<span class="block">{% trans "Initial" %} ${{ watcher.initial_price }}</span>
|
||||
{% endif %}
|
||||
{% if watcher.price_change_direction %}
|
||||
{% if watcher.price_change_direction == 'up' %}
|
||||
<span class="block text-red-600">
|
||||
<span aria-hidden="true">{{ watcher.price_change_symbol }}</span>
|
||||
{% trans "up" %} {{ watcher.price_change_pct }}%
|
||||
</span>
|
||||
{% elif watcher.price_change_direction == 'down' %}
|
||||
<span class="block text-green-600">
|
||||
<span aria-hidden="true">{{ watcher.price_change_symbol }}</span>
|
||||
{% trans "down" %} {{ watcher.price_change_pct }}%
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="block text-gray-500">
|
||||
<span aria-hidden="true">{{ watcher.price_change_symbol }}</span>
|
||||
0.00%
|
||||
</span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Actions row -->
|
||||
@@ -168,6 +191,29 @@
|
||||
{% else %}
|
||||
<span class="text-gray-400">—</span>
|
||||
{% endif %}
|
||||
<div class="mt-1 space-y-0.5 text-xs text-gray-500">
|
||||
{% if watcher.initial_price %}
|
||||
<div>{% trans "Initial" %} ${{ watcher.initial_price }}</div>
|
||||
{% endif %}
|
||||
{% if watcher.price_change_direction %}
|
||||
{% if watcher.price_change_direction == 'up' %}
|
||||
<div class="font-medium text-red-600">
|
||||
<span aria-hidden="true">{{ watcher.price_change_symbol }}</span>
|
||||
{% trans "up" %} {{ watcher.price_change_pct }}%
|
||||
</div>
|
||||
{% elif watcher.price_change_direction == 'down' %}
|
||||
<div class="font-medium text-green-600">
|
||||
<span aria-hidden="true">{{ watcher.price_change_symbol }}</span>
|
||||
{% trans "down" %} {{ watcher.price_change_pct }}%
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="font-medium text-gray-500">
|
||||
<span aria-hidden="true">{{ watcher.price_change_symbol }}</span>
|
||||
0.00%
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-4 py-3 text-gray-500">
|
||||
{% if watcher.last_checked_at %}
|
||||
|
||||
Reference in New Issue
Block a user