Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file removed .env
Empty file.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
*.env
*.pyc
__pycache__/
14 changes: 14 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"python.analysis.extraPaths": [
"./webscraper/ABC"
],
"python.testing.unittestArgs": [
"-v",
"-s",
"./webscraper",
"-p",
"*test*.py"
],
"python.testing.pytestEnabled": false,
"python.testing.unittestEnabled": true
}
Binary file removed accounts/__pycache__/__init__.cpython-312.pyc
Binary file not shown.
Binary file removed accounts/__pycache__/admin.cpython-312.pyc
Binary file not shown.
Binary file removed accounts/__pycache__/apps.cpython-312.pyc
Binary file not shown.
Binary file removed accounts/__pycache__/models.cpython-312.pyc
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Generated by Django 5.2 on 2025-05-05 19:14

import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("accounts", "0001_initial"),
]

operations = [
migrations.RemoveField(
model_name="product",
name="name",
),
migrations.RemoveField(
model_name="product",
name="source_url",
),
migrations.RemoveField(
model_name="useraccount",
name="password",
),
migrations.AddField(
model_name="product",
name="product_name",
field=models.CharField(default="Unnamed Product", max_length=255),
),
migrations.AddField(
model_name="product",
name="url",
field=models.TextField(default="https://example.com"),
),
migrations.AddField(
model_name="product",
name="user",
field=models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
to="accounts.useraccount",
),
),
migrations.AddField(
model_name="useraccount",
name="password_hash",
field=models.CharField(default="defaultpass123", max_length=100),
),
migrations.AlterField(
model_name="product",
name="price",
field=models.DecimalField(decimal_places=2, default=0.0, max_digits=10),
),
]
Binary file not shown.
Binary file not shown.
12 changes: 7 additions & 5 deletions accounts/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,20 @@ def validate_email(value):

class UserAccount(models.Model):
email = models.EmailField(max_length=50, unique=True)
password = models.CharField(max_length=100)
password_hash = models.CharField(max_length=100, default='defaultpass123') # added default
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Concern: Using a default plaintext password ('defaultpass123') is insecure.

Suggestion: Avoid setting a default password, especially one that’s hardcoded and predictable. If necessary for testing, override it in fixtures or during test setup instead.


def clean(self):
validate_email(self.email)

def __str__(self):
return self.email


class Product(models.Model):
name = models.CharField(max_length=200)
price = models.CharField(max_length=10)
source_url = models.URLField(max_length=150)
product_name = models.CharField(max_length=255, default='Unnamed Product')
price = models.DecimalField(max_digits=10, decimal_places=2, default=0.00)
url = models.TextField(default='https://example.com')
user = models.ForeignKey(UserAccount, on_delete=models.CASCADE, null=True) # optional if user not yet created

def __str__(self):
return self.name
return self.product_name
7 changes: 7 additions & 0 deletions accounts/views.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
from django.shortcuts import render
from django.http import JsonResponse
from .models import Product

def product_list(request):
products = Product.objects.all()
data = [{"name": p.product_name, "price": float(p.price), "url": p.url} for p in products]
return JsonResponse(data, safe=False)

# Create your views here.
Binary file removed cheaper/__pycache__/__init__.cpython-312.pyc
Binary file not shown.
Binary file removed cheaper/__pycache__/settings.cpython-312.pyc
Binary file not shown.
Binary file removed cheaper/__pycache__/urls.cpython-312.pyc
Binary file not shown.
2 changes: 2 additions & 0 deletions cheaper/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
"""
from django.contrib import admin
from django.urls import path
from accounts.views import product_list

urlpatterns = [
path('admin/', admin.site.urls),
path('', product_list, name='product_list'), # This sets the homepage
]
Binary file modified db.sqlite3
Binary file not shown.
15 changes: 15 additions & 0 deletions webscraper/ABC/Ebay_API.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from abc import ABC,abstractmethod

class EbayApi(ABC):

@abstractmethod
def retrieve_access_token() -> str:
""" retrieves the user access token for sandbox environment it's a long line
of text, numbers, symbols
"""
pass

@abstractmethod
def retrieve_ebay_response(httprequest:str,query:str) -> dict:
""" retrieves a json of large data with category ids, names, parentcategorynodes """
pass
Binary file not shown.
55 changes: 55 additions & 0 deletions webscraper/api/EbayAPI.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import requests
from requests.auth import HTTPBasicAuth
from dotenv import load_dotenv
import os


load_dotenv() #initialize

class EbayAPI:

client_secret_key = os.getenv("clientsecret")
client_id_key = os.getenv("clientid")

get_user_key = HTTPBasicAuth(client_id_key, client_secret_key)


def retrieve_access_token():
try:
response = requests.post("https://api.sandbox.ebay.com/identity/v1/oauth2/token",
headers = {"Content-Type":"application/x-www-form-urlencoded"},
data = {
"grant_type": "client_credentials",
"scope": "https://api.ebay.com/oauth/api_scope"
},
auth=EbayAPI.get_user_key
)
access_token = response.json().get("access_token")
status_code = response.status_code
if(status_code == 404):
raise Exception("404 error here")
return access_token
except Exception as e:
raise e

def retrieve_ebay_response(httprequest:str,query:str):
auth = EbayAPI.retrieve_access_token()
try:
response = requests.get(httprequest,
headers={
"Authorization": f"Bearer {auth}",
"Content-Type": "application/json"
},
params= {
"q": query,
"category_tree_id": 0
}
)
status_code = response.status_code
if(status_code == 404):
raise Exception("not found 404 error")

return response.json()
except Exception as e:
raise e

Binary file removed webscraper/api/__pycache__/interface.cpython-311.pyc
Binary file not shown.
Binary file removed webscraper/api/__pycache__/routes.cpython-311.pyc
Binary file not shown.
Binary file not shown.
35 changes: 35 additions & 0 deletions webscraper/api/tests/test_ebay_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import unittest
from unittest.mock import patch,Mock
import requests
from webscraper.api.EbayAPI import EbayAPI

class EbayTestApi(unittest.TestCase):

def setUp(self):
self.EbayAPI = EbayAPI


def test_retrieve_access_token(self):
self.EbayAPI.retrieve_access_token()
self.assertEqual(type(self.EbayAPI.retrieve_access_token()),str)

@patch("webscraper.api.EbayAPI.requests.post")
def test_retrieve_access_token_invalid(self,mock_post):
mock_response = Mock()
mock_response.status_code = 404
mock_response.json.return_value ={"error": "not found"}
mock_post.return_value = mock_response

with self.assertRaises(Exception):
self.EbayAPI.retrieve_access_token()



@patch("webscraper.api.EbayAPI.requests.get")
def test_retrieve_ebay_response_invalid(self,mock_get):
self.EbayAPI.retrieve_ebay_response("https://test","item")
self.assertRaises(Exception)


if __name__ == '__main__':
unittest.main()
12 changes: 7 additions & 5 deletions webscraper/src/main.py → webscraper/main.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

import json
#import time // for testing
# i added htese imports below becasue when i ran it it wasnt finding the folders
Expand All @@ -9,16 +8,19 @@


def main():




# Set up the scraper for a simple legal-to-scrape website
scraper = CheaperScraper("https://books.toscrape.com",
user_agent="CheaperBot/0.1",
delay=2.0)

user_agent="CheaperBot/0.1",
delay=2.0)
# Define which pages you want to scrape (you can use "/" for homepage)
pages = ["/"]

# Use the scraper to fetch and parse the pages
results = scraper.scrape(pages)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issue: The line results = CheaperScraper.scraper.scrape(pages) incorrectly attempts to access scraper as a class variable.

Suggestion: results = scraper.scrape(pages) already correctly invokes the instance method.

results = CheaperScraper.scraper.scrape(pages)

# Show the output in the terminal
for path, items in results.items():
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file removed webscraper/src/__pycache__/__init__.cpython-311.pyc
Binary file not shown.
Binary file not shown.
Binary file removed webscraper/src/__pycache__/robot_check.cpython-39.pyc
Binary file not shown.
Binary file not shown.