Skip to content

Commit 435ba7e

Browse files
committed
Feat(user): Add user authentication, Add pagination
- Can now create register new user, login - some extra frontend like userprofile
1 parent 6ebf54c commit 435ba7e

File tree

17 files changed

+622
-87
lines changed

17 files changed

+622
-87
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,6 @@ htmlcov/
1111

1212
dist/
1313
build/
14-
*.egg-info/
14+
*.egg-info/
15+
16+
.env

requirements.txt

180 Bytes
Binary file not shown.

saleapp/__init__.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,30 @@
1+
import os
12
import sys
23
from flask import Flask
34
from flask_sqlalchemy import SQLAlchemy
45
from flask_admin import Admin
6+
from flask_login import LoginManager
57

6-
app = Flask(__name__)
8+
import cloudinary
79

8-
app.secret_key = b'_5#y2L"F4Q8z\n\xec]/'
10+
app = Flask(__name__)
911

12+
app.secret_key = '_5#y2L"F4Q8z\n\xec]/'
1013
app.config["SQLALCHEMY_DATABASE_URI"] = "mysql+pymysql://root:123456789@localhost/flashsaledb?charset=utf8mb4"
1114
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = True
15+
app.config["PAGE_SIZE"] = 8
1216

1317
db = SQLAlchemy(app)
1418

19+
login_manager = LoginManager(app)
20+
1521
admin = Admin(app, name='ĐiệnMáyXanhLè Admin', template_mode='bootstrap4')
1622

23+
cloudinary.config(
24+
cloud_name=os.getenv('CLOUD_NAME'),
25+
api_key=os.getenv('API_KEY'),
26+
api_secret=os.getenv('API_SECRET'))
27+
28+
1729
if not '-m' in sys.argv:
1830
from saleapp import routes

saleapp/admin.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from saleapp import admin, db
2-
from saleapp.models import Category, Product, Tag
2+
from saleapp.models import Category, Product, Tag, User
33
from flask_admin.contrib.sqla import ModelView
44

55
# Custom Views Config
@@ -14,4 +14,5 @@ class ProductView(ModelView):
1414

1515
admin.add_view(ModelView(Category, db.session))
1616
admin.add_view(ProductView(Product, db.session))
17+
admin.add_view(ModelView(User, db.session))
1718
admin.add_view(ModelView(Tag, db.session))

saleapp/models.py

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
from datetime import datetime
2-
from sqlalchemy import Column, Integer, String, Float
2+
from enum import Enum as UserEnum
3+
4+
from flask_login import UserMixin
5+
from sqlalchemy import Column, Enum, Float, Integer, String
36
from sqlalchemy.orm import backref, relationship
47
from sqlalchemy.sql.schema import ForeignKey
5-
from sqlalchemy.sql.sqltypes import DateTime
8+
from sqlalchemy.sql.sqltypes import Boolean, DateTime, Enum
69

710
from saleapp import db
811

12+
913
class BaseModel(db.Model):
1014
# Bảng trung gian cho các bảng khác, và không muốn tạo bảng này khi db.create_all()
1115
# thì ta phải dùng lệnh __abstract__
@@ -35,6 +39,7 @@ class Product(BaseModel):
3539
description = Column(String(255), nullable=False)
3640
price = Column(Float, default=0)
3741
image = Column(String(255), nullable=False)
42+
active = Column(Boolean, default=True)
3843
created_at = Column(DateTime, default=datetime.now())
3944
category_id = Column(Integer, ForeignKey(Category.id), nullable=False)
4045
# lazy=subquery: Truy cập 1 product tự động truy vấn danh sach tags luôn
@@ -51,28 +56,44 @@ def __str__(self):
5156
return self.name
5257

5358

59+
class UserRole(UserEnum):
60+
ADMIN = 1
61+
USER = 2
62+
63+
64+
class User(BaseModel, UserMixin):
65+
name = Column(String(50), nullable=False)
66+
username = Column(String(50), nullable=False, unique=True)
67+
password = Column(String(50), nullable=False)
68+
email = Column(String(50), nullable=False)
69+
active = Column(Boolean, default=True)
70+
created_at = Column(DateTime, default=datetime.now())
71+
avatar = Column(String(100))
72+
user_role = Column(Enum(UserRole), default=UserRole.USER)
73+
def __str__(self):
74+
return self.name
5475

5576

5677

5778

5879
if __name__ == '__main__':
59-
# db.create_all()
60-
c1 = Category(name="Mobile")
61-
c2 = Category(name="tablet")
80+
db.create_all()
81+
# c1 = Category(name="Mobile")
82+
# c2 = Category(name="tablet")
6283

63-
p1 = Product(name='Mobile',description='Apple, 32GB, RAM: 3GB, iOS13',price="17000000",image='https://cdn.tgdd.vn/Products/Images/42/78124/iphone-7-plus-gold-400x460-400x460.png',category_id=1)
64-
p2 = Product(name='iPad Pro 2020',description='Apple, 128GB, RAM: 6GB',price="37000000",image='https://cdn.tgdd.vn/Products/Images/522/221775/ipad-pro-12-9-inch-wifi-128gb-2020-xam-400x460-1-400x460.png', category_id= 2)
84+
# p1 = Product(name='Mobile',description='Apple, 32GB, RAM: 3GB, iOS13',price="17000000",image='https://cdn.tgdd.vn/Products/Images/42/78124/iphone-7-plus-gold-400x460-400x460.png',category_id=1)
85+
# p2 = Product(name='iPad Pro 2020',description='Apple, 128GB, RAM: 6GB',price="37000000",image='https://cdn.tgdd.vn/Products/Images/522/221775/ipad-pro-12-9-inch-wifi-128gb-2020-xam-400x460-1-400x460.png', category_id= 2)
6586

66-
t1 = Tag(name='new')
67-
t2 = Tag(name='promotion')
87+
# t1 = Tag(name='new')
88+
# t2 = Tag(name='promotion')
6889

69-
db.session.add(c1)
70-
db.session.add(c2)
90+
# db.session.add(c1)
91+
# db.session.add(c2)
7192

72-
db.session.add(p1)
73-
db.session.add(p2)
93+
# db.session.add(p1)
94+
# db.session.add(p2)
7495

75-
db.session.add(t1)
76-
db.session.add(t2)
96+
# db.session.add(t1)
97+
# db.session.add(t2)
7798

78-
db.session.commit()
99+
# db.session.commit()

saleapp/routes.py

Lines changed: 90 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,43 @@
1+
import math
2+
3+
import cloudinary.uploader
4+
from flask import request, url_for
15
from flask.templating import render_template
2-
from flask import request
3-
from saleapp import app, utils
6+
from flask_login import login_user, logout_user
7+
from werkzeug.utils import redirect
8+
9+
from saleapp import app, login_manager, utils
410
# Import admin
511
from saleapp.admin import *
612

713

14+
@app.context_processor
15+
def inject_categories():
16+
return {
17+
'categories': utils.load_categories()
18+
}
19+
20+
@login_manager.user_loader
21+
def inject_user(user_id):
22+
return utils.get_user_by_id(user_id=user_id)
23+
24+
825
@app.route('/')
926
def home():
10-
print(utils.load_products())
1127
# Load all Products
28+
kw = request.args.get('keyword')
29+
page = request.args.get('page', 1)
30+
cate_id = request.args.get('category_id')
31+
products = utils.load_products(category_id=cate_id, name=kw, page=int(page))
32+
products_size = utils.count_products()
33+
1234
return render_template(
1335
"./pages/home.html",
1436
title="Tất cả sản phẩm",
15-
products=utils.load_products(),
16-
categories=utils.load_categories()
17-
)
37+
products=products,
38+
pages=math.ceil(products_size/app.config['PAGE_SIZE']))
1839

1940

20-
# products Route
21-
@app.route('/products', methods=['GET'])
22-
def products():
23-
# get parameters
24-
_id = int(request.args.get('category_id', 0))
25-
26-
# Filter python objects with list comprehensions
27-
output_products = utils.load_products_by_categoryId(_id)
28-
29-
return render_template(
30-
"./pages/products.html",
31-
title="Products",
32-
products=output_products,
33-
)
34-
3541
# search Route
3642
@app.route('/search', methods=['GET'])
3743
def search():
@@ -43,13 +49,74 @@ def search():
4349

4450
# Filter by name or by price
4551
if (searchByName):
46-
output_products = utils.load_products(keyword)
52+
output_products = utils.load_products(name=keyword)
4753
else:
48-
output_products = utils.load_products_by_price(from_price, to_price)
54+
output_products = utils.load_products(from_price=from_price, to_price=to_price)
4955

5056
return render_template(
5157
"./pages/search.html",
5258
title=('%s Giá tốt nhất' % keyword) if searchByName else ('%s VNĐ đến %s VNĐ' % (from_price, to_price)),
5359
products=utils.load_products(),
5460
filtered_products=output_products,
5561
)
62+
63+
64+
@app.route('/register', methods=['GET', 'POST'])
65+
def user_register():
66+
err_msg = ""
67+
if request.method == 'POST':
68+
# lay du lieu trong request form
69+
name = request.form['name']
70+
username = request.form['username']
71+
email = request.form['email']
72+
password = request.form['password']
73+
confirm = request.form['confirm']
74+
avatar = request.files['avatar']
75+
avatar_path = None
76+
77+
# them vao db
78+
try:
79+
if password == confirm:
80+
if avatar:
81+
upload_result = cloudinary.uploader.upload(avatar)
82+
avatar_path = upload_result['secure_url']
83+
84+
utils.addUser(name=name, username=username, password=password, email=email, avatar=avatar_path)
85+
return redirect(url_for('home'))
86+
else:
87+
err_msg = "Wrong password"
88+
except Exception as exception:
89+
err_msg = 'Error from server: ' + str(exception)
90+
91+
return render_template(
92+
"./pages/register.html",
93+
title="Register New User",
94+
err_msg=err_msg
95+
)
96+
97+
@app.route('/user-login', methods=['GET', 'POST'])
98+
def user_login():
99+
err_msg = ""
100+
if request.method == 'POST':
101+
username = request.form['username']
102+
password = request.form['password']
103+
user = utils.check_login(username=username, password=password)
104+
105+
if user:
106+
login_user(user)
107+
print("logib status:%s" % login_user(user))
108+
print(user.name) # output a name
109+
return redirect(url_for('home'))
110+
else:
111+
err_msg = "Wrong username or password"
112+
113+
return render_template(
114+
"./pages/login.html",
115+
title="Login",
116+
err_msg=err_msg
117+
)
118+
119+
@app.route('/user-logout')
120+
def user_logout():
121+
logout_user()
122+
return redirect(url_for('user_login'))

saleapp/static/css/style.css

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,101 @@ output::after {
5555
}
5656

5757

58+
/*
59+
User Profile
60+
*/
61+
62+
.dropdown-menu {
63+
padding: 0.7rem 0rem;
64+
font-size: 0.875rem;
65+
line-height: 22px;
66+
color: #5c5776;
67+
border: none;
68+
box-shadow: 0 10px 30px 0 rgba(31, 45, 61, 0.1);
69+
border-radius: 0.5rem;
70+
}
71+
72+
.dropdown-menu {
73+
display: block;
74+
visibility: hidden;
75+
opacity: 0;
76+
transform: translateY(20px);
77+
transition: all 0.3s ease-in;
78+
}
79+
80+
.dropdown:hover>.dropdown-menu {
81+
transform: scaleY(1);
82+
opacity: 1;
83+
visibility: visible;
84+
}
85+
86+
.dropdown-submenu:hover>.dropdown-menu {
87+
transform: scaleY(1);
88+
opacity: 1;
89+
visibility: visible;
90+
}
91+
92+
@media (min-width: 990px) {
93+
.dropright-lg {
94+
position: relative;
95+
}
96+
97+
.dropright-lg .dropdown-menu {
98+
top: 0;
99+
right: auto;
100+
left: 100%;
101+
margin-top: 0;
102+
margin-right: 0.125rem;
103+
}
104+
}
105+
106+
.dropdown-toggle::after {
107+
display: inline-block;
108+
margin-left: 0.255em;
109+
vertical-align: 0.255em;
110+
content: ">";
111+
border-top: 0rem;
112+
border-right: 0rem;
113+
border-bottom: 0;
114+
border-left: 0rem;
115+
float: right;
116+
}
117+
118+
.avatar-md {
119+
width: 56px;
120+
height: 56px;
121+
}
122+
123+
.avatar img {
124+
width: 100%;
125+
height: 100%;
126+
-o-object-fit: cover;
127+
object-fit: cover;
128+
}
129+
130+
.avatar {
131+
position: relative;
132+
display: inline-block;
133+
width: 3rem;
134+
height: 3rem;
135+
font-size: 1rem;
136+
}
137+
138+
.avatar-online:before {
139+
background-color: green;
140+
}
141+
142+
.avatar-indicators:before {
143+
content: "";
144+
position: absolute;
145+
bottom: 0px;
146+
right: 5%;
147+
width: 30%;
148+
height: 30%;
149+
border-radius: 50%;
150+
border: 2px solid #fff;
151+
display: table;
152+
}
58153

59154
/*
60155
Footer

saleapp/templates/base.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
</main>
2020

21-
<footer class="footer" id="footer">
21+
<footer class="footer mt-4" id="footer">
2222
{% include './layout/footer.html' %}
2323
</footer>
2424

0 commit comments

Comments
 (0)