diff --git a/requirements.txt b/requirements.txt index 744a7fe2e1512298e3b8fb1c34709dda3d44e57a..aea6d6b60e314ea92cdfaf92780b6a0ba7feee38 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,6 +6,7 @@ flask==2.2.2 python-dotenv flask-login flask_babel +datetime flask-mongoengine==1.0 Pillow diff --git a/src/tjts5901/auth.py b/src/tjts5901/auth.py index 0f620118a977e2de8fd2c9a95c3fdc2162c6bbf1..5a8bf5f354a290a9b39a873c65776aef169023cd 100644 --- a/src/tjts5901/auth.py +++ b/src/tjts5901/auth.py @@ -221,6 +221,8 @@ def profile(email): for item in items: item.current_price = get_item_price(item) + item.created_at = item.created_at.strftime("%Y-%m-%d %H:%M:%S") + item.closes_at = item.closes_at.strftime("%Y-%m-%d %H:%M:%S") item.image_base64 = base64.b64encode(item.image.read()).decode('utf-8') return render_template('auth/profile.html', user=user, items=items) diff --git a/src/tjts5901/models.py b/src/tjts5901/models.py index 8bc0a486c80606840a7c40fc7a6bffa461685ab1..06d08ff853755e4f99a57b6a04136ac4212d215e 100644 --- a/src/tjts5901/models.py +++ b/src/tjts5901/models.py @@ -67,6 +67,8 @@ class Item(db.Document): meta = {"indexes": [ {"fields": [ "closes_at", + "closed_processed", + "seller_informed_about_result" ]} ]} @@ -82,6 +84,14 @@ class Item(db.Document): closes_at = DateTimeField() + closed_processed = BooleanField(required=True, default=False) + "Flag indicating if closing of auction was already processed" + + seller_informed_about_result = BooleanField(required=True, default=False) + "Flag indicating if seller has already been informed about the result of the auction" + + + image = FileField() def save(self, *args, **kwargs): @@ -113,6 +123,9 @@ class Bid(db.Document): "amount", "item", "created_at", + "auction_end_processed", + "winning_bid", + "bidder_informed" ]} ]} @@ -128,6 +141,15 @@ class Bid(db.Document): created_at = DateTimeField(required=True, default=datetime.utcnow) "Date and time that the bid was placed." + auction_end_processed = BooleanField(required=True, default=False) + "Flag indicating, that closed auction has been processes" + + winning_bid = BooleanField(required=True, default=False) + "Flag indicating, whether the bid was the winning bid of the auction" + + bidder_informed = BooleanField(required=True, default=False) + "Flag indicating, whether the bidder has been informed about the result of the auction" + class AccessToken(db.Document): diff --git a/src/tjts5901/templates/addItem.html b/src/tjts5901/templates/addItem.html index d38b6b26f462061de13b9557737b28c72bbc31cb..1f7d9012ea25027766ba891724d1d3683a225877 100644 --- a/src/tjts5901/templates/addItem.html +++ b/src/tjts5901/templates/addItem.html @@ -27,11 +27,18 @@ <input type="number" class="form-control" id="sPrice" name="sPrice" min="0" max="100000000"> </div> <div class="mb-3"> + <label for="currency" class="form-label">Currency</label> <select name="currency" id="currency" class="form-control"> {% for code, name in currencies.items() %} <option value="{{ code }}" {% if code == default_currency %}selected{% endif %}>{{ name }}</option> {% endfor %} </select> + </div> + <div class="mb-3"> + </select> + <label for="closing" class="form-label">Duration of auction in hours and minutes</label> + <input type="time" id="duration" name="duration" step="60"> + </select> </div> <div class="mb-3"> <label for="image" class="form-label">Image (Please add image in .jpg format)</label> diff --git a/src/tjts5901/templates/auth/profile.html b/src/tjts5901/templates/auth/profile.html index 472222841333d4a6c0aa24739518811017cbe0e1..cdb327f47b3541c1ad4cf7d1ae8e64b8abad0bed 100644 --- a/src/tjts5901/templates/auth/profile.html +++ b/src/tjts5901/templates/auth/profile.html @@ -41,6 +41,8 @@ <p class="card-text">{{ auction.description }}</p> <p class="card-text">Starting Bid: {{ auction.starting_bid }} €</p> <p class="card-text">Current Price: {{ auction.current_price }} €</p> + <p class="card-text">Starting Time: {{ auction.created_at }} </p> + <p class="card-text">Closing Time: {{ auction.closes_at }} </p> </div> <div class="card-footer"> </div> diff --git a/src/tjts5901/templates/base.html b/src/tjts5901/templates/base.html index 1587fbd2d240dedd1ae0bb6b7ef5dddcd3e22107..d1819cf1985b1e51c9eb5179b2758ad80a98dee1 100644 --- a/src/tjts5901/templates/base.html +++ b/src/tjts5901/templates/base.html @@ -36,7 +36,7 @@ <ul class="navbar-nav"> {% if current_user.is_authenticated %} <li class="nav-item"><span>{{current_user['email'] }}</span></li> - <li class="nav-item"><a href="{{ url_for('auth.profile', email='me') }}" class="nav-link">My pro</a></li> + <li class="nav-item"><a href="{{ url_for('auth.profile', email='me') }}" class="nav-link">My Profile</a></li> <li class="nav-item"><a href="{{ url_for('auth.logout') }}" class="nav-link">Log Out</a></li> {% else %} <li class="nav-item"><a href="{{ url_for('auth.register') }}" class="nav-link">Register</a> diff --git a/src/tjts5901/views.py b/src/tjts5901/views.py index a63e5a1cd2a38580748cf782070cd96bad739877..cff5f803fa28614310ffce31b7b3488479a9ddca 100644 --- a/src/tjts5901/views.py +++ b/src/tjts5901/views.py @@ -7,8 +7,9 @@ import base64 import logging from typing import Optional from datetime import datetime, timedelta +from mongoengine.queryset.visitor import Q from flask import Blueprint, flash, redirect, render_template, request, url_for, jsonify -from .models import Item, User, Bid +from .models import Item, Bid from .auth import login_required, current_user from flask_babel import _, get_locale @@ -78,6 +79,66 @@ def get_item_price(item: Item) -> int: return winning_bid.amount else: return item.starting_bid + + +def check_auction_ends(): + """ + Check if all previously not processed auctions have ended now + Get all closed items, check if they belong to the current user and inform about + the ending of the auction. + Then process all bids on these items and check if they won + """ + items = Item.objects.filter(Q(closes_at=datetime.utcnow()) & Q(closed_processed=False)) \ + .order_by('-closes_at') + for item in items: + item.update(closed_processed = True) + message = None + + if item.seller == current_user: + message = "The auction of your item \'{}\' has ended. ".format(item.title) + item.update(seller_informed_about_result = True) + + # Get (potential) bids for the item and process them + bids = Bid.objects(item=item).order_by('-amount') + + if len(bids) >= 1: + # Set winning_bid = True for winning bid + bids[0].update(winning_bid = True) + bids[0].update(auction_end_processed = True) + + # Set winning_bid = False for all other bids + for bid in bids[1:]: + bid.update(winning_bid = False) + bid.update(auction_end_processed = True) + + # Inform seller if this is current user + if item.seller == current_user: + message += "Congrats! It was sold for {}.".format(bids[0].amount) + + # Inform current user if no one bid on the item + else: + if item.seller == current_user: + message += "No one bid on your item. Try again!" + + #Display message if not None + if message is not None: + flash((message)) + + +def check_bids_ended(): + """ + Method for checking all bids of current user, marked as auction_end_processed + and inform bidder if neccessary + """ + bids = Bid.objects.filter(Q(bidder=current_user) & Q(auction_end_processed=True) & Q(bidder_informed=False)) + + if len(bids) >= 1: + for bid in bids: + if bid.winning_bid == True: + flash(("Congrats! You won the auction of '{}' with your bid over {}.".format(bid.item.title, bid.amount))) + else: + flash(("Pity! You were not successfull with the auction of '{}' with your bid over {}.".format(bid.item.title, bid.amount))) + bid.update(bidder_informed = True) @bp.route("/test") @@ -97,6 +158,7 @@ def test_item_adding(): item.starting_bid = 100 item.seller = current_user item.image = image + item.closes_at = datetime.utcnow() + timedelta(days=1) item.save() @@ -115,11 +177,16 @@ def add_item(): currency = request.form.get('currency', REF_CURRENCY) starting_price = convert_from_currency(request.form['sPrice'], currency) image = request.files['image'] + duration = request.form.get('duration') + error = None if not nItem: error = 'Item name is required.' if not starting_price or starting_price < 1: error = Markup(_("Starting bid must be greater than %(amount)s.", amount=format_converted_currency(1, currency))) + if not duration: + error = 'Duration is required.' + print(duration, type(duration)) if error is None: try: @@ -128,7 +195,7 @@ def add_item(): description=description, starting_bid=starting_price, seller=current_user, - closes_at=datetime.utcnow() + timedelta(days=1), + closes_at=datetime.utcnow() + timedelta(hours=int(duration[:2]), minutes=int(duration[-2:])), image=image ) @@ -157,15 +224,18 @@ def add_item(): @login_required def list_bid(): """ - page that lists all items currently on auction + page that lists all items currently on auction and checks for notification """ + check_auction_ends() + check_bids_ended() # only on sale items are shown items = Item.objects.filter(closes_at__gt=datetime.utcnow()) \ .order_by('-closes_at') for item in items: item.image_base64 = base64.b64encode(item.image.read()).decode('utf-8') + item.created_at = item.created_at.strftime("%Y-%m-%d %H:%M:%S") item.current_price = get_item_price(item) html = render_template("listBid.html", items=items) @@ -335,10 +405,7 @@ def page_bid(id): local_min_bid = convert_currency(min_bid, local_currency) item.image_base64 = base64.b64encode(item.image.read()).decode('utf-8') - - # Dark pattern to show enticing message to user - #if item.closes_at < datetime.utcnow() + timedelta(hours=1): - # flash("This item is closing soon! Act now! Now! Now!") + item.created_at = item.created_at.strftime("%Y-%m-%d %H:%M:%S") return render_template('view.html', item=item, min_bid=min_bid,