add post comments logic
This commit is contained in:
parent
270345c59c
commit
35d7cffa9a
|
@ -1,8 +1,8 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# alembic revision --autogenerate -m "post"
|
#alembic revision --autogenerate -m "add_title_to_post_table"
|
||||||
alembic upgrade head
|
alembic upgrade head
|
||||||
|
|
||||||
cd src
|
cd src
|
||||||
|
|
||||||
gunicorn main:app --workers 5 --worker-class uvicorn.workers.UvicornWorker --bind=0.0.0.0:8000
|
gunicorn main:app --workers 1 --worker-class uvicorn.workers.UvicornWorker --bind=0.0.0.0:8000
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
"""add_title_to_post_table
|
||||||
|
|
||||||
|
Revision ID: 2fc416358aee
|
||||||
|
Revises: 75599d686560
|
||||||
|
Create Date: 2023-06-19 23:46:01.609132
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
import sqlmodel
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '2fc416358aee'
|
||||||
|
down_revision = '75599d686560'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.add_column('post', sa.Column('title', sqlmodel.sql.sqltypes.AutoString(), nullable=False))
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_column('post', 'title')
|
||||||
|
# ### end Alembic commands ###
|
|
@ -33,6 +33,7 @@ class PostTagLink(SQLModel, table=True):
|
||||||
|
|
||||||
|
|
||||||
class PostBase(SQLModel):
|
class PostBase(SQLModel):
|
||||||
|
title: str
|
||||||
text: str
|
text: str
|
||||||
|
|
||||||
|
|
||||||
|
@ -45,6 +46,19 @@ class Post(PostBase, table=True):
|
||||||
tags: List["Tag"] = Relationship(back_populates="posts", link_model=PostTagLink)
|
tags: List["Tag"] = Relationship(back_populates="posts", link_model=PostTagLink)
|
||||||
|
|
||||||
|
|
||||||
|
class UserRead(UserBase):
|
||||||
|
posts: List[Post]
|
||||||
|
|
||||||
|
|
||||||
|
class PostRead(PostBase):
|
||||||
|
id: int
|
||||||
|
author_id: int
|
||||||
|
|
||||||
|
|
||||||
|
class PostCreate(PostBase):
|
||||||
|
author_id: int
|
||||||
|
|
||||||
|
|
||||||
class TagBase(SQLModel):
|
class TagBase(SQLModel):
|
||||||
name: str
|
name: str
|
||||||
|
|
||||||
|
@ -66,3 +80,13 @@ class Comment(CommentBase, table=True):
|
||||||
|
|
||||||
author: User | None = Relationship(back_populates="comments")
|
author: User | None = Relationship(back_populates="comments")
|
||||||
post: Post | None = Relationship(back_populates="comments")
|
post: Post | None = Relationship(back_populates="comments")
|
||||||
|
|
||||||
|
|
||||||
|
class CommentRead(CommentBase):
|
||||||
|
id: int
|
||||||
|
author_id: int
|
||||||
|
post_id: int
|
||||||
|
|
||||||
|
|
||||||
|
class CommentCreate(CommentBase):
|
||||||
|
author_id: int
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
from typing import Annotated, List
|
||||||
|
|
||||||
|
from fastapi import APIRouter, Request, Depends, Header, HTTPException, status
|
||||||
|
from fastapi.templating import Jinja2Templates
|
||||||
|
from sqlmodel import Session, select
|
||||||
|
|
||||||
|
from blog.models import Post, PostRead, PostCreate, CommentRead, CommentCreate, Comment
|
||||||
|
from database import get_session
|
||||||
|
from enums import HeaderAccept
|
||||||
|
|
||||||
|
router = APIRouter(prefix='/posts', tags=["Posts"])
|
||||||
|
|
||||||
|
templates = Jinja2Templates(directory="templates")
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/", response_model=List[PostRead])
|
||||||
|
def get_posts(
|
||||||
|
request: Request,
|
||||||
|
session: Session = Depends(get_session),
|
||||||
|
accept: Annotated[str | None, Header()] = HeaderAccept.json):
|
||||||
|
results = session.exec(select(Post)).all()
|
||||||
|
if HeaderAccept.html in accept:
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
"blog/posts.html",
|
||||||
|
{
|
||||||
|
"request": request,
|
||||||
|
"posts": list(map(lambda x: x.dict(), results))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/{post_id}", response_model=PostRead)
|
||||||
|
def get_post(
|
||||||
|
post_id: int,
|
||||||
|
request: Request,
|
||||||
|
session: Session = Depends(get_session),
|
||||||
|
accept: Annotated[str, Header()] = HeaderAccept.json):
|
||||||
|
post = session.get(Post, post_id)
|
||||||
|
if not post:
|
||||||
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Post not found")
|
||||||
|
if HeaderAccept.html in accept:
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
"blog/post.html",
|
||||||
|
{
|
||||||
|
"request": request,
|
||||||
|
"post": post.dict(),
|
||||||
|
"author": post.author.username,
|
||||||
|
"comments": list(map(lambda x: dict(**x.dict(), author=x.author.username), post.comments))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return post
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/", response_model=PostRead)
|
||||||
|
def create_post(post: PostCreate, session: Session = Depends(get_session)):
|
||||||
|
db_post = Post.from_orm(post)
|
||||||
|
session.add(db_post)
|
||||||
|
session.commit()
|
||||||
|
session.refresh(db_post)
|
||||||
|
return db_post
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/{post_id}/comments", response_model=List[CommentRead], tags=['Comments'])
|
||||||
|
def get_comments(
|
||||||
|
post_id: int,
|
||||||
|
session: Session = Depends(get_session)):
|
||||||
|
results = session.exec(select(Comment).where(Comment.post_id == post_id)).all()
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/{post_id}/comments", response_model=CommentRead, tags=['Comments'])
|
||||||
|
def add_comment(
|
||||||
|
post_id: int,
|
||||||
|
comment: CommentCreate,
|
||||||
|
session: Session = Depends(get_session)):
|
||||||
|
db_comment = Comment.from_orm(comment)
|
||||||
|
db_comment.post_id = post_id
|
||||||
|
session.add(db_comment)
|
||||||
|
session.commit()
|
||||||
|
session.refresh(db_comment)
|
||||||
|
return db_comment
|
|
@ -1,16 +1,37 @@
|
||||||
from fastapi import APIRouter, Request
|
from typing import Annotated
|
||||||
|
|
||||||
|
from fastapi import APIRouter, Request, Depends, Header, HTTPException, status
|
||||||
from fastapi.templating import Jinja2Templates
|
from fastapi.templating import Jinja2Templates
|
||||||
|
from sqlmodel import Session
|
||||||
|
|
||||||
|
from blog.models import UserRead, User
|
||||||
|
from blog.posts import router as posts_router
|
||||||
|
from database import get_session
|
||||||
|
from enums import HeaderAccept
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
router.include_router(router=posts_router)
|
||||||
|
|
||||||
templates = Jinja2Templates(directory="templates")
|
templates = Jinja2Templates(directory="templates")
|
||||||
|
|
||||||
|
|
||||||
@router.get('/')
|
@router.get("/users/{user_id}", response_model=UserRead, tags=['Users'])
|
||||||
def index(request: Request):
|
def get_user(
|
||||||
|
user_id: int,
|
||||||
|
request: Request,
|
||||||
|
session: Session = Depends(get_session),
|
||||||
|
accept: Annotated[str, Header()] = HeaderAccept.json):
|
||||||
|
user = session.get(User, user_id)
|
||||||
|
if not user:
|
||||||
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Post not found")
|
||||||
|
if HeaderAccept.html in accept:
|
||||||
return templates.TemplateResponse(
|
return templates.TemplateResponse(
|
||||||
"index.html",
|
"blog/user.html",
|
||||||
{
|
{
|
||||||
"request": request
|
"request": request,
|
||||||
|
"user": user.dict(),
|
||||||
|
"posts": list(map(lambda x: x.dict(), user.posts))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
return user
|
||||||
|
|
|
@ -5,6 +5,6 @@ from products.router import router as products_router
|
||||||
from blog.router import router as blog_router
|
from blog.router import router as blog_router
|
||||||
|
|
||||||
main_router = APIRouter()
|
main_router = APIRouter()
|
||||||
main_router.include_router(router=users_router)
|
# main_router.include_router(router=users_router)
|
||||||
main_router.include_router(router=products_router)
|
# main_router.include_router(router=products_router)
|
||||||
main_router.include_router(router=blog_router)
|
main_router.include_router(router=blog_router)
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %} {{ post.title }} {% endblock %}
|
||||||
|
|
||||||
|
{% block head %}
|
||||||
|
<style>
|
||||||
|
*, ::after, ::before {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
body{
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
}
|
||||||
|
.container{
|
||||||
|
max-width: 980px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<script defer>
|
||||||
|
function removeEmpty(obj) {
|
||||||
|
return Object.fromEntries(Object.entries(obj).filter(([_, v]) => v != null & v != ''));
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendComment() {
|
||||||
|
const form = document.getElementById("commentForm");
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
var formData = new FormData(form);
|
||||||
|
|
||||||
|
xhr.open('POST', '/posts/{{ post.id }}/comments')
|
||||||
|
xhr.setRequestHeader("Content-Type", "application/json");
|
||||||
|
|
||||||
|
var payload = JSON.parse(JSON.stringify(removeEmpty(Object.fromEntries(formData))))
|
||||||
|
payload['author_id'] = 1
|
||||||
|
|
||||||
|
xhr.send(JSON.stringify(payload));
|
||||||
|
|
||||||
|
xhr.onreadystatechange = function () {
|
||||||
|
if (xhr.readyState == XMLHttpRequest.DONE) {
|
||||||
|
location.replace("/posts/{{ post.id }}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<h1>{{post.title}}</h1>
|
||||||
|
<h4>Author: <a href="/users/{{ post.author_id }}">{{ author }}</a></h4>
|
||||||
|
<div>{{ post.text }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<h2>Comments</h2>
|
||||||
|
<div class="container">
|
||||||
|
<form id="commentForm">
|
||||||
|
<div class="container">
|
||||||
|
<label>
|
||||||
|
<textarea form="commentForm" name="text" placeholder="Comment"></textarea>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<input type = "button" value = "Send" onclick="sendComment();"/>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% for comment in comments | reverse %}
|
||||||
|
<div class="container">
|
||||||
|
<h4>Author: <a href="/users/{{ comment.author_id }}">{{ comment.author }}</a></h4>
|
||||||
|
<div>{{ comment.text }}</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,48 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %} Posts {% endblock %}
|
||||||
|
|
||||||
|
{% block head %}
|
||||||
|
<style>
|
||||||
|
*, ::after, ::before {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
body{
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
|
||||||
|
}
|
||||||
|
img {
|
||||||
|
object-fit: cover;
|
||||||
|
width: 120px;
|
||||||
|
height: 120px;
|
||||||
|
}
|
||||||
|
table{
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
.table {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
color: #212529;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table thead th {
|
||||||
|
vertical-align: bottom;
|
||||||
|
border-bottom: 2px solid #dee2e6;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
.table td, .table th {
|
||||||
|
padding: 0.75rem;
|
||||||
|
vertical-align: top;
|
||||||
|
border-top: 1px solid #dee2e6;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{% for post in posts %}
|
||||||
|
<div>
|
||||||
|
<a href="/posts/{{ post.id }}"><h1>{{ post.title }}</h1></a>
|
||||||
|
<div>{{ post.text }}</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,44 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %} {{user.username}}{% endblock %}
|
||||||
|
|
||||||
|
{% block head %}
|
||||||
|
<style>
|
||||||
|
*, ::after, ::before {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
body{
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
}
|
||||||
|
.container{
|
||||||
|
max-width: 980px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container">
|
||||||
|
<h1>{{user.username}}</h1>
|
||||||
|
{% if user.full_name %}
|
||||||
|
<h5>Full Name: {{user.full_name}}</h5>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if user.email %}
|
||||||
|
<h5>Email: {{user.email}}</h5>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<h2>Posts</h2>
|
||||||
|
{% for post in posts %}
|
||||||
|
<div class="container">
|
||||||
|
<a href="/posts/{{ post.id }}">
|
||||||
|
<h5>{{post.title}}</h5>
|
||||||
|
</a>
|
||||||
|
<div>{{ post.text }}</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
Loading…
Reference in New Issue