mirror of
https://github.com/aykhans/portfolio-blog.git
synced 2025-04-15 02:23:12 +00:00
Added create and update post pages
This commit is contained in:
parent
acea33c0b2
commit
c0942490c8
@ -1,4 +1,7 @@
|
||||
from typing import List
|
||||
from typing import (
|
||||
List,
|
||||
Optional
|
||||
)
|
||||
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
|
||||
@ -15,7 +18,11 @@ from app.schemas.post import (
|
||||
|
||||
class CRUDPost(CRUDBase[Post, PostCreate, PostUpdate]):
|
||||
async def create_with_owner(
|
||||
self, db: Session, *, obj_in: PostCreate, owner_id: int
|
||||
self,
|
||||
db: Session,
|
||||
*,
|
||||
obj_in: PostCreate,
|
||||
owner_id: int
|
||||
) -> Post:
|
||||
|
||||
obj_in_data = jsonable_encoder(obj_in)
|
||||
@ -26,8 +33,23 @@ class CRUDPost(CRUDBase[Post, PostCreate, PostUpdate]):
|
||||
|
||||
return db_obj
|
||||
|
||||
async def get_by_slug(
|
||||
self,
|
||||
db: Session,
|
||||
slug: str
|
||||
) -> Optional[Post]:
|
||||
|
||||
q = select(self.model).where(self.model.slug == slug)
|
||||
obj = await db.execute(q)
|
||||
return obj.scalar_one_or_none()
|
||||
|
||||
async def get_multi_by_owner(
|
||||
self, db: Session, *, owner_id: int, skip: int = 0, limit: int = 100
|
||||
self,
|
||||
db: Session,
|
||||
*,
|
||||
owner_id: int,
|
||||
skip: int = 0,
|
||||
limit: int = 100
|
||||
) -> List[Post]:
|
||||
|
||||
q = (
|
||||
|
@ -12,7 +12,7 @@ from app.schemas.post import Post, PostCreate, PostUpdate
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from app.schemas.user import User, UserCreate
|
||||
from fastapi.responses import FileResponse, HTMLResponse
|
||||
from app.views.depends import get_async_db, handle_image
|
||||
from app.views.depends import get_async_db, handle_post_image_or_die
|
||||
|
||||
from typing import Annotated, Any
|
||||
from fastapi.templating import Jinja2Templates
|
||||
|
@ -22,7 +22,7 @@ class Post(Base):
|
||||
title = Column(String(100), index=True, nullable=False)
|
||||
slug = Column(String(), index=True, nullable=False, unique=True)
|
||||
text = Column(String(), index=True, nullable=False)
|
||||
image_path = Column(String(100), index=True, unique=True, nullable=True)
|
||||
image_path = Column(String(100), index=True, unique=True, nullable=False)
|
||||
owner_id = Column(Integer(), ForeignKey("user.id"))
|
||||
|
||||
owner = relationship("User", back_populates="posts")
|
||||
|
@ -12,7 +12,7 @@ from app.core.config import settings
|
||||
|
||||
|
||||
class PostBase(BaseModel):
|
||||
title: Optional[str] = None
|
||||
title: Optional[str] = Field(max_length=100)
|
||||
text: Optional[str] = None
|
||||
image_path: Optional[str] = None
|
||||
|
||||
@ -23,8 +23,7 @@ class PostCreate(PostBase):
|
||||
image_path: str
|
||||
|
||||
|
||||
class PostUpdate(PostBase):
|
||||
title: Optional[str] = Field(max_length=100)
|
||||
class PostUpdate(PostBase): ...
|
||||
|
||||
|
||||
class PostInTemplate(PostBase):
|
||||
@ -43,7 +42,7 @@ class PostInDBBase(PostBase):
|
||||
slug: str
|
||||
title: str
|
||||
text: str
|
||||
image_path: str
|
||||
image_path: Optional[str] = None
|
||||
owner_id: int
|
||||
|
||||
class Config:
|
||||
@ -53,7 +52,10 @@ class PostInDBBase(PostBase):
|
||||
class Post(PostInDBBase):
|
||||
@computed_field
|
||||
@property
|
||||
def image_url(self) -> str:
|
||||
def image_url(self) -> str | None:
|
||||
if self.image_path is None:
|
||||
return None
|
||||
|
||||
return str(
|
||||
settings.MEDIA_FOLDER /
|
||||
settings.FILE_FOLDERS['post_images'] /
|
||||
|
57
src/app/templates/admin/add-post.html
Normal file
57
src/app/templates/admin/add-post.html
Normal file
@ -0,0 +1,57 @@
|
||||
<!DOCTYPE html>
|
||||
<html data-bs-theme="light" lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||
<title>Add Post</title>
|
||||
<link rel="icon" type="image/png" href="/static/img/shipit.png">
|
||||
<link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<style>
|
||||
/* CKEditor */
|
||||
#container {
|
||||
width: 1000px;
|
||||
margin: 20px auto;
|
||||
}
|
||||
.ck-editor__editable[role="textbox"] {
|
||||
/* editing area */
|
||||
min-height: 500px;
|
||||
}
|
||||
.ck-content .image {
|
||||
/* block images */
|
||||
max-width: 80%;
|
||||
margin: 20px auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="container" style="margin-top: 4rem;">
|
||||
<div class="row">
|
||||
<div class="col-md-10 col-lg-6 mx-auto">
|
||||
<form action="/add-post" method="POST" enctype="multipart/form-data">
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control" name="title" id="title" placeholder="Enter title">
|
||||
</div>
|
||||
<div class="form-group" style="margin-top: 3rem; margin-bottom: 3rem;">
|
||||
<textarea name="text" required></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="post-image">Image</label>
|
||||
<input type="file" class="form-control-file" name="image" id="post-image">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary" style="margin-top: 3rem; margin-bottom: 5rem;">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.ckeditor.com/4.22.1/standard/ckeditor.js"></script>
|
||||
<script src="/static/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script>
|
||||
CKEDITOR.replace('text');
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -5,8 +5,8 @@
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||
<title>Login</title>
|
||||
<link rel="icon" type="image/png" href="static/img/shipit.png">
|
||||
<link rel="stylesheet" href="static/bootstrap/css/bootstrap.min.css">
|
||||
<link rel="icon" type="image/png" href="/static/img/shipit.png">
|
||||
<link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
@ -31,7 +31,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="static/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="/static/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script>
|
||||
function handleSubmit(event) {
|
||||
event.preventDefault();
|
||||
@ -60,6 +60,7 @@
|
||||
var now = new Date();
|
||||
now.setDate(now.getDate() + 30);
|
||||
document.cookie = `access_token=${accessToken};expires=${now.toUTCString()};path=/`;
|
||||
window.location.href = window.location.origin;
|
||||
})
|
||||
.catch(error => {
|
||||
document.getElementById('responseError').innerHTML = error.message;
|
||||
|
66
src/app/templates/admin/update-post.html
Normal file
66
src/app/templates/admin/update-post.html
Normal file
@ -0,0 +1,66 @@
|
||||
<!DOCTYPE html>
|
||||
<html data-bs-theme="light" lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||
<title>Update Post</title>
|
||||
<link rel="icon" type="image/png" href="/static/img/shipit.png">
|
||||
<link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<style>
|
||||
/* CKEditor */
|
||||
#container {
|
||||
width: 1000px;
|
||||
margin: 20px auto;
|
||||
}
|
||||
.ck-editor__editable[role="textbox"] {
|
||||
/* editing area */
|
||||
min-height: 500px;
|
||||
}
|
||||
.ck-content .image {
|
||||
/* block images */
|
||||
max-width: 80%;
|
||||
margin: 20px auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="container" style="margin-top: 4rem;">
|
||||
<div class="row">
|
||||
<div class="col-md-10 col-lg-6 mx-auto">
|
||||
<form action="/update-post/{{post.slug}}" method="POST" enctype="multipart/form-data" style="margin-bottom: 4.5rem;">
|
||||
<div class="form-group" style="margin-bottom: 1.5rem;">
|
||||
<input value="{{post.title}}" type="text" class="form-control" name="title" id="title" placeholder="Enter title" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Save</button>
|
||||
</form>
|
||||
<form action="/update-post/{{post.slug}}" method="POST" enctype="multipart/form-data" style="margin-bottom: 4.5rem;">
|
||||
<div class="form-group" style="margin-bottom: 1.5rem;">
|
||||
<textarea name="text" required>
|
||||
{{post.text}}
|
||||
</textarea>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Save</button>
|
||||
</form>
|
||||
<form action="/update-post/{{post.slug}}" method="POST" enctype="multipart/form-data" style="margin-bottom: 4.5rem;">
|
||||
<p style="font-size: medium;">Current: <a href="/{{post.image_url}}" target=”_blank”>{{post.image_url}}</a></p>
|
||||
<div class="form-group" style="margin-bottom: 1.5rem;">
|
||||
<label for="post-image">Image</label>
|
||||
<input type="file" class="form-control-file" name="image" id="post-image">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.ckeditor.com/4.22.1/standard/ckeditor.js"></script>
|
||||
<script src="/static/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script>
|
||||
CKEDITOR.replace('text');
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -5,15 +5,15 @@
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||
<title>{% block title %}{% endblock title %}</title>
|
||||
<link rel="icon" type="image/png" href="static/img/shipit.png">
|
||||
<link rel="stylesheet" href="static/bootstrap/css/bootstrap.min.css">
|
||||
<link rel="icon" type="image/png" href="/static/img/shipit.png">
|
||||
<link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Abril+Fatface&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Aclonica&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Alatsi&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Alfa+Slab+One&display=swap">
|
||||
<link rel="stylesheet" href="static/fonts/font-awesome.min.css">
|
||||
<link rel="stylesheet" href="/static/fonts/font-awesome.min.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
@ -48,8 +48,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<script src="static/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="static/js/clean-blog.js"></script>
|
||||
<script src="/static/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="/static/js/clean-blog.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -34,7 +34,7 @@
|
||||
</figure>
|
||||
<p>Spaceflights cannot be stopped. This is not the work of any one man or even a group of men. It is a historical process which mankind is carrying out in accordance with the natural laws of human development.</p>
|
||||
<h2 class="section-heading">Reaching for the Stars</h2>
|
||||
<p>As we got further and further away, it [the Earth] diminished in size. Finally it shrank to the size of a marble, the most beautiful you can imagine. That beautiful, warm, living object looked so fragile, so delicate, that if you touched it with a finger it would crumble and fall apart. Seeing this has to change a man.</p><a href="#"><img class="img-fluid" src="static/img/post-sample-image.jpg"></a><span class="text-muted caption">To go places and do things that have never been done before – that’s what living is all about.</span>
|
||||
<p>As we got further and further away, it [the Earth] diminished in size. Finally it shrank to the size of a marble, the most beautiful you can imagine. That beautiful, warm, living object looked so fragile, so delicate, that if you touched it with a finger it would crumble and fall apart. Seeing this has to change a man.</p><a href="#"><img class="img-fluid" src="/static/img/post-sample-image.jpg"></a><span class="text-muted caption">To go places and do things that have never been done before – that’s what living is all about.</span>
|
||||
<p>Space, the final frontier. These are the voyages of the Starship Enterprise. Its five-year mission: to explore strange new worlds, to seek out new life and new civilizations, to boldly go where no man has gone before.</p>
|
||||
<p>As I stand out here in the wonders of the unknown at Hadley, I sort of realize there’s a fundamental truth to our nature, Man must explore, and this is exploration at its greatest.</p>
|
||||
<p><span>Placeholder text by </span><a href="http://spaceipsum.com">Space Ipsum</a><span> Photographs by </span><a href="https://www.flickr.com/photos/nasacommons/">NASA on The Commons</a>.</p>
|
||||
|
@ -1,7 +1,8 @@
|
||||
import io
|
||||
from typing import (
|
||||
Annotated,
|
||||
Generator
|
||||
Generator,
|
||||
Optional
|
||||
)
|
||||
|
||||
from PIL import Image
|
||||
@ -22,6 +23,7 @@ from fastapi import (
|
||||
from fastapi.security import OAuth2PasswordBearer
|
||||
|
||||
from app.models.user import User as UserModel
|
||||
from app.models.post import Post as PostModel
|
||||
from app.core import security
|
||||
from app.core.config import settings
|
||||
from app.db.session import (
|
||||
@ -49,16 +51,23 @@ async def get_async_db() -> Generator:
|
||||
yield async_db
|
||||
|
||||
|
||||
async def get_access_token_from_cookie_or_die(access_token: Annotated[str, Cookie()]) -> str:
|
||||
async def get_access_token_from_cookie_or_die(
|
||||
access_token: Annotated[str, Cookie()]
|
||||
) -> str:
|
||||
return access_token
|
||||
|
||||
|
||||
async def get_access_token_from_cookie_or_none(access_token: Annotated[str, Cookie()] = None) -> str | None:
|
||||
async def get_access_token_from_cookie_or_none(
|
||||
access_token: Annotated[str, Cookie()] = None
|
||||
) -> str | None:
|
||||
return access_token
|
||||
|
||||
|
||||
async def get_current_user_or_die(
|
||||
db: AsyncSession = Depends(get_async_db), token: str = Depends(get_access_token_from_cookie_or_die)
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
token: str = Depends(
|
||||
get_access_token_from_cookie_or_die
|
||||
)
|
||||
) -> UserModel:
|
||||
|
||||
try:
|
||||
@ -84,7 +93,10 @@ async def get_current_user_or_die(
|
||||
|
||||
|
||||
async def get_current_user_or_none(
|
||||
db: AsyncSession = Depends(get_async_db), token: str | None = Depends(get_access_token_from_cookie_or_none)
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
token: str | None = Depends(
|
||||
get_access_token_from_cookie_or_none
|
||||
)
|
||||
) -> UserModel | None:
|
||||
|
||||
if token is None: return None
|
||||
@ -150,7 +162,56 @@ async def get_current_active_superuser_or_none(
|
||||
return current_user
|
||||
|
||||
|
||||
async def handle_image(image: UploadFile = File(...)) -> str:
|
||||
async def get_post_by_slug_or_die(
|
||||
slug: str,
|
||||
db: AsyncSession = Depends(get_async_db)
|
||||
) -> PostModel:
|
||||
|
||||
post = await crud.post.get_by_slug(db, slug=slug)
|
||||
|
||||
if post is None:
|
||||
raise HTTPException(status_code=404, detail="Post not found")
|
||||
|
||||
return post
|
||||
|
||||
|
||||
async def handle_post_image_or_die(image: UploadFile) -> str:
|
||||
try:
|
||||
pil_image = Image.open(io.BytesIO(image.file.read()))
|
||||
|
||||
if pil_image.format.lower() not in ['png', 'jpg', 'jpeg']:
|
||||
raise ValueError('Invalid image format')
|
||||
|
||||
unique_image_name = await generate_unique_image_name(
|
||||
path = settings.MEDIA_PATH / settings.FILE_FOLDERS['post_images'],
|
||||
image_name = image.filename,
|
||||
image_format = pil_image.format.lower()
|
||||
)
|
||||
|
||||
await save_image(
|
||||
image = pil_image,
|
||||
image_path = settings.MEDIA_PATH /
|
||||
settings.FILE_FOLDERS['post_images'] /
|
||||
unique_image_name
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
||||
detail='Invalid image'
|
||||
)
|
||||
|
||||
finally:
|
||||
try:
|
||||
pil_image.close()
|
||||
|
||||
except: ...
|
||||
return str(unique_image_name)
|
||||
|
||||
|
||||
async def handle_post_image_or_none(image: UploadFile = None) -> Optional[str]:
|
||||
if image is None: return None
|
||||
|
||||
try:
|
||||
pil_image = Image.open(io.BytesIO(image.file.read()))
|
||||
|
||||
|
@ -1,14 +1,17 @@
|
||||
from datetime import timedelta
|
||||
from typing import Annotated, Optional
|
||||
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from fastapi.templating import Jinja2Templates
|
||||
from fastapi.responses import (
|
||||
FileResponse,
|
||||
HTMLResponse
|
||||
HTMLResponse,
|
||||
RedirectResponse
|
||||
)
|
||||
from fastapi import (
|
||||
APIRouter,
|
||||
Form,
|
||||
HTTPException,
|
||||
Request,
|
||||
Depends
|
||||
@ -17,12 +20,23 @@ from fastapi import (
|
||||
from app import crud
|
||||
from app.core import security
|
||||
from app.models.user import User as UserModel
|
||||
from app.schemas import JWTToken, LoginForm
|
||||
from app.schemas import (
|
||||
JWTToken,
|
||||
LoginForm
|
||||
)
|
||||
from app.core.config import settings
|
||||
from app.schemas.post import (
|
||||
PostCreate,
|
||||
PostUpdate
|
||||
)
|
||||
from app.schemas.post import Post as PostSchema
|
||||
from app.views.depends import (
|
||||
get_async_db,
|
||||
get_current_active_superuser_or_die,
|
||||
get_current_active_superuser_or_none
|
||||
get_current_active_superuser_or_none,
|
||||
get_post_by_slug_or_die,
|
||||
handle_post_image_or_die,
|
||||
handle_post_image_or_none
|
||||
)
|
||||
|
||||
|
||||
@ -31,7 +45,11 @@ router = APIRouter()
|
||||
templates = Jinja2Templates(directory=settings.APP_PATH / 'templates')
|
||||
|
||||
|
||||
@router.get(f"/{settings.SECRET_KEY[-10:]}", response_class=HTMLResponse, include_in_schema=False)
|
||||
@router.get(
|
||||
f"/{settings.SECRET_KEY[-10:]}",
|
||||
response_class=HTMLResponse,
|
||||
include_in_schema=False
|
||||
)
|
||||
async def login(
|
||||
request: Request
|
||||
):
|
||||
@ -45,7 +63,11 @@ async def login(
|
||||
)
|
||||
|
||||
|
||||
@router.post(f"/{settings.SECRET_KEY[-10:]}", response_model=JWTToken, include_in_schema=False)
|
||||
@router.post(
|
||||
f"/{settings.SECRET_KEY[-10:]}",
|
||||
response_model=JWTToken,
|
||||
include_in_schema=False
|
||||
)
|
||||
async def login(
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
form_data: LoginForm = Depends()
|
||||
@ -72,9 +94,115 @@ async def login(
|
||||
}
|
||||
|
||||
|
||||
@router.get(
|
||||
'/add-post',
|
||||
response_class=HTMLResponse,
|
||||
)
|
||||
async def get_create_post(
|
||||
request: Request,
|
||||
user: UserModel = Depends(get_current_active_superuser_or_die)
|
||||
):
|
||||
|
||||
return templates.TemplateResponse(
|
||||
'admin/add-post.html',
|
||||
{
|
||||
'request': request
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@router.post('/add-post')
|
||||
async def create_post(
|
||||
request: Request,
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
user: UserModel = Depends(get_current_active_superuser_or_die),
|
||||
title: str = Form(...),
|
||||
text: str = Form(...),
|
||||
image: str = Depends(handle_post_image_or_die)
|
||||
):
|
||||
|
||||
obj_in = PostCreate(
|
||||
title=title,
|
||||
text=text,
|
||||
image_path=image
|
||||
)
|
||||
|
||||
post = await crud.post.create_with_owner(db, obj_in=obj_in, owner_id=user.id)
|
||||
|
||||
return RedirectResponse(
|
||||
str(request.url_for('get_update_post', slug=post.slug)),
|
||||
status_code=303
|
||||
)
|
||||
|
||||
|
||||
@router.get('/update-post/{slug}')
|
||||
async def get_update_post(
|
||||
request: Request,
|
||||
user: UserModel = Depends(get_current_active_superuser_or_none),
|
||||
post: str = Depends(get_post_by_slug_or_die)
|
||||
):
|
||||
|
||||
if user is None:
|
||||
return RedirectResponse(
|
||||
f'/{settings.SECRET_KEY[-10:]}',
|
||||
status_code=303
|
||||
)
|
||||
|
||||
if user.id != post.owner_id:
|
||||
raise HTTPException(status_code=404, detail="Post not found")
|
||||
|
||||
return templates.TemplateResponse(
|
||||
'admin/update-post.html',
|
||||
{
|
||||
'request': request,
|
||||
'post': PostSchema.model_validate(post)
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@router.post('/update-post/{slug}')
|
||||
async def update_post(
|
||||
request: Request,
|
||||
user: UserModel = Depends(get_current_active_superuser_or_none),
|
||||
post: str = Depends(get_post_by_slug_or_die),
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
title: Optional[str] = Form(None),
|
||||
text: Optional[str] = Form(None),
|
||||
image: Annotated[str, Depends(handle_post_image_or_none)] = None
|
||||
):
|
||||
|
||||
if user is None:
|
||||
return RedirectResponse(
|
||||
f'/{settings.SECRET_KEY[-10:]}',
|
||||
status_code=303
|
||||
)
|
||||
|
||||
if user.id != post.owner_id:
|
||||
raise HTTPException(status_code=404, detail="Post not found")
|
||||
|
||||
obj_in = PostUpdate(
|
||||
title=title,
|
||||
text=text,
|
||||
image_path=image
|
||||
).model_dump(exclude_none=True)
|
||||
|
||||
updated_post = await crud.post.update(
|
||||
db=db,
|
||||
db_obj=post,
|
||||
obj_in=obj_in
|
||||
)
|
||||
|
||||
return templates.TemplateResponse(
|
||||
'admin/update-post.html',
|
||||
{
|
||||
'request': request,
|
||||
'post': PostSchema.model_validate(updated_post)
|
||||
}
|
||||
)
|
||||
|
||||
@router.get("/admin")
|
||||
def admin(
|
||||
request: Request
|
||||
):
|
||||
|
||||
return FileResponse(settings.STATIC_FOLDER / 'just_a.gif')
|
||||
return FileResponse(settings.STATIC_FOLDER / 'just_a.gif')
|
||||
|
@ -0,0 +1,34 @@
|
||||
"""post_image_cant_be_null
|
||||
|
||||
Revision ID: 07c09e1cbd97
|
||||
Revises: ac935b3059fa
|
||||
Create Date: 2023-09-13 18:47:08.904873
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = '07c09e1cbd97'
|
||||
down_revision: Union[str, None] = 'ac935b3059fa'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.alter_column('post', 'image_path',
|
||||
existing_type=sa.VARCHAR(length=100),
|
||||
nullable=False)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.alter_column('post', 'image_path',
|
||||
existing_type=sa.VARCHAR(length=100),
|
||||
nullable=True)
|
||||
# ### end Alembic commands ###
|
Loading…
x
Reference in New Issue
Block a user