add CRUD for WorkType
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
import logging
|
||||
import logging.config
|
||||
from typing import Any
|
||||
|
||||
LOGGING_CONFIG: dict[str, Any] = {
|
||||
"version": 1,
|
||||
"disable_existing_loggers": False,
|
||||
"formatters": {
|
||||
"default": {
|
||||
"()": "uvicorn.logging.DefaultFormatter",
|
||||
"fmt": "%(asctime)s - %(name)s - %(levelprefix)s %(message)s",
|
||||
"use_colors": None,
|
||||
},
|
||||
"access": {
|
||||
"()": "uvicorn.logging.AccessFormatter",
|
||||
"fmt": '%(asctime)s - %(name)s - %(levelprefix)s %(client_addr)s - "%(request_line)s" %(status_code)s', # noqa: E501
|
||||
},
|
||||
"access_file": {
|
||||
"()": "uvicorn.logging.AccessFormatter",
|
||||
"fmt": '%(asctime)s - %(name)s - %(levelprefix)s %(client_addr)s - "%(request_line)s" %(status_code)s', # noqa: E501
|
||||
"use_colors": False,
|
||||
},
|
||||
},
|
||||
"handlers": {
|
||||
"file_handler": {
|
||||
"formatter": "access_file",
|
||||
"class": "logging.handlers.RotatingFileHandler",
|
||||
"filename" : "./app.log",
|
||||
"mode" : "a+",
|
||||
"maxBytes" : 10*1024*1024,
|
||||
"backupCount": 0,
|
||||
},
|
||||
|
||||
"default": {
|
||||
"formatter": "default",
|
||||
"class": "logging.StreamHandler",
|
||||
"stream": "ext://sys.stderr",
|
||||
},
|
||||
"access": {
|
||||
"formatter": "access",
|
||||
"class": "logging.StreamHandler",
|
||||
"stream": "ext://sys.stdout",
|
||||
},
|
||||
},
|
||||
"loggers": {
|
||||
"uvicorn": {"handlers": ["default"], "level": "INFO", "propagate": False},
|
||||
"uvicorn.error": {"level": "INFO"},
|
||||
"uvicorn.access": {"handlers": ["access", "file_handler"], "level": "INFO", "propagate": False},
|
||||
},
|
||||
}
|
||||
|
||||
logging.config.dictConfig(LOGGING_CONFIG)
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -10,7 +10,7 @@ class Base(DeclarativeBase):
|
||||
|
||||
|
||||
class BaseMixin:
|
||||
#id = Column(String(255), primary_key=True, default=uuid.uuid4)
|
||||
#id = Column(String, primary_key=True, default=uuid.uuid4)
|
||||
id: Mapped[str] = mapped_column(primary_key=True, default=uuid.uuid4)
|
||||
# created_date = Column(DateTime)
|
||||
created_date: Mapped[datetime] = mapped_column(default=func.now())
|
||||
|
||||
@@ -6,28 +6,28 @@ from src.db.models.base import Base, BaseMixin
|
||||
|
||||
class Article(Base, BaseMixin):
|
||||
__tablename__ = 'article'
|
||||
title = Column(String(length=255), unique=True)
|
||||
title = Column(String, unique=True)
|
||||
article_authors = relationship("ArticleAuthor")
|
||||
|
||||
|
||||
class Author(Base, BaseMixin):
|
||||
__tablename__ = 'author'
|
||||
first_name = Column(String(255))
|
||||
last_name = Column(String(255))
|
||||
first_name = Column(String)
|
||||
last_name = Column(String)
|
||||
article_authors = relationship("ArticleAuthor")
|
||||
book_authors = relationship("BookAuthor")
|
||||
|
||||
|
||||
class BookshelfPublisher(Base, BaseMixin):
|
||||
__tablename__ = 'bookshelf_publisher'
|
||||
name = Column(String(length=255), unique=True)
|
||||
name = Column(String, unique=True)
|
||||
books = relationship("Book")
|
||||
|
||||
|
||||
class Book(Base, BaseMixin):
|
||||
__tablename__ = 'book'
|
||||
isbn = Column(String(255), unique=True)
|
||||
title = Column(String(255))
|
||||
isbn = Column(String, unique=True)
|
||||
title = Column(String)
|
||||
year = Column(Integer, nullable=False)
|
||||
publisher_id = Column(String, ForeignKey('bookshelf_publisher.id'), nullable=False)
|
||||
publisher = relationship('BookshelfPublisher', back_populates="books")
|
||||
|
||||
@@ -1,14 +1,23 @@
|
||||
from typing import Dict, List
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from typing import Dict, List, Optional
|
||||
from natsort import natsorted
|
||||
from sqlalchemy import Column, ForeignKey, Integer, String, Boolean
|
||||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy import Column, ForeignKey, Integer, String, Boolean, func
|
||||
from sqlalchemy.orm import relationship, Mapped, mapped_column
|
||||
|
||||
from src.db.models.base import Base, BaseMixin
|
||||
|
||||
|
||||
class Publisher(Base, BaseMixin):
|
||||
class Publisher(Base):
|
||||
__tablename__ = "publisher"
|
||||
id: Mapped[str] = mapped_column(primary_key=True, default=uuid.uuid4)
|
||||
created_date: Mapped[datetime] = mapped_column(default=func.now())
|
||||
last_modified_date: Mapped[datetime] = mapped_column(default=func.now())
|
||||
version: Mapped[int] = mapped_column(default=0)
|
||||
name = Column(String, unique=True)
|
||||
parent_publisher_id: Mapped[Optional[str]] = mapped_column(ForeignKey('publisher.id'))
|
||||
parent_publisher: Mapped[Optional['Publisher']] = relationship("Publisher", back_populates="imprints", remote_side=[id])
|
||||
imprints: Mapped[List['Publisher']] = relationship('Publisher', back_populates="parent_publisher")
|
||||
comics = relationship("Comic")
|
||||
|
||||
def __repr__(self):
|
||||
@@ -25,6 +34,7 @@ class Comic(Base, BaseMixin):
|
||||
publisher = relationship("Publisher", back_populates="comics")
|
||||
current_order = Column(Boolean)
|
||||
completed = Column(Boolean)
|
||||
weblink = Column(String, nullable=True)
|
||||
issues = relationship("Issue", order_by="Issue.issue_number")
|
||||
story_arcs = relationship("StoryArc")
|
||||
trade_paperbacks = relationship("TradePaperback")
|
||||
@@ -91,6 +101,7 @@ class Issue(Base, BaseMixin):
|
||||
class Artist(Base, BaseMixin):
|
||||
__tablename__ = "artist"
|
||||
name = Column(String, nullable=False)
|
||||
weblink = Column(String, nullable=True)
|
||||
comic_works = relationship("ComicWork")
|
||||
|
||||
def get_comics(self) -> Dict[str, List[str]]:
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from typing import List, Type
|
||||
from typing import List, Type, AnyStr
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from src.core.log_conf import logger
|
||||
from src.db.models.comic import Artist, Comic, Issue, WorkType
|
||||
from src.schema.comics.artist import ArtistDetailResponse
|
||||
from src.schema.comics.issue import IssueDetailsResponse
|
||||
from src.webapps.comic.forms import AddWorktypeForm
|
||||
from src.schema.comics.worktype import AddWorkType
|
||||
|
||||
|
||||
def get_artist_details(artist: Artist) -> ArtistDetailResponse:
|
||||
@@ -42,7 +43,7 @@ def get_issue_details(issue: Issue) -> IssueDetailsResponse:
|
||||
)
|
||||
return response
|
||||
|
||||
def create_new_worktype(work: AddWorktypeForm, db: Session) -> WorkType:
|
||||
def create_new_worktype(work: AddWorkType, db: Session) -> WorkType:
|
||||
worktype = WorkType()
|
||||
worktype.id = str(uuid.uuid4())
|
||||
worktype.created_date = datetime.now()
|
||||
@@ -51,5 +52,15 @@ def create_new_worktype(work: AddWorktypeForm, db: Session) -> WorkType:
|
||||
db.add(worktype)
|
||||
db.commit()
|
||||
db.refresh(worktype)
|
||||
print(worktype)
|
||||
logger.info(f"create_new_worktype: {worktype}")
|
||||
return worktype
|
||||
|
||||
|
||||
def update_worktype(work: AddWorkType, worktype_id: AnyStr, db: Session) -> WorkType:
|
||||
logger.info("update worktype")
|
||||
worktype = db.get(WorkType, worktype_id)
|
||||
worktype.name = work.worktype
|
||||
db.add(worktype)
|
||||
db.commit()
|
||||
db.refresh(worktype)
|
||||
return worktype
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
import logging
|
||||
import logging.config
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
|
||||
from src.apis.base import api_router
|
||||
from src.core.log_conf import LOGGING_CONFIG, logger
|
||||
from src.db.session import engine
|
||||
from src.db.utils import check_db_connected, check_db_disconnected
|
||||
from src.webapps.base import api_router as web_app_router
|
||||
from src.core.config import settings
|
||||
from src.db.models.base import Base
|
||||
|
||||
logging.basicConfig(level=logging.INFO,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
handlers=[logging.StreamHandler()]) # Logs to console
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
@@ -31,13 +30,12 @@ def configure_static(app: FastAPI):
|
||||
def create_tables():
|
||||
Base.metadata.create_all(bind=engine)
|
||||
|
||||
def start_application():
|
||||
logging.info(f"using database: {settings.DATABASE_URL}")
|
||||
def start_application(log):
|
||||
log.info(f"using database: {settings.DATABASE_URL}")
|
||||
app = FastAPI(title=settings.PROJECT_NAME, version=settings.PROJECT_VERSION, lifespan=lifespan)
|
||||
include_router(app)
|
||||
configure_static(app)
|
||||
create_tables()
|
||||
return app
|
||||
|
||||
|
||||
kontor = start_application()
|
||||
kontor = start_application(logger)
|
||||
|
||||
@@ -15,4 +15,5 @@ class ArtistResponse(BaseModel):
|
||||
class ArtistDetailResponse(BaseModel):
|
||||
id: str
|
||||
name: str
|
||||
weblink: str
|
||||
works: Dict[str, List[str]]
|
||||
|
||||
@@ -16,6 +16,7 @@ class ComicDetailsResponse(BaseModel):
|
||||
title: str
|
||||
completed : bool
|
||||
current_order : bool
|
||||
weblink: str
|
||||
publisher: str
|
||||
volumes: List[str]
|
||||
works: Dict[str, List[str]]
|
||||
@@ -47,9 +48,9 @@ def get_comic_details(comic: Comic) -> ComicDetailsResponse | None:
|
||||
title=comic.title,
|
||||
completed=comic.completed,
|
||||
current_order=comic.current_order,
|
||||
weblink=comic.weblink,
|
||||
publisher=comic.publisher.name,
|
||||
volumes=volumes,
|
||||
works=works
|
||||
)
|
||||
return response
|
||||
|
||||
|
||||
@@ -20,6 +20,10 @@
|
||||
<th scope="row">Artist Name</th>
|
||||
<td colspan="2">{{artist.name}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Link</th>
|
||||
<td colspan="2">{{artist.weblink}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Works</th>
|
||||
<td colspan="2">
|
||||
@@ -46,5 +50,9 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div><a href="/comic/artists" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Back to list</a></div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@@ -27,6 +27,10 @@
|
||||
{% endwith %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Link</th>
|
||||
<td colspan="2">{{comic.weblink}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Works</th>
|
||||
<td colspan="2">
|
||||
|
||||
@@ -20,6 +20,24 @@
|
||||
<th scope="row">Publisher Name</th>
|
||||
<td colspan="2">{{publisher.name}}</td>
|
||||
</tr>
|
||||
{% if publisher.parent_publisher_id %}
|
||||
<tr>
|
||||
<th scope="row">Parent Company</th>
|
||||
<td colspan="2"><a href="/comic/publishers/{{publisher.parent_publisher_id}}">{{publisher.parent_publisher.name}}</a></td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if publisher.imprints|length > 0 %}
|
||||
<tr>
|
||||
<th scope="row">Imprints</th>
|
||||
<td colspan="2">
|
||||
<ul>
|
||||
{% for imprint in publisher.imprints %}
|
||||
<li><a href="/comic/publishers/{{imprint.id}}">{{imprint.name}}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr>
|
||||
<th scope="row">Comics</th>
|
||||
<td colspan="2">
|
||||
|
||||
@@ -30,6 +30,10 @@
|
||||
{% endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">ID</th>
|
||||
<td colspan="2">{{worktype.id}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Data Created</th>
|
||||
<td colspan="2">{{worktype.created_date}}</td>
|
||||
@@ -45,5 +49,23 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div>
|
||||
<a href="/comic/worktypes" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Back to list</a>
|
||||
<a href="/comic/worktype/edit/{{worktype.id}}" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Edit</a>
|
||||
<a href="/comic/worktype/delete/{{worktype.id}}" class="btn btn-outline-danger btn-sm active" role="button" aria-pressed="true">Delete</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script type="text/javascript">
|
||||
function delete_job(id){
|
||||
fetch('/comics/worktypes/'+id+'/delete/'+id,{
|
||||
method:'DELETE',})
|
||||
.then(response => response.json())
|
||||
.then(document.getElementById('result').innerHTML = "Refreshing...")
|
||||
.then(data => document.getElementById('result').innerHTML = data.detail);
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -16,11 +16,13 @@
|
||||
<tbody>
|
||||
{% for worktype in worktypes %}
|
||||
<tr>
|
||||
<th scope="row"><a href="/comic/worktypes/{{worktype.id}}">{{worktype.name}}</a></th>
|
||||
<th scope="row"><a href="/comic/worktypes/{{worktype.id}}">{{worktype.name}}</a></th>
|
||||
<td><a href="/comic/worktype/edit/{{worktype.id}}" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Edit</a></td>
|
||||
<td><a href="/comic/worktype/delete/{{worktype.id}}" class="btn btn-outline-danger btn-sm active" role="button" aria-pressed="true">Delete</a></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<a href="/comic/add-worktype" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Add WorkType</a>
|
||||
<a href="/comic/worktype/add" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Add WorkType</a>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<div class="card shadow p-3 mb-2 bg-body rounded" style="width: 18rem;">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">{{obj.name}}</h5>
|
||||
<p class="card-text">Link : {{obj.weblink}}</p>
|
||||
<a href="/comic/artists/{{obj.id}}" class="btn btn-primary">Read more</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<li><a class="dropdown-item" href="/comic/artists/">Artists</a></li>
|
||||
<li><a class="dropdown-item" href="/comic/publishers/">Publishers</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><a class="dropdown-item" href="/comic/worktypes/">WorkTypes</a></li>
|
||||
<li><a class="dropdown-item" href="/comic/worktypes">WorkTypes</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item dropdown">
|
||||
|
||||
@@ -16,13 +16,13 @@
|
||||
<th scope="col">Cloudlink</th>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
{% for mediavideo in mediavideos %}
|
||||
<tr>
|
||||
{% for mediavideo in mediavideos %}
|
||||
<tr>
|
||||
<th scope="row"><a href="/media/videos/{{mediavideo.id}}">{{mediavideo.title}}</a></th>
|
||||
<td>{{mediavideo.url}}</td>
|
||||
<td>{{mediavideo.cloud_link}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
from fastapi import APIRouter, Request, responses, status
|
||||
from fastapi.templating import Jinja2Templates
|
||||
from fastapi.responses import RedirectResponse
|
||||
|
||||
from src.apis.utils import SessionDep
|
||||
from src.db.models.comic import Comic, Artist, Publisher, Issue, WorkType
|
||||
from typing import AnyStr
|
||||
|
||||
from src.db.repository.comic import create_new_worktype
|
||||
from src.db.repository.comic import create_new_worktype, update_worktype
|
||||
from src.main import logger
|
||||
from src.schema.comics.worktype import AddWorkType
|
||||
from src.webapps.comic.forms import AddWorktypeForm
|
||||
|
||||
@@ -59,26 +62,54 @@ def get_worktypes(db: SessionDep, request: Request, msg: str = None):
|
||||
return templates.TemplateResponse("comic/worktypes.html", {"request": request, "msg": msg, "worktypes": worktypes})
|
||||
|
||||
@router.get("/worktypes/{worktype_id}")
|
||||
def worktype_detail(worktype_id: AnyStr, request: Request, db: SessionDep):
|
||||
def worktype_detail(db: SessionDep, request: Request, worktype_id: AnyStr):
|
||||
worktype = db.get(WorkType, worktype_id)
|
||||
return templates.TemplateResponse("comic/worktype_detail.html", {"request": request, "worktype": worktype})
|
||||
|
||||
@router.get("/add-worktype")
|
||||
@router.get("/worktype/add")
|
||||
def add_worktype(request: Request, db: SessionDep):
|
||||
return templates.TemplateResponse("comic/add_worktype.html", {"request": request})
|
||||
return templates.TemplateResponse("comic/worktype_edit.html", {"request": request})
|
||||
|
||||
@router.post("/add-worktype")
|
||||
async def post_worktype(request: Request, db: SessionDep):
|
||||
@router.post("/worktype/add")
|
||||
async def add_worktype(db: SessionDep, request: Request):
|
||||
form = AddWorktypeForm(request)
|
||||
await form.load_data()
|
||||
if form.is_valid():
|
||||
try:
|
||||
work = AddWorkType(**form.__dict__)
|
||||
worktype = create_new_worktype(work=work, db=db)
|
||||
return responses.RedirectResponse(f"/comic/worktypes/{worktype.id}", status_code=status.HTTP_302_FOUND)
|
||||
logger.info(f"add_worktype: redirect to /comic/worktypes/{worktype.id}")
|
||||
return RedirectResponse(f"/comic/worktypes/{worktype.id}", status_code=status.HTTP_303_SEE_OTHER)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
form.__dict__.get("errors").append("worktype already added")
|
||||
return templates.TemplateResponse("comic/add_worktype.html", form.__dict__)
|
||||
return templates.TemplateResponse("comic/add_worktype.html", form.__dict__)
|
||||
return templates.TemplateResponse("comic/worktype_edit.html", form.__dict__)
|
||||
print("form is not valid")
|
||||
return templates.TemplateResponse("comic/worktype_edit.html", form.__dict__)
|
||||
|
||||
@router.get("/worktype/edit/{worktype_id}")
|
||||
def edit_worktype(db: SessionDep, request: Request, worktype_id: str):
|
||||
worktype = db.get(WorkType, worktype_id)
|
||||
return templates.TemplateResponse("comic/worktype_edit.html", {"request": request, "worktype": worktype.name})
|
||||
|
||||
@router.post("/worktype/edit/{worktype_id}")
|
||||
async def edit_worktype(request: Request, db: SessionDep, worktype_id: str):
|
||||
form = AddWorktypeForm(request)
|
||||
await form.load_data()
|
||||
if form.is_valid():
|
||||
try:
|
||||
work = AddWorkType(**form.__dict__)
|
||||
worktype = update_worktype(work=work, worktype_id=worktype_id, db=db)
|
||||
return RedirectResponse(f"/comic/worktypes/{worktype.id}", status_code=status.HTTP_303_SEE_OTHER)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
form.__dict__.get("errors").append("worktype already added")
|
||||
return templates.TemplateResponse("comic/worktype_edit.html", form.__dict__)
|
||||
return templates.TemplateResponse("comic/worktype_edit.html", form.__dict__)
|
||||
|
||||
@router.get("/worktype/delete/{worktype_id}")
|
||||
async def delete_worktype(db: SessionDep, request: Request, worktype_id: str):
|
||||
worktype = db.get(WorkType, worktype_id)
|
||||
db.delete(worktype)
|
||||
db.commit()
|
||||
return RedirectResponse("/comic/worktypes", status_code=status.HTTP_303_SEE_OTHER)
|
||||
|
||||
Reference in New Issue
Block a user