mirror of
https://github.com/wahyd4/links.git
synced 2026-08-09 05:06:16 +10:00
173 lines
6.3 KiB
Python
173 lines
6.3 KiB
Python
import logging
|
|
|
|
from django.contrib import messages
|
|
from django.http import HttpResponse, JsonResponse
|
|
from django.shortcuts import get_object_or_404, redirect, render
|
|
from django.views import View
|
|
from django.views.generic import TemplateView
|
|
|
|
from pricemon.forms import PriceWatcherForm
|
|
from pricemon.models import PriceAlert, PriceSnapshot, PriceWatcher
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class DashboardView(TemplateView):
|
|
template_name = 'pricemon/dashboard.html'
|
|
|
|
def get_context_data(self, **kwargs):
|
|
ctx = super().get_context_data(**kwargs)
|
|
watchers = PriceWatcher.objects.all()
|
|
active_alerts = PriceAlert.objects.filter(dismissed=False).select_related('watcher')
|
|
ctx['watchers'] = watchers
|
|
ctx['total_watchers'] = watchers.count()
|
|
ctx['enabled_watchers_count'] = watchers.filter(enabled=True).count()
|
|
ctx['active_alerts_count'] = active_alerts.count()
|
|
ctx['active_alerts'] = active_alerts[:10]
|
|
return ctx
|
|
|
|
|
|
class WatcherCreateView(View):
|
|
template_name = 'pricemon/watcher_form.html'
|
|
|
|
def get(self, request):
|
|
return render(request, self.template_name, {'form': PriceWatcherForm(), 'title': 'Add Watcher'})
|
|
|
|
def post(self, request):
|
|
form = PriceWatcherForm(request.POST)
|
|
if form.is_valid():
|
|
watcher = form.save()
|
|
messages.success(request, f'Watcher "{watcher.name}" created.')
|
|
return redirect('pricemon-dashboard')
|
|
return render(request, self.template_name, {'form': form, 'title': 'Add Watcher'})
|
|
|
|
|
|
class WatcherEditView(View):
|
|
template_name = 'pricemon/watcher_form.html'
|
|
|
|
def get(self, request, pk):
|
|
watcher = get_object_or_404(PriceWatcher, pk=pk)
|
|
return render(request, self.template_name, {
|
|
'form': PriceWatcherForm(instance=watcher),
|
|
'watcher': watcher,
|
|
'title': f'Edit — {watcher.name}',
|
|
})
|
|
|
|
def post(self, request, pk):
|
|
watcher = get_object_or_404(PriceWatcher, pk=pk)
|
|
form = PriceWatcherForm(request.POST, instance=watcher)
|
|
if form.is_valid():
|
|
form.save()
|
|
messages.success(request, f'Watcher "{watcher.name}" updated.')
|
|
return redirect('pricemon-dashboard')
|
|
return render(request, self.template_name, {
|
|
'form': form,
|
|
'watcher': watcher,
|
|
'title': f'Edit — {watcher.name}',
|
|
})
|
|
|
|
|
|
class WatcherDeleteView(View):
|
|
def post(self, request, pk):
|
|
watcher = get_object_or_404(PriceWatcher, pk=pk)
|
|
name = watcher.name
|
|
watcher.delete()
|
|
messages.success(request, f'Watcher "{name}" deleted.')
|
|
return redirect('pricemon-dashboard')
|
|
|
|
|
|
class WatcherToggleEnabledView(View):
|
|
def post(self, request, pk):
|
|
watcher = get_object_or_404(PriceWatcher, pk=pk)
|
|
watcher.enabled = not watcher.enabled
|
|
watcher.save(update_fields=['enabled'])
|
|
state = 'enabled' if watcher.enabled else 'disabled'
|
|
messages.success(request, f'Watcher "{watcher.name}" {state}.')
|
|
return redirect('pricemon-dashboard')
|
|
|
|
|
|
class WatcherHistoryView(TemplateView):
|
|
template_name = 'pricemon/history.html'
|
|
|
|
def get_context_data(self, **kwargs):
|
|
ctx = super().get_context_data(**kwargs)
|
|
watcher = get_object_or_404(PriceWatcher, pk=kwargs['pk'])
|
|
ctx['watcher'] = watcher
|
|
ctx['snapshots'] = watcher.snapshots.all()[:200]
|
|
return ctx
|
|
|
|
|
|
class CheckNowView(View):
|
|
def post(self, request, pk):
|
|
from pricemon.tasks import check_watcher
|
|
watcher = get_object_or_404(PriceWatcher, pk=pk)
|
|
try:
|
|
check_watcher(watcher.pk)
|
|
watcher.refresh_from_db()
|
|
msg = f'Check complete. Current price: ${watcher.last_price}' if watcher.last_price else 'Check complete (price not detected).'
|
|
if request.headers.get('HX-Request'):
|
|
return JsonResponse({'ok': True, 'message': msg})
|
|
messages.success(request, msg)
|
|
except Exception as exc:
|
|
logger.error(f'Manual check failed for watcher {pk}: {exc}')
|
|
if request.headers.get('HX-Request'):
|
|
return JsonResponse({'ok': False, 'message': str(exc)}, status=500)
|
|
messages.error(request, f'Check failed: {exc}')
|
|
return redirect('pricemon-dashboard')
|
|
|
|
|
|
class AlertsPartialView(View):
|
|
def get(self, request):
|
|
alerts = PriceAlert.objects.filter(dismissed=False).select_related('watcher')[:20]
|
|
return render(request, 'pricemon/_alerts.html', {'alerts': alerts})
|
|
|
|
|
|
class DismissAlertView(View):
|
|
def post(self, request, pk):
|
|
alert = get_object_or_404(PriceAlert, pk=pk)
|
|
alert.dismissed = True
|
|
alert.save(update_fields=['dismissed'])
|
|
if request.headers.get('HX-Request'):
|
|
return HttpResponse('')
|
|
return redirect('pricemon-dashboard')
|
|
|
|
|
|
class ChartDataView(View):
|
|
def get(self, request, pk):
|
|
watcher = get_object_or_404(PriceWatcher, pk=pk)
|
|
snapshots = (
|
|
watcher.snapshots
|
|
.filter(price__isnull=False)
|
|
.values('checked_at', 'price')
|
|
.order_by('checked_at')[:500]
|
|
)
|
|
labels = [s['checked_at'].strftime('%Y-%m-%d %H:%M') for s in snapshots]
|
|
prices = [float(s['price']) for s in snapshots]
|
|
return JsonResponse({'labels': labels, 'prices': prices, 'name': watcher.name})
|
|
|
|
|
|
class TestSelectorView(View):
|
|
def post(self, request):
|
|
from pricemon.scraper import _fetch_with_requests, _fetch_with_playwright, parse_price
|
|
url = request.POST.get('url', '').strip()
|
|
css_selector = request.POST.get('css_selector', '').strip()
|
|
|
|
if not url or not css_selector:
|
|
return render(request, 'pricemon/_selector_test_result.html', {
|
|
'error': 'Both URL and CSS selector are required.',
|
|
})
|
|
|
|
raw_text, error = _fetch_with_requests(url, css_selector)
|
|
method = 'requests'
|
|
if not raw_text and not error:
|
|
raw_text, error = _fetch_with_playwright(url, css_selector)
|
|
method = 'playwright'
|
|
|
|
price = parse_price(raw_text) if raw_text else None
|
|
return render(request, 'pricemon/_selector_test_result.html', {
|
|
'raw_text': raw_text,
|
|
'price': price,
|
|
'error': error,
|
|
'method': method,
|
|
})
|