-
Flask Project - MyBlog contactForm, mypage 추가하기프로젝트 2024. 4. 23. 23:01반응형
각 카테고리별 게시글, 특정 사용자의 게시글 등을 확인할 수 있도록 posts_list.html을 적절하게 활용해주겠습니다. 그 뒤에 블로그와 블로그 소유주에 대한 소개를 하는 about_me 페이지와, 문제사항 제보할 수 있는 contact 페이지를 수정해보겠습니다. 그리고 로그인한 후 자신의 정보를 확인해볼 수 있는 mypage를 추가해보겠습니다.
1. posts_list 활용 엔드포인트 추가
home에서는 모든 post, posts_list 에서는 해당 카테고리 post, 사용자 post(user_posts)에서는 사용자가 작성한 모든 post를 보여주도록 하겠습니다.
posts_list.html에서 post_category와 post_owner_name 부분을 클릭하면 해당 카테고리, 사용자의 게시글을 볼 수 있도록 연결해줍니다.
<!-- flask_app/blog/templates/views/posts_list.html --> {% extends "base.html" %} {% block title %} {% if type == 'home' %} MyBlog - home {% elif type == 'category_posts' %} MyBlog - {{category_name}} Category {% else %} MyBlog - {{selected_user.username}} Posts {% endif %} {% endblock %} {% block header %} <header class="masthead" style="background-image: url('../static/assets/img/post-bg.jpg')"> <div class="container position-relative px-4 px-lg-5"> <div class="row gx-4 gx-lg-5 justify-content-center"> <div class="col-md-10 col-lg-8 col-xl-7"> <div class="site-heading"> {% if type == 'user_posts' %} <h2 id="user_wrapper">User: {{selected_user.username}}</h2> {% else %} <h2 id="category_wrapper">Category : {{category_name}}</h2> {% endif %} <span class="subheading" id="posts_count">총 {{ posts | length }}개의 포스트가 있습니다.</span> </div> </div> </div> </div> </header> {% endblock %} {% block content %} <div class="container px-4 px-lg-5"> <div class="row gx-4 gx-lg-5 justify-content-center"> <div class="col-md-10 col-lg-8 col-xl-7"> {% if posts | length %} {% for post in posts %} <div class="post-preview"> <!-- 제목 --> <a href="{{url_for('views.post', post_id=post.id)}}"> <h6 class="post-title" id="post_title">{{post.title}}</h6> </a> <!-- 작성자, 날짜 --> <p class="post-meta"> Category: <a href="{{url_for('views.posts_list', category_id=post.category_id)}}" id="post_category">{{post.category.name}}</a> Posted by <a href="{{url_for('views.user_posts', user_id=post.author_id)}}" id="post_owner_name">{{post.user.username}}</a> <br> {{post.date_created | datetime}} </p> </div> <!-- Divider--> <hr class="my-4" /> {% endfor %} {% else %} <h2 style="text-align: center;">Post가 존재하지 않습니다.</h2> <br><br> {% endif %} </div> </div> </div> {% endblock %}
그리고 home과 posts_list, user_posts(새로운 엔드포인트)를 아래처럼 수정해줍니다.
# flask_app/blog/views.py @views.route("/") @views.route("/home") def home(): posts = db.session.query(get_model('post')).options( selectinload(get_model('post').user), selectinload(get_model('post').category)).all() return render_template(BASE_VIEWS_DIR + "posts_list.html", user=current_user, posts=posts, type='home', category_name='all', ) @views.route('/category') def category(): categories = db.session.query(get_model('category')).all() return render_template(BASE_VIEWS_DIR + "category.html", user=current_user, categories=categories) @views.route("/posts-list/<int:category_id>") def posts_list(category_id): selected_category = db.session.get(get_model('category'), category_id) category_posts = db.session.query(get_model('post')).filter_by(category_id=category_id).options( selectinload(get_model('post').user)).all() if category_posts is None: flash('해당 카테고리는 존재하지 않습니다.', category="error") return redirect(url_for('views.category')) return render_template(BASE_VIEWS_DIR + "posts_list.html", user=current_user, posts=category_posts, type='category_posts', category_name=selected_category.name, ) @views.route("/user_posts/<int:user_id>") def user_posts(user_id): selected_user = db.session.get(get_model('user'), user_id) user_posts = db.session.query(get_model('post')).filter_by(author_id=user_id).options( selectinload(get_model('post').category)).all() if user_posts is None: flash('해당 유저가 작성한 글이 존재하지 않습니다.', category="error") return redirect(url_for('views.home')) return render_template(BASE_VIEWS_DIR + "posts_list.html", user=current_user, posts=user_posts, type='user_posts', selected_user=selected_user, )
이제 특정 카테고리, 특정 사용자의 게시글을 확인할 수 있습니다.
2. about_me 페이지 수정
<!-- flask_app/blog/templates/views/about_me.html --> {% extends "base.html" %} {% block title %} MyBlog - About Me {% endblock %} {% block header %} <header class="masthead" style="background-image: url('../static/assets/img/about-bg.jpg')"> <div class="container position-relative px-4 px-lg-5"> <div class="row gx-4 gx-lg-5 justify-content-center"> <div class="col-md-10 col-lg-8 col-xl-7"> <div class="site-heading"> <h2>About Me</h2> <span class="subheading">Let me introduce myself...</span> </div> </div> </div> </div> </header> {% endblock %} {% block content %} <section class="about-section text-center" style="margin: 24px;"> <div class="row"> <div class="col mx-auto"> <p class="text"> <b>Name:</b> 이름 <br> <b>Email</b>: 이메일 <br> <b>github:</b> <a href="github 링크">github 링크</a><br><br> </p> </div> </div> </section> {% endblock %}
3. contact 기능(model, form 생성)
먼저 사용자가 문제 사항을 제보할 수 있도록 contact 페이지에서 입력을 받을 예정입니다. 사용자 id와 정보를 저장하면 되므로 아래처럼 Message 모델을 생성해줍니다.
class Message(db.Model): id = db.Column(db.Integer, primary_key=True) content = db.Column(db.Text, nullable=False) user_id = db.Column(db.Integer, db.ForeignKey('user.id', ondelete='CASCADE'), nullable=False) user = db.relationship('User', back_populates='user_messages') def get_model(arg): models = { 'user': User, 'post': Post, 'category': Category, 'comment': Comment, 'message': Message } return models[arg]
모델 생성 후 사용자 입력을 위한 ContactForm을 생성해줍니다. 이 때 현재 사용자의 이름과 이메일은 자동으로 채워지고, 변경 불가능하도록 아래처럼 설정해줍니다.
class ContactForm(FlaskForm): name = StringField('name', render_kw={'readonly': True}) email = EmailField('email', render_kw={'readonly': True}) content = TextAreaField('content', validators=[DataRequired('내용을 입력해주세요.')]) def __init__(self, *args, **kwargs): super(ContactForm, self).__init__(*args, **kwargs) self.name.data = current_user.username self.email.data = current_user.email
이제 해당 폼을 사용하기 위해 contact.html을 아래처럼 수정해줍니다.
<!-- flask_app/blog/templates/views/contact.html --> {% extends "base.html" %} {% block title %} MyBlog - Contact Me {% endblock %} {% block header %} <header class="masthead" style="background-image: url('../static/assets/img/about-bg.jpg')"> <div class="container position-relative px-4 px-lg-5"> <div class="row gx-4 gx-lg-5 justify-content-center"> <div class="col-md-10 col-lg-8 col-xl-7"> <div class="site-heading"> <h2>Contact Me</h2> <span class="subheading">Feel free to use the contact form below to send me a message</span> </div> </div> </div> </div> </header> {% endblock %} {% block content %} <section class="about-section text-center" style="margin: 24px;"> <div class="row justify-content-center"> <div class="row" style="border: 1px solid; padding: 8px; max-width: 600px; min-width: 200px;"> <div class="col mx-auto"> <form id="contactForm" method="POST"> {{form.csrf_token}} {% for field in form if field.name != 'csrf_token'%} <div class="form-floating"> {{ field(id=field.id, class="form-control" ~ (' is-invalid' if field.errors else ''), required=True, style=("font-size: 16px;" ~ ("height:200px; resize: none;" if field.name == 'content' else ""))) }} <label for="{{ field.id }}" {% if field.errors %} class="invalid-feedback" {% endif %}>{{ field.label.text }}</label> {% for error in field.errors %} <span class="invalid-feedback" style="font-size: 16px; color:red;">{{ error }}</span> {% endfor %} </div> {% endfor %} <br /> <button class="btn btn-primary text-uppercase" id="submitButton" type="submit">send</button> </form> </div> </div> </div> </section> {% endblock %}
4. contact 엔드포인트 수정
아래처럼 사용자로부터 입력받고 db에 저장한 뒤 content를 초기화한 뒤 다시 돌려줍니다.
@views.route("/contact", methods=['GET', 'POST']) def contact(): form = ContactForm() if request.method == 'POST' and form.validate_on_submit(): message = get_model('message')( content=form.content.data, user_id=current_user.id, ) db.session.add(message) db.session.commit() flash('메세지 전송이 완료되었습니다.', category="success") form.content.data = '' # 폼 초기화 return render_template(BASE_VIEWS_DIR + "contact.html", user=current_user, form=form, )
5. mypage 생성(User 모델 수정)
기본적으로 작성한 게시글, 댓글 개수만 출력하도록 했습니다. 이 때 User 모델에 posts_count, comments_count 필드를 추가해줍니다. 단지 개수만 출력할 때 posts, comments 정보를 가져오는 것은 낭비이기 때문입니다.
sqlalchemy의 event listener를 활용해서, post, comment 추가시 posts_count, comments_count를 변경할 수 있도록 구현했습니다.
아래 수정사항을 확인해주세요.
# flask_app/blog/models.py class User(db.Model, UserMixin): __tablename__ = 'user' # 테이블 이름 명시적 선언 id = db.Column(db.Integer, primary_key=True) # primary key 설정 username = db.Column(db.String(150), unique=True) # username unique email = db.Column(db.String(150), unique=True) # email unique password = db.Column(db.String(150)) # password date_created = db.Column(db.DateTime, default=datetime.now(KST_offset)) # 회원가입 날짜, 시간 기록 post_create_permission = db.Column(db.Boolean, default=False) # 글 작성 권한 여부 admin_check = db.Column(db.Boolean, default=False) # 관리자 권한 여부 posts_count = db.Column(db.Integer, default=0) comments_count = db.Column(db.Integer, default=0) user_posts = db.relationship('Post', back_populates='user', cascade='delete, delete-orphan', lazy='dynamic') user_comments = db.relationship('Comment', back_populates="user", cascade='delete, delete-orphan', lazy='dynamic') user_messages = db.relationship('Message', back_populates='user', cascade='delete, delete-orphan', lazy='dynamic') def __init__(self, username, email, password, post_create_permission=False, admin_check=False, posts_count=0, comments_count=0): self.username = username self.email = email self.password = generate_password_hash(password) self.date_created = datetime.now(KST_offset) self.post_create_permission = post_create_permission self.admin_check = admin_check self.posts_count = posts_count self.comments_count = comments_count def update_myinfo(self): self.posts_count = self.user_posts.count() self.comments_count = self.user_comments.count() db.session.commit() def __repr__(self): return f'{self.__class__.__name__} {self.id}: {self.username}' from sqlalchemy import event, func from sqlalchemy.orm iimport object_session @event.listens_for(db.session, 'before_flush') def after_insert_and_delete(session, flush_context, instances): for obj in session.new | session.deleted: if isinstance(obj, get_model('comment')): post = db.session.get(get_model('post'), obj.post_id) post.comments_count = object_session(obj).query(func.count(get_model('comment').id)).filter_by(post_id=obj.post_id).scalar() user = db.session.get(get_model('user'), obj.author_id) user.comments_count = object_session(obj).query(func.count(get_model('comment').id)).filter_by(author_id=obj.author_id).scalar() if obj in session.new: # 추가 post.comments_count += 1 user.comments_count += 1 else: # 삭제 post.comments_count -= 1 user.comments_count -= 1 elif isinstance(obj, get_model('post')): user = db.session.get(get_model('user'), obj.author_id) user.posts_count = object_session(obj).query(func.count(get_model('post').id)).filter_by(author_id=obj.author_id).scalar() if obj in session.new: # 추가 user.posts_count += 1 else: # 삭제 user.posts_count -= 1
# flask_app/blog/views.py @views.route('/mypage') def mypage(): current_user.update_myinfo() return render_template(BASE_VIEWS_DIR + "mypage.html", user=current_user)
<!-- flask_app/blog/templates/views/mypage.html --> {% extends "base.html" %} {% block title %} MyBlog - MyPage {% endblock %} {% block header %} <header class="masthead" style="background-image: url('../static/assets/img/about-bg.jpg')"> <div class="container position-relative px-4 px-lg-5"> <div class="row gx-4 gx-lg-5 justify-content-center"> <div class="col-md-10 col-lg-8 col-xl-7"> <div class="site-heading"> <h2>Current User: {{user.username}}</h2> </div> </div> </div> </div> </header> {% endblock %} {% block content %} <section class="about-section text-center" style="margin: 24px;"> <div class="row"> <div class="col mx-auto"> <p class="text"> <b>Name:</b> {{user.username}} <br> <b>Email:</b> {{user.email}} <br> <b>작성한 게시글 개수:</b> {{user.posts_count}}<br> <b>작성한 댓글 개수:</b> {{user.comments_count}}<br> </p> </div> </div> </section> {% endblock %}
반응형'프로젝트' 카테고리의 다른 글
Flask Project - MyBlog 댓글 추가하기(feat. flask-migrate) (0) 2024.04.22 Flask Project - MyBlog CI/CD 적용하기(feat. Github Actions) (2) 2024.04.19 Flask Project - MyBlog 배포하기(nginx+gunicorn+flask with docker-compose) (3) 2024.04.18 Flask Project - MyBlog views(post 생성,수정,삭제) with unittest (0) 2024.04.11 Flask Project - MyBlog auth(회원가입, 로그인, 로그아웃) with unittest (0) 2024.04.09