add post comments logic
This commit is contained in:
parent
270345c59c
commit
35d7cffa9a
|
@ -1,8 +1,8 @@
|
|||
#!/bin/bash
|
||||
|
||||
# alembic revision --autogenerate -m "post"
|
||||
#alembic revision --autogenerate -m "add_title_to_post_table"
|
||||
alembic upgrade head
|
||||
|
||||
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):
|
||||
title: str
|
||||
text: str
|
||||
|
||||
|
||||
|
@ -45,6 +46,19 @@ class Post(PostBase, table=True):
|
|||
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):
|
||||
name: str
|
||||
|
||||
|
@ -66,3 +80,13 @@ class Comment(CommentBase, table=True):
|
|||
|
||||
author: User | 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 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.include_router(router=posts_router)
|
||||
|
||||
templates = Jinja2Templates(directory="templates")
|
||||
|
||||
|
||||
@router.get('/')
|
||||
def index(request: Request):
|
||||
return templates.TemplateResponse(
|
||||
"index.html",
|
||||
{
|
||||
"request": request
|
||||
}
|
||||
)
|
||||
@router.get("/users/{user_id}", response_model=UserRead, tags=['Users'])
|
||||
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(
|
||||
"blog/user.html",
|
||||
{
|
||||
"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
|
||||
|
||||
main_router = APIRouter()
|
||||
main_router.include_router(router=users_router)
|
||||
main_router.include_router(router=products_router)
|
||||
# main_router.include_router(router=users_router)
|
||||
# main_router.include_router(router=products_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