mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-12-12 05:15:18 -05:00
Frontend Refactor + Bug Fixes
* merge category and tag selector * unifiy category selector * add hint * spacing * fix nextcloud migration * simplify email validator #261 * formatting * cleanup * auto-gen * format * update run script * unified category/tag selector * rename component * Add advanced search link * remove old code * convert keywords to tags * add proper behavior on rename * proper image name association on rename * fix test cleanup * changelog * set docker comppand * minify on migration Co-authored-by: hay-kot <hay-kot@pm.me>
This commit is contained in:
@@ -127,5 +127,3 @@ class AppSettings(BaseSettings):
|
||||
|
||||
|
||||
settings = AppSettings()
|
||||
|
||||
print(settings.dict())
|
||||
|
||||
@@ -9,7 +9,8 @@ from mealie.db.models.users import User
|
||||
from mealie.schema.category import RecipeCategoryResponse, RecipeTagResponse
|
||||
from mealie.schema.meal import MealPlanInDB
|
||||
from mealie.schema.recipe import Recipe
|
||||
from mealie.schema.settings import CustomPageOut, SiteSettings as SiteSettingsSchema
|
||||
from mealie.schema.settings import CustomPageOut
|
||||
from mealie.schema.settings import SiteSettings as SiteSettingsSchema
|
||||
from mealie.schema.sign_up import SignUpOut
|
||||
from mealie.schema.theme import SiteTheme
|
||||
from mealie.schema.user import GroupInDB, UserInDB
|
||||
|
||||
@@ -60,7 +60,7 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
|
||||
|
||||
@validates("name")
|
||||
def validate_name(self, key, name):
|
||||
assert not name == ""
|
||||
assert name != ""
|
||||
return name
|
||||
|
||||
def __init__(
|
||||
@@ -92,11 +92,7 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
|
||||
self.image = image
|
||||
self.recipeCuisine = recipeCuisine
|
||||
|
||||
if self.nutrition:
|
||||
self.nutrition = Nutrition(**nutrition)
|
||||
else:
|
||||
self.nutrition = Nutrition()
|
||||
|
||||
self.nutrition = Nutrition(**nutrition) if self.nutrition else Nutrition()
|
||||
self.tools = [Tool(tool=x) for x in tools] if tools else []
|
||||
|
||||
self.recipeYield = recipeYield
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import sqlalchemy as sa
|
||||
import sqlalchemy.orm as orm
|
||||
from mealie.db.models.model_base import SqlAlchemyBase
|
||||
from fastapi.logger import logger
|
||||
from mealie.db.models.model_base import SqlAlchemyBase
|
||||
from slugify import slugify
|
||||
from sqlalchemy.orm import validates
|
||||
|
||||
@@ -25,7 +25,7 @@ class Tag(SqlAlchemyBase):
|
||||
assert name != ""
|
||||
return name
|
||||
|
||||
def __init__(self, name) -> None:
|
||||
def __init__(self, name, session=None) -> None:
|
||||
self.name = name.strip()
|
||||
self.slug = slugify(self.name)
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ from mealie.db.db_setup import generate_session
|
||||
from mealie.routes.deps import get_current_user
|
||||
from mealie.schema.recipe import Recipe, RecipeURLIn
|
||||
from mealie.schema.snackbar import SnackResponse
|
||||
from mealie.services.image.image import IMG_OPTIONS, delete_image, read_image, write_image
|
||||
from mealie.services.image.image import IMG_OPTIONS, delete_image, read_image, rename_image, write_image
|
||||
from mealie.services.scraper.scraper import create_from_url
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
@@ -61,6 +61,9 @@ def update_recipe(
|
||||
|
||||
recipe: Recipe = db.recipes.update(session, recipe_slug, data.dict())
|
||||
|
||||
if recipe_slug != recipe.slug:
|
||||
rename_image(original_slug=recipe_slug, new_slug=recipe.slug)
|
||||
|
||||
return recipe.slug
|
||||
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ from fastapi import APIRouter, Depends
|
||||
from mealie.db.database import db
|
||||
from mealie.db.db_setup import generate_session
|
||||
from mealie.routes.deps import get_current_user
|
||||
from mealie.schema.category import RecipeTagResponse
|
||||
from mealie.schema.category import RecipeTagResponse, TagIn
|
||||
from mealie.schema.snackbar import SnackResponse
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
@@ -19,6 +19,14 @@ async def get_all_recipe_tags(session: Session = Depends(generate_session)):
|
||||
""" Returns a list of available tags in the database """
|
||||
return db.tags.get_all_limit_columns(session, ["slug", "name"])
|
||||
|
||||
@router.post("")
|
||||
async def create_recipe_tag(
|
||||
tag: TagIn, session: Session = Depends(generate_session), current_user=Depends(get_current_user)
|
||||
):
|
||||
""" Creates a Tag in the database """
|
||||
|
||||
return db.tags.create(session, tag.dict())
|
||||
|
||||
|
||||
@router.get("/{tag}", response_model=RecipeTagResponse)
|
||||
def get_all_recipes_by_tag(tag: str, session: Session = Depends(generate_session)):
|
||||
|
||||
@@ -4,7 +4,7 @@ from datetime import timedelta
|
||||
from fastapi import APIRouter, Depends, File, UploadFile
|
||||
from fastapi.responses import FileResponse
|
||||
from mealie.core import security
|
||||
from mealie.core.config import settings, app_dirs
|
||||
from mealie.core.config import app_dirs, settings
|
||||
from mealie.core.security import get_password_hash, verify_password
|
||||
from mealie.db.database import db
|
||||
from mealie.db.db_setup import generate_session
|
||||
|
||||
@@ -3,24 +3,30 @@
|
||||
# Get Reload Arg `run.sh reload` for dev server
|
||||
ARG1=${1:-production}
|
||||
|
||||
# Initialize Database Prerun
|
||||
python mealie/db/init_db.py
|
||||
python mealie/services/image/minify.py
|
||||
# Set Script Directory - Used for running the script from a different directory.
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
|
||||
|
||||
## Migrations
|
||||
# # Initialize Database Prerun
|
||||
poetry run python $DIR/db/init_db.py
|
||||
poetry run python $DIR/services/image/minify.py
|
||||
|
||||
# Migrations
|
||||
# TODO
|
||||
# Migrations
|
||||
# Set Port from ENV Variable
|
||||
|
||||
if [ "$ARG1" = "reload" ]
|
||||
if [[ "$ARG1" = "reload" ]]
|
||||
then
|
||||
echo "Hot reload"
|
||||
echo "Hot Reload!"
|
||||
|
||||
# Start API
|
||||
uvicorn mealie.app:app --host 0.0.0.0 --port 9000 --reload
|
||||
else
|
||||
echo "Production config"
|
||||
echo "Production"
|
||||
# Web Server
|
||||
caddy start --config ./Caddyfile
|
||||
|
||||
# Start API
|
||||
uvicorn mealie.app:app --host 0.0.0.0 --port 9000
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
@@ -23,6 +23,10 @@ class RecipeCategoryResponse(CategoryBase):
|
||||
schema_extra = {"example": {"id": 1, "name": "dinner", "recipes": [{}]}}
|
||||
|
||||
|
||||
class TagIn(CategoryIn):
|
||||
pass
|
||||
|
||||
|
||||
class TagBase(CategoryBase):
|
||||
pass
|
||||
|
||||
|
||||
@@ -29,7 +29,6 @@ def read_image(recipe_slug: str, image_type: str = "original") -> Path:
|
||||
Returns:
|
||||
Path: [description]
|
||||
"""
|
||||
print(image_type)
|
||||
recipe_slug = recipe_slug.split(".")[0] # Incase of File Name
|
||||
recipe_image_dir = app_dirs.IMG_DIR.joinpath(recipe_slug)
|
||||
|
||||
@@ -39,6 +38,18 @@ def read_image(recipe_slug: str, image_type: str = "original") -> Path:
|
||||
return None
|
||||
|
||||
|
||||
def rename_image(original_slug, new_slug) -> Path:
|
||||
current_path = app_dirs.IMG_DIR.joinpath(original_slug)
|
||||
new_path = app_dirs.IMG_DIR.joinpath(new_slug)
|
||||
|
||||
try:
|
||||
new_path = current_path.rename(new_path)
|
||||
except FileNotFoundError:
|
||||
logger.error(f"Image Directory {original_slug} Doesn't Exist")
|
||||
|
||||
return new_path
|
||||
|
||||
|
||||
def write_image(recipe_slug: str, file_data: bytes, extension: str) -> Path.name:
|
||||
try:
|
||||
delete_image(recipe_slug)
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi.logger import logger
|
||||
from mealie.core.config import app_dirs
|
||||
from PIL import Image, UnidentifiedImageError
|
||||
from mealie.db.database import db
|
||||
from mealie.db.db_setup import create_session
|
||||
from PIL import Image
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
|
||||
def minify_image(image_file: Path, min_dest: Path, tiny_dest: Path):
|
||||
@@ -24,7 +28,7 @@ def minify_image(image_file: Path, min_dest: Path, tiny_dest: Path):
|
||||
tiny_image = crop_center(img)
|
||||
tiny_image.save(tiny_dest, quality=70)
|
||||
|
||||
except:
|
||||
except Exception:
|
||||
shutil.copy(image_file, min_dest)
|
||||
shutil.copy(image_file, tiny_dest)
|
||||
|
||||
@@ -59,6 +63,28 @@ def move_all_images():
|
||||
image_file.rename(new_folder.joinpath(f"original{image_file.suffix}"))
|
||||
|
||||
|
||||
def validate_slugs_in_database(session: Session = None):
|
||||
def check_image_path(image_name: str, slug_path: str) -> bool:
|
||||
existing_path: Path = app_dirs.IMG_DIR.joinpath(image_name)
|
||||
slug_path: Path = app_dirs.IMG_DIR.joinpath(slug_path)
|
||||
|
||||
if existing_path.is_dir():
|
||||
slug_path.rename(existing_path)
|
||||
else:
|
||||
logger.info("No Image Found")
|
||||
|
||||
session = session or create_session()
|
||||
all_recipes = db.recipes.get_all(session)
|
||||
|
||||
slugs_and_images = [(x.slug, x.image) for x in all_recipes]
|
||||
|
||||
for slug, image in slugs_and_images:
|
||||
image_slug = image.split(".")[0] # Remove Extension
|
||||
if slug != image_slug:
|
||||
logger.info(f"{slug}, Doesn't Match '{image_slug}'")
|
||||
check_image_path(image, slug)
|
||||
|
||||
|
||||
def migrate_images():
|
||||
print("Checking for Images to Minify...")
|
||||
|
||||
@@ -77,10 +103,11 @@ def migrate_images():
|
||||
org_size = sizeof_fmt(image.stat().st_size)
|
||||
dest_size = sizeof_fmt(min_dest.stat().st_size)
|
||||
tiny_size = sizeof_fmt(tiny_dest.stat().st_size)
|
||||
print(f"{image.name} Minified: {org_size} -> {dest_size} -> {tiny_size}")
|
||||
logger.info(f"{image.name} Minified: {org_size} -> {dest_size} -> {tiny_size}")
|
||||
|
||||
print("Finished Minification Check")
|
||||
logger.info("Finished Minification Check")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
migrate_images()
|
||||
validate_slugs_in_database()
|
||||
|
||||
@@ -6,6 +6,7 @@ from fastapi.logger import logger
|
||||
from mealie.core.config import app_dirs
|
||||
from mealie.db.database import db
|
||||
from mealie.schema.recipe import Recipe
|
||||
from mealie.services.image.minify import migrate_images
|
||||
from mealie.utils.unzip import unpack_zip
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
@@ -89,4 +90,5 @@ def chowdown_migrate(session: Session, zip_file: Path):
|
||||
failed_images.append(image.name)
|
||||
report = {"successful": successful_recipes, "failed": failed_recipes}
|
||||
|
||||
migrate_images()
|
||||
return report
|
||||
|
||||
@@ -7,6 +7,7 @@ from pathlib import Path
|
||||
from mealie.core.config import app_dirs
|
||||
from mealie.db.database import db
|
||||
from mealie.schema.recipe import Recipe
|
||||
from mealie.services.image import minify
|
||||
from mealie.services.scraper.cleaner import Cleaner
|
||||
|
||||
|
||||
@@ -23,39 +24,43 @@ def process_selection(selection: Path) -> Path:
|
||||
return None
|
||||
|
||||
|
||||
def clean_nextcloud_tags(nextcloud_tags: str):
|
||||
if not isinstance(nextcloud_tags, str):
|
||||
return None
|
||||
|
||||
return [x.title().lstrip() for x in nextcloud_tags.split(",") if x != ""]
|
||||
|
||||
|
||||
def import_recipes(recipe_dir: Path) -> Recipe:
|
||||
image = False
|
||||
|
||||
for file in recipe_dir.glob("full.*"):
|
||||
image = file
|
||||
break
|
||||
|
||||
for file in recipe_dir.glob("*.json"):
|
||||
recipe_file = file
|
||||
break
|
||||
|
||||
with open(recipe_file, "r") as f:
|
||||
recipe_dict = json.loads(f.read())
|
||||
|
||||
recipe_data = Cleaner.clean(recipe_dict)
|
||||
|
||||
image_name = None
|
||||
if image:
|
||||
image_name = recipe_data["slug"] + image.suffix
|
||||
recipe_data["image"] = image_name
|
||||
else:
|
||||
recipe_data["image"] = "none"
|
||||
image_name = recipe_data["slug"]
|
||||
recipe_data["image"] = recipe_data["slug"]
|
||||
recipe_data["tags"] = clean_nextcloud_tags(recipe_data.get("keywords"))
|
||||
|
||||
recipe = Recipe(**recipe_data)
|
||||
|
||||
if image:
|
||||
shutil.copy(image, app_dirs.IMG_DIR.joinpath(image_name))
|
||||
shutil.copy(image, app_dirs.IMG_DIR.joinpath(image_name + image.suffix))
|
||||
|
||||
return recipe
|
||||
|
||||
|
||||
def prep():
|
||||
try:
|
||||
shutil.rmtree(app_dirs.TEMP_DIR)
|
||||
except:
|
||||
pass
|
||||
shutil.rmtree(app_dirs.TEMP_DIR, ignore_errors=True)
|
||||
app_dirs.TEMP_DIR.mkdir(exist_ok=True, parents=True)
|
||||
|
||||
|
||||
@@ -80,11 +85,13 @@ def migrate(session, selection: str):
|
||||
db.recipes.create(session, recipe.dict())
|
||||
|
||||
successful_imports.append(recipe.name)
|
||||
except:
|
||||
except Exception:
|
||||
session.rollback()
|
||||
logging.error(f"Failed Nextcloud Import: {dir.name}")
|
||||
logging.exception("")
|
||||
failed_imports.append(dir.name)
|
||||
|
||||
cleanup()
|
||||
minify.migrate_images()
|
||||
|
||||
return {"successful": successful_imports, "failed": failed_imports}
|
||||
|
||||
Reference in New Issue
Block a user