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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ https://flask-book-library-render.onrender.com
- Read, add, edit, and delete books.
- Read, add, edit, and delete customers.
- Read, add and delete loans.
- (WIP) Wishlist feature – allows users to create a wishlist of books they want to borrow or read in the future.

- **Search Functionality:**
- Easily search for books by name.
Expand All @@ -21,6 +22,7 @@ https://flask-book-library-render.onrender.com

- **Responsive Design:**
- Provides a seamless user experience across various devices.


## 🛠️ Technologies Used 🛠️

Expand Down
8 changes: 5 additions & 3 deletions app.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from project import app

from project import app, db
from project.wishlist.models import Wishlist # Import your Wishlist model

if __name__ == '__main__':
app.run(debug=True)
with app.app_context():
db.create_all() # Create the wishlist table if it doesn't exist yet
app.run(debug=True)
Binary file added data.sqlite
Binary file not shown.
13 changes: 6 additions & 7 deletions project/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,27 @@
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate


# Database Setup
# App & DB setup
app = Flask(__name__)

app.config['SECRET_KEY'] = 'supersecret' # To allow us to use forms
app.config['SECRET_KEY'] = 'supersecret'
app.config["TEMPLATES_AUTO_RELOAD"] = True

basedir = os.path.abspath(os.path.dirname(__file__))
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///'+os.path.join(basedir, 'data.sqlite')
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(basedir, 'data.sqlite')
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

db = SQLAlchemy(app)
Migrate(app, db)


# Register Blueprints
from project.core.views import core
from project.books.views import books
from project.customers.views import customers
from project.loans.views import loans
from project.wishlist.routes import wishlist_bp

app.register_blueprint(core)
app.register_blueprint(books)
app.register_blueprint(customers)
app.register_blueprint(loans)
app.register_blueprint(loans)
app.register_blueprint(wishlist_bp, url_prefix='/api/wishlist')
Binary file added project/data.sqlite
Binary file not shown.
6 changes: 6 additions & 0 deletions project/wishlist/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
### wishlist/__init__.py
from flask import Blueprint

wishlist_bp = Blueprint('wishlist', __name__)

from . import routes
9 changes: 9 additions & 0 deletions project/wishlist/forms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
### wishlist/forms.py (optional WTForms UI integration)
from flask_wtf import FlaskForm
from wtforms import IntegerField, SubmitField
from wtforms.validators import DataRequired

class WishlistForm(FlaskForm):
customer_id = IntegerField('Customer ID', validators=[DataRequired()])
book_id = IntegerField('Book ID', validators=[DataRequired()])
submit = SubmitField('Add to Wishlist')
19 changes: 19 additions & 0 deletions project/wishlist/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
### wishlist/models.py
from project import db
from datetime import datetime

class Wishlist(db.Model):
__tablename__ = 'wishlist'
id = db.Column(db.Integer, primary_key=True)
customer_id = db.Column(db.Integer, db.ForeignKey('customers.id'), nullable=False)
book_id = db.Column(db.Integer, db.ForeignKey('books.id'), nullable=False)
date_added = db.Column(db.DateTime, default=datetime.utcnow)

def to_dict(self):
return {
'id': self.id,
'customer_id': self.customer_id,
'book_id': self.book_id,
'date_added': self.date_added.isoformat()
}

57 changes: 57 additions & 0 deletions project/wishlist/routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
### wishlist/routes.py
from flask import request, jsonify
from . import wishlist_bp
from .models import Wishlist
from project import db


@wishlist_bp.route('/api/wishlist', methods=['GET'])
def get_wishlist():
wishlists = Wishlist.query.all()
return jsonify([w.to_dict() for w in wishlists]), 200

@wishlist_bp.route('/api/wishlist/<int:wishlist_id>', methods=['GET'])
def get_single_wishlist(wishlist_id):
wishlist = Wishlist.query.get(wishlist_id)
if not wishlist:
return jsonify({'error': 'Wishlist item not found'}), 404
return jsonify(wishlist.to_dict()), 200

@wishlist_bp.route('/api/wishlist', methods=['POST'])
def create_wishlist():
data = request.get_json()
if not data or 'customer_id' not in data or 'book_id' not in data:
return jsonify({'error': 'Invalid input'}), 400

new_item = Wishlist(
customer_id=data['customer_id'],
book_id=data['book_id']
)
db.session.add(new_item)
db.session.commit()
return jsonify(new_item.to_dict()), 201

@wishlist_bp.route('/api/wishlist/<int:wishlist_id>', methods=['PUT'])
def update_wishlist(wishlist_id):
wishlist = Wishlist.query.get(wishlist_id)
if not wishlist:
return jsonify({'error': 'Wishlist item not found'}), 404

data = request.get_json()
if 'customer_id' in data:
wishlist.customer_id = data['customer_id']
if 'book_id' in data:
wishlist.book_id = data['book_id']

db.session.commit()
return jsonify(wishlist.to_dict()), 200

@wishlist_bp.route('/api/wishlist/<int:wishlist_id>', methods=['DELETE'])
def delete_wishlist(wishlist_id):
wishlist = Wishlist.query.get(wishlist_id)
if not wishlist:
return jsonify({'error': 'Wishlist item not found'}), 404

db.session.delete(wishlist)
db.session.commit()
return jsonify({'message': 'Deleted successfully'}), 200
82 changes: 82 additions & 0 deletions test_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import pytest
from app import app
import json

@pytest.fixture
def client():
app.config['TESTING'] = True
with app.test_client() as client:
yield client

def test_get_books_json(client):
rv = client.get('/books/json')
assert rv.status_code == 200
assert 'books' in rv.json
assert isinstance(rv.json['books'], list)

def test_create_book(client):
new_book = {
'name': 'Test Book',
'author': 'Test Author',
'year_published': 2024,
'book_type': 'Fiction'
}
rv = client.post('/books/create', json=new_book)
assert rv.status_code == 302 # Because it redirects

def test_get_book_details(client):
# Ensure the book exists first
new_book = {
'name': 'Lookup Book',
'author': 'Author',
'year_published': 2023,
'book_type': 'Non-fiction'
}
client.post('/books/create', json=new_book)

rv = client.get('/books/details/Lookup Book')
assert rv.status_code == 200
assert rv.json['book']['name'] == 'Lookup Book'

def test_update_book(client):
# First, create a book
new_book = {
'name': 'Editable Book',
'author': 'Author',
'year_published': 2020,
'book_type': 'Mystery'
}
client.post('/books/create', json=new_book)

# Get book id from JSON list
books = client.get('/books/json').json['books']
book_id = next((i for i, b in enumerate(books) if b['name'] == 'Editable Book'), None)

assert book_id is not None

# Update the book
update_data = {
'name': 'Edited Book',
'author': 'Updated Author'
}
rv = client.post(f'/books/{book_id}/edit', json=update_data)
assert rv.status_code == 200
assert rv.json['message'] == 'Book updated successfully'

def test_delete_book(client):
new_book = {
'name': 'Delete Me',
'author': 'Author',
'year_published': 2019,
'book_type': 'Drama'
}
client.post('/books/create', json=new_book)

# Get book id
books = client.get('/books/json').json['books']
book_id = next((i for i, b in enumerate(books) if b['name'] == 'Delete Me'), None)

assert book_id is not None

rv = client.post(f'/books/{book_id}/delete')
assert rv.status_code == 302 # Redirect on success
Loading