Added create_user command & edited imports

This commit is contained in:
Aykhan 2023-09-12 23:40:42 +04:00
parent 77e4722576
commit 006f2af15b
19 changed files with 245 additions and 69 deletions

3
.gitignore vendored
View File

@ -1,4 +1,5 @@
.venv .venv
__pycache__ __pycache__
media media
*.env *.env
test.py

View File

@ -1,4 +1,3 @@
from typing import Optional
from pathlib import Path from pathlib import Path
from pydantic_settings import BaseSettings from pydantic_settings import BaseSettings

View File

@ -1,5 +1,11 @@
from datetime import datetime, timedelta from datetime import (
from typing import Any, Union datetime,
timedelta
)
from typing import (
Any,
Union
)
from jose import jwt from jose import jwt
from passlib.context import CryptContext from passlib.context import CryptContext

View File

@ -1,7 +1,18 @@
from typing import Any, Dict, Generic, List, Optional, Type, TypeVar, Union from typing import (
Any,
Dict,
Generic,
List,
Optional,
Type,
TypeVar,
Union
)
from pydantic import BaseModel
from fastapi.encoders import jsonable_encoder from fastapi.encoders import jsonable_encoder
from pydantic import BaseModel
from sqlalchemy.future import select from sqlalchemy.future import select
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
@ -37,6 +48,14 @@ class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]):
obj = await db.execute(q) obj = await db.execute(q)
return obj.scalars() return obj.scalars()
def sync_get_multi(
self, db: Session, *, skip: int = 0, limit: int = 100
) -> List[ModelType]:
q = select(self.model).offset(skip).limit(limit).order_by(self.model.id.desc())
obj = db.execute(q)
return obj.scalars()
async def create(self, db: Session, *, obj_in: CreateSchemaType) -> ModelType: async def create(self, db: Session, *, obj_in: CreateSchemaType) -> ModelType:
obj_in_data = jsonable_encoder(obj_in) obj_in_data = jsonable_encoder(obj_in)
db_obj = self.model(**obj_in_data) # type: ignore db_obj = self.model(**obj_in_data) # type: ignore

View File

@ -15,6 +15,11 @@ class CRUDUser(CRUDBase[User, UserCreate, UserUpdate]):
obj = await db.execute(q) obj = await db.execute(q)
return obj.scalar_one_or_none() return obj.scalar_one_or_none()
def sync_get_by_email(self, db: Session, *, email: str) -> Optional[User]:
q = select(self.model).where(self.model.email == email)
obj = db.execute(q)
return obj.scalar_one_or_none()
async def create(self, db: Session, *, obj_in: UserCreate) -> User: async def create(self, db: Session, *, obj_in: UserCreate) -> User:
db_obj = User( db_obj = User(
email=obj_in.email, email=obj_in.email,
@ -29,6 +34,20 @@ class CRUDUser(CRUDBase[User, UserCreate, UserUpdate]):
return db_obj return db_obj
def sync_create(self, db: Session, *, obj_in: UserCreate) -> User:
db_obj = User(
email=obj_in.email,
hashed_password=get_password_hash(obj_in.password),
username=obj_in.username,
is_superuser=obj_in.is_superuser,
)
db.add(db_obj)
db.commit()
db.refresh(db_obj)
return db_obj
async def update( async def update(
self, db: Session, *, db_obj: User, obj_in: Union[UserUpdate, Dict[str, Any]] self, db: Session, *, db_obj: User, obj_in: Union[UserUpdate, Dict[str, Any]]
) -> User: ) -> User:

View File

@ -1,7 +1,10 @@
from typing import Any from typing import Any
from sqlalchemy.ext.declarative import as_declarative, declared_attr from sqlalchemy.ext.declarative import as_declarative, declared_attr
from sqlalchemy import DateTime, Column from sqlalchemy import (
DateTime,
Column
)
from sqlalchemy.sql import func from sqlalchemy.sql import func

View File

@ -11,7 +11,7 @@ from app.schemas.login import LoginForm
from app.schemas.post import Post, PostCreate, PostUpdate from app.schemas.post import Post, PostCreate, PostUpdate
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from app.schemas.user import User, UserCreate from app.schemas.user import User, UserCreate
from fastapi.responses import HTMLResponse 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_image
from typing import Annotated, Any from typing import Annotated, Any
@ -46,6 +46,11 @@ async def validation_exception_handler(request, exc):
return await request_validation_exception_handler(request, exc) return await request_validation_exception_handler(request, exc)
@app.exception_handler(404)
async def custom_404_handler(_, __):
return FileResponse(settings.STATIC_FOLDER / '404.jpg')
# @app.post("/login", response_model=JWTToken) # @app.post("/login", response_model=JWTToken)
# async def login( # async def login(
# db: AsyncSession = Depends(get_async_db), # db: AsyncSession = Depends(get_async_db),

View File

@ -1,3 +1,4 @@
from contextlib import contextmanager
from slugify import slugify from slugify import slugify
from sqlalchemy.orm.base import NO_VALUE from sqlalchemy.orm.base import NO_VALUE
@ -34,16 +35,15 @@ from app.core.config import settings
def generate_slug(target, value, oldvalue, initiator): def generate_slug(target, value, oldvalue, initiator):
slug = slugify(value) slug = slugify(value)
db = next(get_db()) with contextmanager(get_db)() as db:
number = 1
temp_slug = slug
number = 1 while db.query(Post).filter(Post.slug == temp_slug).first() is not None:
temp_slug = slug temp_slug = f'{slug}-{number}'
number += 1
while db.query(Post).filter(Post.slug == temp_slug).first() is not None: target.slug = temp_slug
temp_slug = f'{slug}-{number}'
number += 1
target.slug = temp_slug
listen(Post.title, 'set', generate_slug) listen(Post.title, 'set', generate_slug)

View File

@ -10,7 +10,8 @@ from .user import (
User, User,
UserCreate, UserCreate,
UserInDBBase, UserInDBBase,
UserUpdate UserUpdate,
UserBase
) )
from .login import ( from .login import (
JWTToken, JWTToken,

View File

@ -1,5 +1,6 @@
from dataclasses import dataclass from dataclasses import dataclass
from typing import Optional from typing import Optional
from fastapi import Form from fastapi import Form
from pydantic import ( from pydantic import (

View File

@ -1,6 +1,9 @@
from typing import Optional from typing import Optional
from pydantic import BaseModel, EmailStr from pydantic import (
BaseModel,
EmailStr
)
class UserBase(BaseModel): class UserBase(BaseModel):

View File

@ -13,28 +13,53 @@
<div class="container" style="margin-top: 10rem;"> <div class="container" style="margin-top: 10rem;">
<div class="row"> <div class="row">
<div class="col-md-10 col-lg-4 mx-auto"> <div class="col-md-10 col-lg-4 mx-auto">
<form> <form id="loginForm">
<div class="form-outline mb-4"> <div class="form-outline mb-4">
<input type="email" id="form2Example1" class="form-control" /> <input type="email" id="form2Example1" name="email" class="form-control" />
<label class="form-label" for="form2Example1">Email address</label> <label class="form-label" for="form2Example1">Email address</label>
</div> </div>
<div class="form-outline mb-4"> <div class="form-outline mb-4">
<input type="password" id="form2Example2" class="form-control" /> <input type="password" id="form2Example2" name="password" class="form-control" />
<label class="form-label" for="form2Example2">Password</label> <label class="form-label" for="form2Example2">Password</label>
</div> </div>
<button type="button" class="btn btn-primary btn-block mb-4">Sign in</button> <button type="sumbit" class="btn btn-primary btn-block mb-4">Sign in</button>
</form> </form>
</div> </div>
</div> </div>
</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();
const form = document.getElementById('loginForm');
const formData = new FormData(form);
console.log(formData);
fetch('{{login_url}}', {
method: 'POST',
body: formData,
})
.then(response => response.json())
.then(data => {
const accessToken = data.access_token;
localStorage.setItem('accessToken', accessToken)
})
.catch(error => {
console.error('Login error:', error);
});
}
const loginForm = document.getElementById('loginForm');
loginForm.addEventListener('submit', handleSubmit);
</script>
</body> </body>
</html> </html>
<!-- <!DOCTYPE html> <!-- <!DOCTYPE html>
<html> <html>
<head> <head>
@ -46,32 +71,5 @@
<input type="password" name="password" placeholder="Password"> <input type="password" name="password" placeholder="Password">
<button type="submit">Login</button> <button type="submit">Login</button>
</form> </form>
<script>
// Function to handle the form submission
function handleSubmit(event) {
event.preventDefault();
const form = document.getElementById('loginForm');
const formData = new FormData(form);
fetch('/login', {
method: 'POST', // Adjust the HTTP method if needed
body: formData,
})
.then(response => response.json())
.then(data => {
const accessToken = data.access_token;
console.log(accessToken);
document.cookie = `access_token=${accessToken}`;
})
.catch(error => {
console.error('Login error:', error);
});
}
// Add a submit event listener to the form
const loginForm = document.getElementById('loginForm');
loginForm.addEventListener('submit', handleSubmit);
</script>
</body> </body>
</html> --> </html> -->

View File

@ -1 +0,0 @@
<h1>Navbar</h1>

View File

@ -0,0 +1,119 @@
from sys import path
path.append('/src')
from contextlib import contextmanager
from typing import Optional
from pydantic import (
EmailStr,
ValidationError,
ConfigDict
)
from app import crud
from app.schemas import UserCreate
from app.views.depends import get_db
class UserCreateCommand(UserCreate):
model_config = ConfigDict(validate_assignment=True)
email: Optional[EmailStr] = None
password: Optional[str] = None
if __name__ == "__main__":
user_in = UserCreateCommand()
while 1:
email = input('*Email: ')
if not email:
print('Email is required\n')
continue
try:
user_in.email = email
except ValidationError as e:
print('\n', e, end='\n\n')
continue
with contextmanager(get_db)() as db:
user = crud.user.sync_get_by_email(
db,
email=user_in.email
)
if user:
print('User already exists\n')
continue
break
while 1:
username = input('Username: ')
if username:
try:
user_in.username = username
except ValidationError as e:
print('\n', e, end='\n\n')
continue
break
while 1:
password = input('*Password: ')
if not password:
print('Password is required\n')
continue
try:
user_in.password = password
except ValidationError as e:
print('\n', e, end='\n\n')
continue
break
while 1:
is_active = input('Is active? y/n (y): ') or 'y'
if is_active == 'y':
user_in.is_active = True
elif is_active == 'n':
user_in.is_active = False
else:
print('Invalid input\n')
continue
break
while 1:
is_superuser = input('Is superuser? y/n (n): ') or 'n'
if is_superuser == 'y':
user_in.is_superuser = True
elif is_superuser == 'n':
user_in.is_superuser = False
else:
print('Invalid input\n')
continue
break
with contextmanager(get_db)() as db:
user = crud.user.sync_create(
db,
obj_in=user_in
)
print(f'\nUser created:\n{user_in}\n')

View File

@ -1,8 +1,12 @@
import io import io
from pathlib import Path from typing import (
from typing import Annotated, Generator Annotated,
Generator
)
from PIL import Image from PIL import Image
from jose import jwt from jose import jwt
from pydantic import ValidationError from pydantic import ValidationError
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
@ -20,7 +24,10 @@ from fastapi.security import OAuth2PasswordBearer
from app.models.user import User as UserModel from app.models.user import User as UserModel
from app.core import security from app.core import security
from app.core.config import settings from app.core.config import settings
from app.db.session import SessionLocal, AsyncSessionLocal from app.db.session import (
SessionLocal,
AsyncSessionLocal
)
from app import crud from app import crud
from app import schemas from app import schemas
from app.utils.image_operations import ( from app.utils.image_operations import (
@ -31,22 +38,15 @@ from app.utils.image_operations import (
reusable_oauth2 = OAuth2PasswordBearer(tokenUrl='/login') reusable_oauth2 = OAuth2PasswordBearer(tokenUrl='/login')
def get_db() -> Generator:
try:
db = SessionLocal()
yield db
finally: def get_db() -> Generator:
db.close() with SessionLocal() as db:
yield db
async def get_async_db() -> Generator: async def get_async_db() -> Generator:
try: async with AsyncSessionLocal() as async_db:
async with AsyncSessionLocal() as async_db: yield await async_db
yield async_db
finally:
await async_db.close()
async def get_access_token_from_cookie(access_token: Annotated[str, Cookie()]): async def get_access_token_from_cookie(access_token: Annotated[str, Cookie()]):

View File

@ -19,7 +19,7 @@ from app import crud
from app.core.config import settings from app.core.config import settings
from app.schemas import ListPostInTemplate from app.schemas import ListPostInTemplate
from app.schemas.main import SendEmail from app.schemas.main import SendEmail
from app.utils.email import send_email_notification from app.utils.email_utils import send_email_notification
from app.views.depends import get_async_db from app.views.depends import get_async_db

View File

@ -1,10 +1,12 @@
from typing import Any
from datetime import timedelta from datetime import timedelta
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from fastapi.responses import FileResponse, HTMLResponse from fastapi.responses import (
FileResponse,
HTMLResponse
)
from fastapi import ( from fastapi import (
APIRouter, APIRouter,
HTTPException, HTTPException,
@ -36,7 +38,8 @@ async def login(
return templates.TemplateResponse( return templates.TemplateResponse(
'admin/login.html', 'admin/login.html',
{ {
'request': request 'request': request,
'login_url': f'/{settings.SECRET_KEY[-10:]}'
} }
) )

BIN
src/static/404.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB