Published: Jan. 22, 2021 by lukemakin |  estimated reading time: 21 minutes
Amazon price tracker in python / django
Source code to the youtube tutorial series:

Building Amazon price tracker with Django and Beautiful Soup

The entire youtube playlist available under this link: https://www.youtube.com/watch?v=HBqYKthsB2E&list=PLgjw1dR712jqjwhvA2V6_DTVVKou_HHlW&index=1&t=3s

models.py

from django.db import models
from .utils import get_link_data

class Link(models.Model):
name = models.CharField(max_length=220, blank=True)
url = models.URLField()
current_price = models.FloatField(blank=True)
old_price = models.FloatField(default=0)
price_difference = models.FloatField(default=0)
updated = models.DateTimeField(auto_now=True)
created = models.DateTimeField(auto_now_add=True)

def __str__(self):
return str(self.name)

class Meta:
ordering = ('price_difference', '-created')

def save(self, *args, **kwargs):
name, price = get_link_data(self.url)
old_price = self.current_price
if self.current_price:
if price != old_price:
diff = price - old_price
self.price_difference = round(diff, 2)
self.old_price = old_price
else:
self.old_price = 0
self.price_difference = 0

self.name = name
self.current_price = price

super().save(*args, **kwargs)



utils.py

import requests
import lxml
from bs4 import BeautifulSoup

def get_link_data(url):
headers = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.193 Safari/537.36",
"Accept-Language": "en-GB,en-US;q=0.9,en;q=0.8"
}

response = requests.get(url, headers=headers)

soup = BeautifulSoup(response.text, "lxml")

name = soup.select_one(selector="#productTitle").getText()
name = name.strip()

price = soup.select_one(selector="#priceblock_ourprice").getText()
price = float(price[1:])

return name, price

views.py


from django.shortcuts import render, redirect
from django.urls import reverse_lazy
from .models import Link
from .forms import AddLinkForm
from django.views.generic import DeleteView

def home_view(request):
no_discounted = 0
error = None

form = AddLinkForm(request.POST or None)

if request.method == 'POST':
try:
if form.is_valid():
form.save()
except AttributeError:
error = "Ups ... couldn't get the name or the price"
except:
error = "Ups ... something went wrong"

form = AddLinkForm()

qs = Link.objects.all()
items_no = qs.count()

if items_no > 0:
discount_list = []
for item in qs:
if item.old_price > item.current_price:
discount_list.append(item)
no_discounted = len(discount_list)

context = {
'qs': qs,
'items_no': items_no,
'no_discounted': no_discounted,
'form': form,
'error': error,
}

return render(request, 'links/main.html', context)

class LinkDeleteView(DeleteView):
model = Link
template_name = 'links/confirm_del.html'
success_url = reverse_lazy('home')

def update_prices(request):
qs = Link.objects.all()
for link in qs:
link.save()
return redirect('home')

forms.py


from django import forms
from .models import Link

class AddLinkForm(forms.ModelForm):
class Meta:
model = Link
fields = ('url', )

templatetags/colorize.py


from django import template
from django.utils.safestring import mark_safe

register = template.Library()

@register.filter
def colorize(val):
mark = str(val)[:1]
if mark == "-":
html_string = f"<span style='color:green'>{val}</span>"
elif mark == "0":
html_string = f"<span style='color:blue'>{val}</span>"
else:
html_string = f"<span style='color:red'>{val}</span>"
return mark_safe(html_string)

base.html


<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">

<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous">

<title>My amazon price tracker</title>
</head>
<body>
<div class="container mt-3">
{% block content %}
{% endblock content %}
</div>

<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/js/bootstrap.bundle.min.js" integrity="sha384-ygbV9kiqUc6oa4msXn9868pTtWMgiQaeYH7/t7LECLbyPA2x65Kgf80OJFdroafW" crossorigin="anonymous"></script>
</body>
</html>

main.html


{% extends "base.html" %}
{% load crispy_forms_tags %}
{% load colorize %}
{% block content %}
<!-- Modal -->
<div class="modal fade" id="addItemModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Add an item for tracking</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form action="" method="POST" autocomplete="off">
{% csrf_token %}
{{form|crispy}}
<button type="submit" class="btn btn-primary mt-2">Save</button>
</form>
</div>
</div>
</div>
</div>

<div class="row">
<div class="col">
<button class="btn btn-outline-primary" data-bs-toggle="modal" data-bs-target="#addItemModal">Add</button>
<a href="{% url 'update-prices' %}">
<button class="btn btn-info">Update</button>
</a>
</div>
<div class="col" style="text-align:right">
<div>Total number of items being tracked: {{items_no}}</div>
<div>Discounted items: {{no_discounted}}</div>
</div>
</div>
{% if qs %}
{% for item in qs %}
<div class="card mt-3">
<div class="card-header">
<h5>{{item.name}}</h5>
<a href="{% url 'delete' item.pk %}"><button class="btn btn-danger">delete?</button></a>
</div>
<div class="card-body">
<div class="blockquote">
<div>current price ($): {{item.current_price}}</div>
<div>old price ($): {{item.old_price}}</div>
<div>difference ($): {{item.price_difference|colorize}}</div>
<div>link: <a href="{{item.url}}">{{item.url}}</a></div>
</div>
</div>
</div>
{% endfor %}
{% else %}
<h3> No items being tracked ... </h3>
{% endif %}
{% endblock content %}

confirm_del.html


{% extends "base.html" %}

{% block content %}
<form method="POST" action="">
{% csrf_token %}
<p>Are you sure you want to dekete the link: "{{object.name}}"?</p>
<button type="submit" class="btn btn-primary">yes</button>
</form>
{% endblock content %}

urls.py


from django.contrib import admin
from django.urls import path
from links.views import home_view, update_prices, LinkDeleteView

urlpatterns = [
path('admin/', admin.site.urls),
path('', home_view, name='home'),
path('update/', update_prices, name='update-prices'),
path('delete/<pk>/', LinkDeleteView.as_view(), name="delete"),
]







 
Extras
To view additional content login or create a free account
Categories:
Share your thoughts

mahdi
2 weeks, 4 days ago
Your tutorials are excellent. Thank you very much.
Signup to the newsletter
To get the latest updates from pyplane
© copyright 2019 pyplane.com