[엘리스 AI 트랙] 10주차 - 1
- comment 테이블 추가
- 책 상세 페이지 구현
- 상세 페이지에서 댓글 남기기 기능 구현
- 댓글 내용과 평점 null인 경우 댓글 등록 금지 기능 구현
- 메인 페이지에서 평균 평점 보여주는 기능 구현
- 이메일 중복 체크, 유효한 이메일 형식 체크
- 이름 유효한 형식 체크
- 비밀번호 유효한 형식 체크
- 현재 대여중인 책 중복으로 빌리지 못하게 하는 기능 추가
- 대여 기록 페이지 구현
mysql> create table `comment_tb`(
-> _id int primary key auto_increment not null,
-> user_id int not null,
-> book_id int not null,
-> comment text,
-> star_rating int not null,
-> created_at date,
-> foreign key(user_id) references user_tb(_id) on update cascade,
-> foreign key(book_id) references books_tb(_id) on update cascade);
상세 페이지에서 댓글을 남기면 기록할 comment 테이블을 추가했다. 내용 없이 별점만 남기는 경우가 많아서 별점만 not null로 만들었는데, 제약 조건을 다시 보니 댓글 내용도 null 이 아니어야 해서 alter로 수정했다.
(참고 : https://blog.naver.com/PostView.nhn?isHttpsRedirect=true&blogId=siiyoo&logNo=70133007298)
mysql> alter table comment_tb modify comment text not null;
mysql> alter table comment_tb modify created_at datetime;
db.Datetime
이 없다고 오류가 나서 구글링을 또 한참 열심히 했다. 결론은 db.DateTime
이다. 대문자 한 끝 차이였다. 조심하자. (참고 : https://stackoverflow.com/questions/62262102/attributeerror-sqlalchemy-object-has-no-attribute-datetime)class Comment(db.Model):
__tablename__='comment_tb'
# 1. 작성자 2. 책id 3. 내용 4. 별점 5. 작성일
_id = db.Column(db.Integer, primary_key=True, nullable=False, autoincrement=True)
user_id = db.Column(db.Integer, db.ForeignKey('user_tb._id'), nullable=False)
book_id = db.Column(db.Integer, db.ForeignKey('books_tb._id'), nullable=False)
comment = db.Column(db.Text, nullable=False)
star_rating = db.Column(db.Integer, nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
def __init__(self, user_id, book_id, comment, star_rating):
self.user_id = user_id
self.book_id = book_id
self.comment = comment
self.star_rating = star_rating
delete from mytable;
책 이름 누르면 상세 페이지로 가게 url_for 이용
→ 여기서 url을 어떤 식으로 넘겨줄지 많은 고민과 에러와 싸운 끝에 다음처럼 구현.
(참고 : https://flask.palletsprojects.com/en/2.0.x/api/#flask.url_for)
<h5 class="book-name"><a href="{{ url_for('.bookInfo', book_id = book._id) }}">{{ book.book_name }}</a></h5>
from sqlalchemy import func
를 추가한다.ratings = db.session.query(db.func.avg(Comment.star_rating).label("rating_avg")).filter(Comment.book_id==book_id).first()
처럼 db.func.avg 형태로 쓸 수 있다. label을 추가하면 sql에서 as로 alias 설정(별명지어주는 기능)과 같다.@board.route("/info/<int:book_id>", methods=["GET", "POST"])
def bookInfo(book_id):
book = Books.query.filter(Books._id==book_id).first()
if request.method == "GET":
# 책 정보 모두, 댓글 정보 모두
comments = Comment.query.filter(Comment.book_id==book_id).order_by(Comment.created_at.desc()).all()
return render_template("info.html", book=book, comments=comments)
else:
# 댓글 추가 -> comment 테이블에 값 추가
commenter = request.form['commenter']
book_id = request.form['book_id']
comment = request.form['comment']
star_rating = request.form['star_rating']
c = Comment(commenter, book_id, comment, star_rating)
db.session.add(c)
db.session.commit()
ratings = db.session.query(db.func.avg(Comment.star_rating).label("rating_avg")).filter(Comment.book_id==book_id).first()
book.rating_avg = round(ratings.rating_avg)
db.session.commit()
return jsonify({"result": "success"})
댓글 등록하기 버튼을 누르면 실행되는 javascript 함수에서 아래처럼 검사한다.
let comment = $("#comment").val()
let star_rating = $("#star-rating").val()
if (comment == '' || star_rating == '') {
alert("댓글과 평점은 필수 항목입니다.");
return;
}
본문 사이에 아래와 같이 추가해서 간단하게 구현했다. python에서 문자열 곱하기 기능을 이용했다.
<p class="stock">{{"★"*book.rating_avg}}</p>
유효한 이메일 형식은 구글링으로 정규표현식을 이용해서 처리했다.
- 유효한 이메일 정규표현식 참고 : https://www.w3resource.com/javascript/form/email-validation.php
- 정규 표현식 참고 : https://heropy.blog/2018/10/28/regexp/
- Uncaught TypeError: Cannot read properties of undefined (reading 'match')
에러 해결 : 위의 정규표현식 참고 페이지에서 복사해서 사용하니 이런 에러가 나왔다. 이는 if문 내부에 input.value.match(mailformat)
으로 설정되어 있던 코드에서 value를 지워주니 해결되었다.
function validateEmail(inputText)
{
let mailformat = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/;
if(inputText.match(mailformat))
{
return true;
}
else
{
alert("유효하지 않은 이메일입니다.");
return false;
}
}
user = User.query.filter(User.email == user_email).first()
if user is not None:
return jsonify({"result": "duplicate"})
success: function (res) {
if (res['result'] == 'success') {
alert("회원가입 성공!");
window.location.href = '/'
}
else if (res['result'] == 'duplicate'){
alert("중복된 아이디입니다.");
window.location.reload()
}
}
function validateName(inputText){
const regex = /^[ㄱ-ㅎ|가-힣|a-z|A-Z|]+$/;
if(inputText.match(regex)){
return true;
}
else{
alert("이름은 영문 또는 한글만 입력 가능합니다.");
return false;
}
}
```jsx
function validatePW(inputText){
var strongRegex = new RegExp("^(?=.*[a-zA-Z])(?=.*[0-9])(?=.*[!@#\$%\^&\*])(?=.{8,})");
if(inputText.match(strongRegex)){
return true;
}
else{
alert("영문, 숫자, 특수문자를 모두 포함하여 8자리 이상의 비밀번호를 입력하세요.");
return false;
}
}
```
백엔드 코드 : 현재 대여중인 책은 아직 반납일이 기록되지 않아 null 값이다. 따라서 반납일이 아직 설정되지 않은 같은 책이라면 중복이라고 결과를 전달한다.
TypeError: 'BaseQuery' object is not callable
에러가 나서 Querying with function on Flask-SQLAlchemy model gives BaseQuery object is not callable error 해당 링크를 참고하여 해결했다.same_book = db.session.query(Rent).filter(Rent.book_id==book_id, Rent.user_id==user_id, Rent.return_date==None).first()
if same_book is not None:
return jsonify({"result": "duplicated"})
else if (result == "duplicated") {
alert("이미 대여한 책입니다.")
}
html 코드는 거의 return 페이지와 유사하다.
Rent.return_date.isnot(None)
형식으로 조건을 줄 수 있다. Rent.return_date!=None
은 에러가 발생한다.records = db.session.query(Books.img_path, Books.book_name, Books._id, Books.rating_avg, Rent.rent_date, Rent.return_date).filter(Books._id==Rent.book_id, Rent.user_id==g.user._id, Rent.return_date.isnot(None)).all()