10-1 : 도서관 대여 서비스 (개인 프로젝트)

[엘리스 AI 트랙] 10주차 - 1

  • comment 테이블 추가
  • 책 상세 페이지 구현
  • 상세 페이지에서 댓글 남기기 기능 구현
  • 댓글 내용과 평점 null인 경우 댓글 등록 금지 기능 구현
  • 메인 페이지에서 평균 평점 보여주는 기능 구현
  • 이메일 중복 체크, 유효한 이메일 형식 체크
  • 이름 유효한 형식 체크
  • 비밀번호 유효한 형식 체크
  • 현재 대여중인 책 중복으로 빌리지 못하게 하는 기능 추가
  • 대여 기록 페이지 구현

TIL

1. comment 테이블 추가

 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;
    • 제약 조건에 댓글을 보여줄 때 최신순으로 보여주는 기능이 있어서, date보다는 datetime으로 저장하는 게 나을 것 같아서 수정했다.
    mysql> alter table comment_tb modify created_at 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
    • mysql에서 테이블 전체 데이터 삭제하는 방법
      delete from mytable;


2. 책 상세 페이지 구현 & 3. 상세 페이지에서 댓글 남기기 기능 구현

  • 책 이름 누르면 상세 페이지로 가게 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>
    @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"})


4. 댓글 내용과 평점 null인 경우 댓글 등록 금지 기능 구현

  • 댓글 등록하기 버튼을 누르면 실행되는 javascript 함수에서 아래처럼 검사한다.

    let comment = $("#comment").val()
    let star_rating = $("#star-rating").val()
    
    if (comment == '' || star_rating == '') {
        alert("댓글과 평점은 필수 항목입니다.");
        return;
    }


5. 메인 페이지에서 평균 평점 보여주는 기능 구현

  • 본문 사이에 아래와 같이 추가해서 간단하게 구현했다. python에서 문자열 곱하기 기능을 이용했다.

    <p class="stock">{{"★"*book.rating_avg}}</p>


6. 이메일 중복 체크, 유효한 이메일 형식 체크

  • 유효한 이메일 형식은 구글링으로 정규표현식을 이용해서 처리했다.
    - 유효한 이메일 정규표현식 참고 : 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;
            }
        }
    • 이메일 중복 체크는 백엔드 코드에서 다음처럼 이미 해당 email을 사용하는 user가 있는지 검사한다. 이미 있으면 duplicate라는 결과 값을 넘겨주었다.
    user = User.query.filter(User.email == user_email).first()
    if user is not None:
        return jsonify({"result": "duplicate"})
    • 받은 결과로 알림을 띄워준다. (ajax 내부 코드)
    success: function (res) {
        if (res['result'] == 'success') {
            alert("회원가입 성공!");
            window.location.href = '/'
        }
        else if (res['result'] == 'duplicate'){
            alert("중복된 아이디입니다.");
            window.location.reload()
        }
    }


7. 이름 유효한 형식 체크

  • 구글링으로 한글 또는 영문인지 검사하는 정규표현식을 이용했다.
    - 참고 : https://codingbroker.tistory.com/119
    function validateName(inputText){
        const regex = /^[ㄱ-ㅎ|가-힣|a-z|A-Z|]+$/;
        if(inputText.match(regex)){
            return true;
        }
        else{
            alert("이름은 영문 또는 한글만 입력 가능합니다.");
            return false;
        }
    }


8. 비밀번호 유효한 형식 체크


9. 현재 대여중인 책 중복으로 빌리지 못하게 하는 기능 추가

  • 백엔드 코드 : 현재 대여중인 책은 아직 반납일이 기록되지 않아 null 값이다. 따라서 반납일이 아직 설정되지 않은 같은 책이라면 중복이라고 결과를 전달한다.

    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"})
    • 프론트 코드에서 ajax의 success 결과를 받아 다음 코드를 넣어주었다.
    else if (result == "duplicated") {
        alert("이미 대여한 책입니다.")
    }


10. 대여 기록 페이지 구현

  • html 코드는 거의 return 페이지와 유사하다.

    • 백엔드 코드도 return 부분과 비슷한데 다른 점 한가지는 반납일 컬럼 값이 null 값이 아니어야 반납된 책이다. 따라서 이미 반납된 책만 불러와야 한다. sqlalchemy에서 다음처럼 null값 검사를 할 수 있다. filter 내부에 Rent.return_date.isnot(None) 형식으로 조건을 줄 수 있다. Rent.return_date!=None은 에러가 발생한다.
      (참고 : https://stackoverflow.com/questions/21784851/sqlalchemy-is-not-null-select)
    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()
    • 대여 기록 페이지 결과 화면

좋은 웹페이지 즐겨찾기