Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • startuplab/courses/tjts5901-continuous-software-engineering/TJTS5901-K23_template
  • planet-of-the-apes/tjts-5901-apeuction
  • uunot-yliopiston-leivissa/tjts-5901-uunot
  • contain-the-cry/tjts-5901-auction-system
  • avengers/avengers
  • cse6/cse-6
  • 13th/13-sins-of-gitlab
  • fast-and-furious/fast-and-furious
  • back-to-the-future/delorean-auction
  • monty-pythons-the-meaning-of-life/the-meaning-of-life
  • team-atlantis/the-empire
  • code-with-the-wind/auction-project
  • the-pirates/the-pirates
  • do-the-right-thing/do-the-right-thing
  • inception/inception
  • the-social-network-syndicate/the-social-auction-network
  • team-the-hunt-for-red-october/tjts-5901-k-23-red-october
  • good-on-paper/good-paper-project
  • desperados/desperados
19 results
Show changes
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}Sell items{% endblock %}</h1>
{% endblock %}
{% block content %}
<body>
<header>
<meta charset="utf-8">
<title>Sell Item</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-GLhlTQ8iRABdZLl6O3oVMWSktQOp6b7In1Zl3/Jr59b6EGGoI1aFkw7cmDA6j6gD" crossorigin="anonymous">
</header>
<main>
<form class="container-fluid" action="{{ url_for('views.add_item') }}" method="POST" enctype="multipart/form-data">
<div class="container">
<div class="mb-3">
<label for="email" class="form-label">Email address</label>
<input type="email" class="form-control" id="email" name="email" aria-describedby="emailHelp">
</div>
<div class="mb-3">
<label for="nItem" class="form-label">Item's name</label>
<input type="text" class="form-control" id="nItem" name="nItem">
</div>
<div class="mb-3">
<label for="description" class="form-label">Description</label>
<textarea class="form-control" id="description" name="description" maxlength="700"></textarea>
</div>
<div class="mb-3">
<label for="sPrice" class="form-label">Starting price</label>
<input type="number" class="form-control" id="sPrice" name="sPrice" min="0" max="100000000">
</div>
<div class="mb-3">
<label for="image" class="form-label">Image (Please add image in .jpg format)</label>
<input type="file" class="form-control" id="image" name="image">
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</form>
</main>
<footer>
</footer>
</body>
</html>
{% endblock %}
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}Log In{% endblock %}</h1>
{% endblock %}
{% block content %}
<section class="min-vh-100 d-flex align-items-center bg-secondary">
<div class="container">
<div class="row justify-content-center">
<div class="col-12 d-flex align-items-center justify-content-center">
<div class="card card-tertiary w-100 fmxw-400">
<div class="card-header text-center">
<span>Sign in to our platform</span>
</div>
<div class="login-dialog-img"></div>
<div class="card-body">
<form action="{{ url_for('auth.login') }}" class="mt-4" method="POST">
<div class="form-group">
<label for="email" class="mb-2">Email</label>
<input name="email" id="email" type="email" class="form-control" placeholder="Your email"
required="">
</div>
<div class="form-group">
<div class="form-group">
<label for="password" class="mb-2">Password</label>
<input name="password" id="password" type="password" class="form-control"
placeholder="Your password" required="">
</div>
<div class="d-flex justify-content-between align-items-center mb-4">
<div class="form-check">
<label class="form-check-label">
<input name="remember-me" class="form-check-input" type="checkbox">
<span class="form-check-x"></span>
<span class="form-check-sign"></span>
Remember me
</label>
</div>
<p class="m-0"><a href="#" class="text-right">Lost password?</a></p>
</div>
</div>
<button type="submit" class="btn btn-block btn-primary">Login</button>
</form>
<div class="d-block d-sm-flex justify-content-center align-items-center mt-4">
<p class="font-weight-normal">
Not registered?
<a href="{{ url_for('auth.register') }}" class="font-weight-bold">Create an account</a>
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
{% endblock %}
{% extends 'base.html' %}
{% block header %}
<div class="container">
<h1>{% block title %}{{user.email}}'s' Profile Page{% endblock %}</h1>
</div>
{% endblock %}
{% block content %}
<div class="container">
<div class="row">
<div class="col-sm-3"></div>
<div class="col-sm-6 profile-section">
<div class="card card-secondary">
<div class="card-header text-center">
<span>{{ user.email }}</span>
</div>
<div class="card-body">
<div class="form-group d-flex align-items-center justify-content-between">
<label for="default" class="mr-3">Email:</label>
<input id="default" type="text" class="form-control w-75" value="{{ user.email }}">
</div>
</div>
<div class="card-footer">
</div>
</div>
</div>
<div class="col-sm-3"></div>
</div>
<div class="row">
<div class="col-sm-3"></div>
<div class="col-sm-6 auction-section">
<h3>Recent Auctions</h3>
<div class="card-deck">
{% for auction in items %}
<div class="card">
<img src="..." class="card-img-top" alt="...">
<div class="card-body">
<h5 class="card-title">{{ auction.title }}</h5>
<p class="card-text">{{ auction.description }}</p>
<p class="card-text">Starting Bid: ${{ auction.starting_bid }}</p>
</div>
<div class="card-footer">
</div>
</div>
{% endfor %}
</div>
<div class="col-sm-3"></div>
</div>
</div>
{% endblock %}
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}Register{% endblock %}</h1>
{% endblock %}
<style>
.login-dialog-img {
background-image: url("https://openai-labs-public-images-prod.azureedge.net/user-eKykIYWOdPMyg5hmQc750Q6a/generations/generation-GZTU47SaNB9GeJVLaJXUfztk/image.webp");
background-size: cover;
background-position: center;
background-repeat: no-repeat;
width: 100%;
height: 8em;
}
</style>
{% block content %}
<section class="min-vh-100 d-flex align-items-center bg-secondary">
<div class="container">
<div class="row justify-content-center">
<div class="col-12 d-flex align-items-center justify-content-center">
<div class="card card-tertiary w-100 fmxw-400">
<div class="card-header text-center">
<span>Register on our platform</span>
</div>
<div class="login-dialog-img"></div>
<div class="card-body">
<form action="{{ url_for('auth.register') }}" method="POST" class="mt-4">
<div class="form-group">
<label for="email" class="mb-2">Email</label>
<input name="email" id="email" type="email" class="form-control" placeholder="Your email" required="">
</div>
<div class="form-group">
<div class="form-group">
<label for="password" class="mb-2">Password</label>
<input name="password" id="password" type="password" class="form-control" placeholder="Your password" required="">
</div>
<div class="form-group">
<label for="confirmPassword" class="mb-2">Confirm password</label>
<input name="password2" id="confirmPassword" type="password" class="form-control" placeholder="Confirm password"
required="">
</div>
<div class="d-flex justify-content-between align-items-center mb-4">
<div class="form-check">
<label class="form-check-label">
<input name="terms" class="form-check-input" type="checkbox">
<span class="form-check-x"></span>
<span class="form-check-sign"></span>
I agree to the <a href="#">terms and conditions</a>
</label>
</div>
</div>
</div>
<button type="submit" class="btn btn-block btn-primary">Register account</button>
</form>
<div class="d-block d-sm-flex justify-content-center align-items-center mt-4">
<p class="font-weight-normal">
Already have an account?
<a href="./login.html" class="font-weight-bold">Login here</a>
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
{% endblock %}
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}Access tokens{% endblock %}</h1>
{% endblock %}
{% block content %}
<div class="container">
<div class="row">
<div class="col-md-4">
<h4>{{("Personal Access Tokens")}}</h4>
<p>
Personal access tokens allow third-party services to authenticate with our application on your behalf.
</p>
</div>
<div class="col-md-8">
{% if token %}
<div class="alert alert-success" role="alert">
<h4 class="alert-heading">{{("Your new personal access token")}}</h4>
<p>
Your new personal access token is shown below. You may now use this token to make API requests.
</p>
<div class="input-group mb-3">
<input type="text" class="form-control" id="token" value="{{ token.token }}" readonly>
<button class="btn btn-outline-secondary" type="button" id="copy-token" onclick="copyToken()">{{("Copy")}}</button>
<script>
function copyToken() {
var copyText = document.getElementById("token");
copyText.select();
copyText.setSelectionRange(0, 99999);
document.execCommand("copy");
}
</script>
</div>
<small class="form-text text-muted">{{ ("Make sure to copy your new token now. You won't be able to see it again!") }}</small>
<hr>
</div>
{% endif %}
<div class="card">
<div class="card-header">
<div class="text-center">{{ ("Create access token") }}</div>
</div>
<form action="{{url_for('auth.user_access_tokens', email='me')}}" method="post" class="card-body">
<div class="form-group">
<label for="name">{{ ("Name") }}</label>
<input type="text" class="form-control" name="name" id="name" placeholder="{{ (" Enter token name") }}">
<div class="form-text text-muted">{{ ("Give your token a descriptive name so you can easily identify it in the future.") }}</div>
</div>
<div class="form-group">
<label class="form-check-label" for="expires">{{ ("Expires at") }}</label>
<input type="date" class="form-control" name="expires" id="expires">
<div class="form-text text-muted">{{ ("Leave blank to never expire.") }}</div>
</div>
<button type="submit" class="btn btn-primary">{{ ("Create access token") }}</button>
</form>
</div>
</div>
</div>
</div>
<div class="container">
<div class="row">
<div class="col-md-8 offset-md-4">
<h2 class="text-center mb-4">{{ ("Active Access Tokens") }}</h2>
<table class="table table-striped">
<thead>
<tr>
<th>{{ ("Token name") }}</th>
<th>{{ ("Created") }}</th>
<th>{{ ("Last used") }}</th>
<th>{{ ("Expires") }}</th>
<th>{{ ("Actions") }}</th>
</tr>
</thead>
<tbody>
{% for access_token in tokens %}
<tr>
<td>{{ access_token.name }}</td>
<td>{{ access_token.created_at}}</td>
<td>
{% if access_token.last_used_at %}
{{ access_token.last_used_at }}
{% else %}{{ ("Never") }}
{% endif %}
</td>
<td>
{% if access_token.expires_at %}
{{ access_token.expires_at }}
{% else %}{{ ("Never") }}
{% endif %}
</td>
<td>
<form action="{{ url_for('auth.delete_user_access_token', email='me', id=access_token.id) }}"
method="post">
<button type="submit" class="btn btn-danger">{{ ("Delete") }}</button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endblock %}
<!doctype html>
<html lang="en">
<head>
<title>{% block title %}{% endblock %} - {{ config['BRAND'] }}</title>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css" integrity="sha384-xOolHFLEh07PJGoPkLv1IbcEPTNtaed2xpHsD9ESMhqIYd0nLMwNLD69Npy4HI+N" crossorigin="anonymous">
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<a class="navbar-brand" href="{{url_for('views.index')}}">{{config['BRAND']}}</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarColor01"
aria-controls="navbarColor01" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarTogglerDemo03">
"""
<ul class="navbar-nav mr-auto">
<li class="nav-item active">
<a class="nav-link" href="{{ url_for('views.list_bid') }}">Buy</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('views.add_item') }}">Sell</a>
</li>
</ul>
"""
<ul class="navbar-nav">
{% if g.user %}
<li class="nav-item"><span>{{ g.user['username'] }}</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.logout') }}" class="nav-link">Log Out</a></li>
{% else %}
<li class="nav-item"><a href="{{ url_for('auth.register') }}" class="nav-link">Register</a>
<li class="nav-item"><a href="{{ url_for('auth.login') }}" class="nav-link">Log In</a>
{% endif %}
</ul>
</div>
</nav>
</head>
<body class="bg-secondary">
<header>
{% block header %}
<h1>{{ config['BRAND'] }}</h1>
{% endblock %}
</header>
<main class="content">
{% for message in get_flashed_messages() %}
<div class="container">
<div class="flash alert alert-primary">{{ message }}</div>
</div>
{% endfor %}
{% block content %}
<!-- MAIN CONTENT BLOCK MISSING -->
{% endblock %}
</main>
<!-- Option 1: jQuery and Bootstrap Bundle (includes Popper) -->
<script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-Fy6S3B9q64WdZWQUiU+q4/2Lc9npb8tCaSX9FK7E8HnRr0Jz8D6OP9dO5Vg3Q9ct" crossorigin="anonymous"></script>
</body>
</html>
......@@ -10,5 +10,8 @@
</head>
<body>
<h1>Hello, world!</h1>
<h2>Hallo, Welt!</h2>
<h2>Bonjour le monde!</h2>
<h2>Hei maailma!</h2>
</body>
</html>
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}View all items currently on auction {% endblock %}</h1>
{% endblock %}
{% block content %}
<head>
<meta charset="utf-8">
<title>View items</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-GLhlTQ8iRABdZLl6O3oVMWSktQOp6b7In1Zl3/Jr59b6EGGoI1aFkw7cmDA6j6gD" crossorigin="anonymous">
</head>
<body>
<main>
<div class="list-group container">
{%for item in items %}
<div class="d-flex w-100 justify-content-between mb-4">
<img src="data:image/png;base64,{{ item.image_base64 }}">
<div class="list-group-item list-group-item-action" aria-current="true">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">{{ item.title }}</h5>
<small>{{ item.created_at }}</small>
</div>
<p class="mb-1">{{ item.description }}</p>
<small>{{ item.current_price}}</small>
<a href="{{ url_for('views.page_bid', id=item.id) }}"><button class="btn btn-primary">Bid</button></a>
</div>
<div class="row justify-content-md-center">
<div class="col-md-auto">
<a href="https://twitter.com/intent/tweet?url={{ url_for('views.list_bid', id=item.id, _external=True) | urlencode }}&text={{ "Check out this awesome and cheap item." | urlencode }}" target="_blank" style="background-color: #1DA1F2; color:#fff;">
<button class="btn btn-primary" type="button">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-twitter" viewBox="0 0 16 16">
<path d="M5.026 15c6.038 0 9.341-5.003 9.341-9.334 0-.14 0-.282-.006-.422A6.685 6.685 0 0 0 16 3.542a6.658 6.658 0 0 1-1.889.518 3.301 3.301 0 0 0 1.447-1.817 6.533 6.533 0 0 1-2.087.793A3.286 3.286 0 0 0 7.875 6.03a9.325 9.325 0 0 1-6.767-3.429 3.289 3.289 0 0 0 1.018 4.382A3.323 3.323 0 0 1 .64 6.575v.045a3.288 3.288 0 0 0 2.632 3.218 3.203 3.203 0 0 1-.865.115 3.23 3.23 0 0 1-.614-.057 3.283 3.283 0 0 0 3.067 2.277A6.588 6.588 0 0 1 .78 13.58a6.32 6.32 0 0 1-.78-.045A9.344 9.344 0 0 0 5.026 15z"/>
</svg>
</button>
</a>
<a href="https://www.facebook.com/sharer.php?u={{ url_for('views.list_bid', id=item.id, _external=True) | urlencode }}" target="_blank" style="background-color: #4267B2; color:#fff;">
<button class="btn btn-primary" type="button">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-facebook" viewBox="0 0 16 16">
<path d="M16 8.049c0-4.446-3.582-8.05-8-8.05C3.58 0-.002 3.603-.002 8.05c0 4.017 2.926 7.347 6.75 7.951v-5.625h-2.03V8.05H6.75V6.275c0-2.017 1.195-3.131 3.022-3.131.876 0 1.791.157 1.791.157v1.98h-1.009c-.993 0-1.303.621-1.303 1.258v1.51h2.218l-.354 2.326H9.25V16c3.824-.604 6.75-3.934 6.75-7.951z"/>
</svg>
</button>
</a>
</div>
</div>
</div>
{% endfor %}
</div>
</main>
<footer>
</footer>
</body>
</html>
{% endblock %}
\ No newline at end of file
{% extends 'base.html' %}
{% block header %}
<div class="container">
<div class="d-flex align-items-center">
<h1 class="mr-auto">{% block title %}{{item.title}}{% endblock %}</h1>
</div>
</div>
{% endblock %}
{% block content %}
<div class="d-flex mb-4 container">
<div class="col-md-4">
<img src="data:image/png;base64,{{ item.image_base64 }}">
</div>
<div class="col-md-4">
<h5 class="mb-1">{{ item.title }}</h5>
<p class="mb-1">{{ item.description }}</p>
<p class="mb-1">Created : {{ item.created_at }}</p>
<p class="mb-1">Current bid : {{ current_price }}</p><br/>
<form method="POST" action="{{ url_for('views.bid', id=item.id)}}" class="row" enctype="multipart/form-data">
<input class="col-md-9" id="bid" name ="bid" type="number" min="{{ min_bid }}" placeholder="write your bid">
<button for="bid" type="submit" class="btn btn-primary col-md-3">Validate</button>
</form>
</div>
</div>
{% endblock %}
\ No newline at end of file
src/tjts5901/test.jpeg

14.2 KiB

......@@ -3,14 +3,68 @@ Basic views for Application
===========================
"""
from flask import Blueprint, render_template, send_from_directory
import base64
import random
import logging
from typing import Optional
from datetime import datetime, timedelta
from flask import Blueprint, flash, redirect, render_template, request, url_for, jsonify
from .models import Item, User, Bid
from .auth import login_required, current_user
# Main blueprint.
bp = Blueprint('views', __name__)
api = Blueprint('api', __name__, url_prefix='/api')
logger = logging.getLogger(__name__)
# Blueprint for documentation.
docs_bp = Blueprint('docs', __name__)
MIN_BID_INCREMENT = 1
def get_winning_bid(item: Item) -> Optional[Bid]:
"""
Return the (currently) winning bid for the given item.
If there are no bids, or the item is not yet closed, return None.
:param item: The item to get the winning bid for.
:return: The winning bid, or None.
"""
winning_bid = None
try:
winning_bid = Bid.objects(item=item) \
.filter(created_at__lt=item.closes_at) \
.order_by('-amount') \
.first()
except Exception as exc:
logger.warning("Error getting winning bid: %s", exc, exc_info=True, extra={
'item_id': item.id,
})
return winning_bid
def get_item_price(item: Item) -> int:
"""
Return the current price of the given item.
If there are no bids, return the starting bid.
:param item: The item to get the price for.
:return: The current price.
"""
winning_bid = get_winning_bid(item)
if winning_bid:
return winning_bid.amount + MIN_BID_INCREMENT
else:
return item.starting_bid
@bp.route("/")
def index() -> str:
"""
......@@ -20,5 +74,262 @@ def index() -> str:
# Render template file. Template file is using Jinja2 syntax, and can be passed an arbitrary number
# of arguments to be used in the template. The template file is located in the templates directory.
html = render_template("index.html.j2", title="TJTS5901 Example. I should be changed.")
html = render_template("index.html.j2", title="TJTS5901 Example. It was changed by Arno.")
return html
@bp.route("/test")
def test_item_adding():
"""
Test item is added
"""
# for test purpose create random number (unique mail address required)
ran_int = str(random.randint(0,10000))
# Create a new user
user = User()
user.email = "test" + ran_int + "@gmail.com"
user.password = "placeholder"
user.save()
#load test image
with open("src/tjts5901/test.jpeg", "rb") as image_file:
image = image_file.read()
# Create a new item
item = Item()
item.title = "Test title"
item.description = "This is a test description"
item.starting_bid = 100
item.seller = user
item.image = image
item.save()
return "OK"
@bp.route("/addItem", methods=('GET', 'POST'))
@login_required
def add_item():
"""
AddItem page.
"""
if request.method == 'POST':
nItem = request.form.get('nItem')
description = request.form.get('description')
starting_price = int(request.form.get('sPrice'))
image = request.files['image']
error = None
if not nItem:
error = 'Item name is required.'
if not starting_price or starting_price < 1:
error = 'Starting price must be greater than 0.'
if error is None:
# Placeholder for user as long as login function is not defined
ran_int = str(random.randint(0,10000))
user = User()
user.email = "test" + ran_int + "@gmail.com"
user.password = "placeholder"
user.save()
try:
item = Item(
title=nItem,
description=description,
starting_bid=starting_price,
seller=user,
closes_at=datetime.utcnow() + timedelta(days=1),
image=image
)
item.save()
except Exception as exc:
error = f"Error creating item: {exc!s}"
print('error:', exc)
else:
return redirect(url_for('views.list_bid'))
flash(error)
item = render_template("addItem.html")
return item
@bp.route("/listBid")
@login_required
def list_bid():
"""
page that lists all items currently on auction
"""
# 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.current_price = get_item_price(item) - MIN_BID_INCREMENT
html = render_template("listBid.html", items=items)
return html
@bp.route("item/<id>/bid", methods=('POST',))
@login_required
def bid(id):
"""
method that saves bid on object
If the bid is valid, create a new bid and redirect to the item view page.
Otherwise, display an error message and redirect back to the item view page.
"""
# frontend not implemented yet, therefore not active
item = Item.objects.get_or_404(id=id)
min_amount = item.starting_bid
amount = int(request.form.get('bid'))
if amount <= min_amount:
flash(("Bid must be at least %(min_amount)s", min_amount))
return redirect(url_for('views.list_bid'))
if item.closes_at < datetime.utcnow():
flash("This item is no longer on sale.")
return redirect(url_for('views.list_bid'))
try:
# Notice: if you have integrated the flask-login extension, use current_user
# instead of g.user
bid = Bid(
item=item,
bidder=current_user,
amount=amount,
)
bid.save()
except Exception as exc:
flash(_("Error placing bid: %(exc)s", exc=exc))
else:
flash(("Bid placed successfully!"))
return redirect(url_for('views.list_bid', id=id))
@api.route('<id>/bids', methods=('GET',))
@login_required
def api_item_bids(id):
"""
Get the bids for an item.
:param id: The id of the item to get bids for.
:return: A JSON response containing the bids.
"""
item = Item.objects.get_or_404(id=id)
bids = []
for bid in Bid.objects(item=item).order_by('-amount'):
bids.append(bid.to_json())
return jsonify({
'success': True,
'bids': bids
})
@api.route('<id>/bids', methods=('POST',))
@login_required
def api_item_place_bid(id):
"""
Place a bid on an item.
If the bid is valid, create a new bid and return the bid.
Otherwise, return an error message.
Only accepts `REF_CURRENCY` bids.
:param id: The id of the item to bid on.
:return: A JSON response containing the bid.
"""
item = Item.objects.get_or_404(id=id)
min_amount = get_item_price(item)
try:
amount = int(request.form['amount'])
except KeyError:
return jsonify({
'success': False,
'error': ("Missing required argument %(argname)s", argname:='amount')
})
except ValueError:
return jsonify({
'success': False,
'error': ("Invalid value for argument %(argname)s", argname:='amount')
})
except Exception as exc:
return jsonify({
'success': False,
'error': ("Error parsing argument %(argname)s: %(exc)s", argname:='amount', exc:=exc)
})
if amount < min_amount:
return jsonify({
'success': False,
'error': ("Bid must be at least %(min_amount)s", min_amount:=min_amount)
})
if item.closes_at < datetime.utcnow():
return jsonify({
'success': False,
'error': ("This item is no longer on sale.")
})
try:
bid = Bid(
item=item,
bidder=current_user,
amount=amount,
)
bid.save()
except Exception as exc:
logger.error("Error placing bid: %s", exc, exc_info=True, extra={
'item_id': item.id,
'bidder_id': current_user.id,
'amount': amount,
})
return jsonify({
'success': False,
'error': ("Error placing bid: %(exc)s", exc:=exc)
})
print(type(bid))
return jsonify({
'success': True,
'bid': bid.to_json()
})
@bp.route("/view/<id>")
@login_required
def page_bid(id):
"""
Item view page.
Displays the item details, and a form to place a bid.
"""
item = Item.objects.get_or_404(id=id)
# Set the current price for the bid according to current highest bid
winning_bid = get_winning_bid(item)
min_bid = get_item_price(item)
current_price = min_bid - MIN_BID_INCREMENT
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!")
return render_template('view.html', item=item, current_price=current_price, min_bid=min_bid)
import pytest
from flask import Flask
from tjts5901.app import create_app
@pytest.fixture
def app():
"""
Application fixture.
Every test that requires `app` as parameter can use this fixture.
Example:
>>> def test_mytest(app: Flask):
>>> ...
"""
flask_app = create_app({
'TESTING': True,
'DEBUG': False,
# We need to set SERVER_NAME and PREFERRED_URL_SCHEME for testing.
'SERVER_NAME': 'localhost.localdomain',
'PREFERRED_URL_SCHEME': 'http',
})
# If you have done ties4080 course and have used Flask-WTF, you might
# have noticed that CSRF protection is enabled by default. This is
# problematic for testing, because we don't have a browser to generate
# CSRF tokens. We can disable CSRF protection for testing, but we need
# to make sure that we don't have CSRF protection enabled in production.
# flask_app.config['WTF_CSRF_ENABLED'] = False
# flask_app.config['WTF_CSRF_METHODS'] = []
# flask_app.config['WTF_CSRF_CHECK_DEFAULT'] = False
flask_app.testing = True
yield flask_app
# Do some cleanup here if needed.
...
@pytest.fixture
def client(app):
return app.test_client()
from tjts5901.views import test_item_adding
def test_db():
"""
Testing the db connection via previously defined test method
"""
#assert init_db({'TESTING': True}).testing
assert test_item_adding()
from tjts5901 import create_app
def test_config():
assert not create_app().testing
assert create_app({'TESTING': True}).testing
## The GitLab Server URL (with protocol) that want to register the runner against
## ref: https://docs.gitlab.com/runner/commands/index.html#gitlab-runner-register
##
gitlabUrl: "https://gitlab.jyu.fi"
## The Registration Token for adding new Runners to the GitLab Server. This must
## be retrieved from your GitLab Instance.
## ref: https://docs.gitlab.com/ce/ci/runners/index.html
## NOTE: In TJTS5901 we set this using kubernetes secrets.
##
# runnerRegistrationToken: gitlab-runner-secret
## Unregister all runners before termination
##
## Updating the runner's chart version or configuration will cause the runner container
## to be terminated and created again. This may cause your Gitlab instance to reference
## non-existant runners. Un-registering the runner before termination mitigates this issue.
## ref: https://docs.gitlab.com/runner/commands/index.html#gitlab-runner-unregister
##
unregisterRunners: true
## Configure the maximum number of concurrent jobs
## - Documentation: https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-global-section
## - Default value: 10
concurrent: 2
## For RBAC support:
rbac:
create: true
clusterWideAccess: false
# rules:
# - apiGroups: [""]
# resources: ["pods"]
# verbs: ["get", "create", "delete"]
# - apiGroups: [""]
# resources: ["pods/exec"]
# verbs: ["get", "create"]
# - apiGroups: [""]
# resources: ["pods/attach"]
# verbs: ["create"]
# - apiGroups: [""]
# resources: ["secrets", "configmaps"]
# verbs: ["create", "update", "delete"]
# - apiGroups: [""]
# resources: ["services"]
# verbs: ["create"]
## Configuration for the Pods that the runner launches for each new job
##
runners:
image: ubuntu:20.04
builds: {}
services: {}
helpers: {}
name: "Kubernetes runner on Azure for TJTS5901"
## Tags associated with the runner. Comma-separated list of tags.
## - Documentation: https://docs.gitlab.com/ce/ci/runners/#using-tags
tags: kubernetes, cluster
## Determine whether the runner should also run jobs without tags.
## - Documentation: https://docs.gitlab.com/ee/ci/runners/configure_runners.html#set-a-runner-to-run-untagged-jobs
runUntagged: true
# runner configuration, where the multi line strings is evaluated as
# template so you can specify helm values inside of it.
#
# tpl: https://helm.sh/docs/howto/charts_tips_and_tricks/#using-the-tpl-function
# runner configuration: https://docs.gitlab.com/runner/configuration/advanced-configuration.html
config: |
[[runners]]
environment = ["DOCKER_DRIVER=overlay2"]
[runners.kubernetes]
namespace = "{{.Release.Namespace}}"
privileged = true
[[runners.kubernetes.volumes.empty_dir]]
name = "docker-certs"
mount_path = "/certs/client"
medium = "Memory"
## The name of the secret containing runner-token and runner-registration-token
secret: runner-registration-token
## Run all containers with the privileged flag enabled
## This will allow the docker:dind image to run if you need to run Docker
## commands. Please read the docs before turning this on:
## - Documentation: https://docs.gitlab.com/runner/executors/kubernetes.html#using-docker-dind
privileged: true