Skip to content
Snippets Groups Projects
Verified Commit ad4ecd3a authored by Arun Joshua's avatar Arun Joshua
Browse files

Add Book Model [WIP]

Other changes:
* Create a test user in create_db.py
* Form to add books
* Dashboard updates
* /add route
parent 651c19f9
No related branches found
No related tags found
No related merge requests found
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField, BooleanField
from wtforms import StringField, PasswordField, SubmitField, BooleanField, DecimalField
from wtforms.fields.html5 import EmailField
from wtforms.validators import DataRequired, Length, Email, EqualTo, ValidationError
from wtforms.validators import (
DataRequired,
Length,
Email,
EqualTo,
ValidationError,
Regexp,
)
from bam.models import User
class RegistrationForm(FlaskForm):
username = StringField(
"Username",
validators=[DataRequired()],
validators=[DataRequired(), Length(max=16)],
render_kw={"placeholder": "Enter a username"},
)
email = EmailField(
......@@ -23,7 +30,10 @@ class RegistrationForm(FlaskForm):
)
confirm_password = PasswordField(
"Confirm Password",
validators=[DataRequired(), EqualTo("password")],
validators=[
DataRequired(),
EqualTo("password", message="Passwords do not match."),
],
render_kw={"placeholder": "Re-enter your password"},
)
......@@ -51,3 +61,19 @@ class LoginForm(FlaskForm):
)
remember = BooleanField("Remember Me")
submit = SubmitField("Login")
class AddBookForm(FlaskForm):
title = StringField("Title", validators=[DataRequired()])
author = StringField("Author", validators=[DataRequired()])
isbn = StringField(
"ISBN",
validators=[
Regexp(
r"^(\d{10}\d{3}?)?$", message="Invalid ISBN. Must be 10 or 13 digits."
),
],
)
price = DecimalField("Price", default=0.0)
submit = SubmitField("Add book")
......@@ -10,9 +10,20 @@ def load_user(user_id):
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(120), unique=True, nullable=False)
username = db.Column(db.String(60), unique=True, nullable=False)
username = db.Column(db.String(16), unique=True, nullable=False)
password = db.Column(db.String(60), nullable=False)
role = db.Column(db.String(10), default="user")
def __repr__(self):
return f"User({self.email}, {self.password})"
class Book(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(120), nullable=False)
author = db.Column(db.String(120), nullable=False)
isbn = db.Column(db.String(13))
price = db.Column(db.Float, default=0.0)
addedby = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)
users = db.relationship(User)
\ No newline at end of file
from flask import render_template, url_for, flash, redirect
from flask_login import login_user, current_user, logout_user
from flask_login import login_user, current_user, logout_user, login_required
from bam import app, db, bcrypt
from bam.forms import RegistrationForm, LoginForm
from bam.models import User
from bam.forms import RegistrationForm, LoginForm, AddBookForm
from bam.models import User, Book
@app.route("/")
@app.route("/home")
def home():
if current_user.is_authenticated:
return render_template("dash.html")
return render_template("books.html")
return render_template("home.html")
......@@ -51,3 +51,21 @@ def logout():
logout_user()
# flash("You have been successfully logged out", "success")
return redirect(url_for("home"))
@app.route("/add", methods=["GET", "POST"])
@login_required
def addBook():
form = AddBookForm()
if form.validate_on_submit():
book = Book(
title=form.title.data,
author=form.author.data,
isbn=form.isbn.data,
price=form.price.data,
addedby=current_user.id,
)
db.session.add(book)
db.session.commit()
return redirect(url_for("home"))
return render_template("add.html", title="Add book", form=form)
\ No newline at end of file
.content {
align-self: flex-start;
}
form {
width: 100%;
}
form {
display: flex;
flex-direction: column;
min-width: min(90vw, 400px);
}
form > label {
padding-bottom: 5px;
}
input {
font-family: Roboto;
font-size: 16px;
border-radius: 3px;
padding: 12px;
background-color: rgba(10, 10, 10, 0.5);
border: 1px solid #111;
color: #eee;
transition: background 0.3s;
}
input:hover {
--hcolor: rgba(10, 10, 10, 0.7);
background-color: var(--hcolor);
border: 1px solid var(--hcolor);
}
input:focus,
input:active {
background-color: rgba(30, 30, 30, 0.5);
border: 1px solid grey;
outline: none;
}
label {
text-transform: uppercase;
font-weight: 700;
font-size: 0.9rem;
letter-spacing: 1px;
}
.field-error {
font-style: oblique;
color: var(--red);
font-size: 0.9rem;
}
[type="submit"] {
background-color: var(--red);
border: none;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 1px;
}
[type="submit"]:hover {
background-color: var(--dark-red);
cursor: pointer;
}
label,
[type="submit"] {
margin-top: 10px;
}
@import url("https://fonts.googleapis.com/css2?family=Roboto:wght@100;300;400;500;700;900&display=swap");
html,
body {
margin: 0;
padding: 0;
min-height: 100vh;
}
:root {
--red: #e50914;
--dark-red: #bb0009;
--t-dark: rgba(0, 0, 0, 0.8);
--t-darker: rgba(0, 0, 0, 0.9);
--nav-padding-top: 20px;
--nav-padding-side: 50px;
--logo-height: 60px;
--nav-gap: 20px;
}
@media screen and (max-width: 600px) {
:root {
--nav-padding-top: 15px;
--nav-padding-side: 15px;
--logo-height: 50px;
}
.navbar {
flex-wrap: wrap;
}
.navbar .space {
display: none;
}
}
* {
box-sizing: border-box;
}
body {
background-color: #222;
color: white;
font-family: Roboto;
}
.maincontainer {
display: flex;
flex-direction: column;
align-items: center;
min-height: 100vh;
}
.navbar {
background: linear-gradient(to bottom, #040404, transparent);
align-items: center;
padding: var(--nav-padding-top) var(--nav-padding-side);
display: flex;
width: 100%;
gap: var(--nav-gap);
}
.navbar .logo {
height: var(--logo-height);
margin-right: calc(var(--nav-padding-side) - var(--nav-gap));
}
.navbar .logo img {
height: 100%;
filter: drop-shadow(2px 2px 6px #111);
}
.navbar a {
text-decoration: none;
font-size: 20px;
font-weight: 500;
color: #aaa;
transition: color 0.2s ease;
}
.navbar a.active {
color: #eee;
}
.navbar a:hover {
color: #fff;
}
.navbar .space,
.content {
flex-grow: 1;
}
.content {
padding: 0 var(--nav-padding-side);
}
.credits {
padding: 15px;
font-size: 0.7rem;
color: #888;
opacity: 0.5;
background: linear-gradient(to bottom, transparent, #111);
width: 100%;
text-align: center;
}
{% extends "dash.html" %} {% set active_page = "add" %} {% block head %}
<link
rel="stylesheet"
href="{{ url_for('static', filename='bookform.css') }}"
/>
{% endblock %} {% block content %}
<form class="addBookForm" action="" method="POST">
{{ form.hidden_tag() }}
<h1>Add a Book</h1>
{{ form.title.label() }} {{ form.title }} {% for error in form.title.errors %}
<div class="field-error">{{ error }}</div>
{% endfor %} {{ form.author.label() }} {{ form.author }} {% for error in
form.author.errors %}
<div class="field-error">{{ error }}</div>
{% endfor %} {{ form.isbn.label() }} {{ form.isbn }} {% for error in
form.isbn.errors %}
<div class="field-error">{{ error }}</div>
{% endfor %} {{ form.price.label() }} {{ form.price }} {% for error in
form.price.errors %}
<div class="field-error">{{ error }}</div>
{% endfor %} {{ form.submit() }}
</form>
{% endblock %}
{% extends "dash.html" %} {% set active_page = "books" %} {% block content %}
<div>WIP</div>
{% endblock %}
......@@ -3,9 +3,41 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>BAM - Dashboard</title>
{% if title %}
<title>BAM - {{ title }}</title>
{% else %}
<title>BAM</title>
{% endif %}
<link
rel="stylesheet"
href="{{ url_for('static', filename='dash.css') }}"
/>
{% block head %}{% endblock %}
</head>
<body>
Yaaay - you are authorized!
<div class="maincontainer">
<div class="navbar">
<div class="logo">
<a href="{{ url_for('home') }}">
<img src="https://i.imgur.com/t2ZlJ06.png" />
</a>
</div>
<a
href="{{ url_for('home') }}"
class="{{ 'active' if active_page == 'books' else '' }}"
>Home</a
>
<a
href="{{ url_for('addBook') }}"
class="{{ 'active' if active_page == 'add' else '' }}"
>Add a book</a
>
<div class="space"></div>
<a href="#">Settings</a>
<a href="{{ url_for('logout') }}">Logout</a>
</div>
<div class="content">{% block content %}{% endblock %}</div>
<div class="credits">Built with ❤ using Flask</div>
</div>
</body>
</html>
from bam import db
from bam import db, bcrypt
from bam.models import User
# Create databases
db.create_all()
# Create a test user
db.session.add(
User(
username="bookmaster",
email="bookmaster@example.com",
password=bcrypt.generate_password_hash("masterofbooks").decode("utf-8"),
)
)
db.session.commit()
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment