def decorated_function(*args, **kwargs): api_enabled = configparser_results( current_app.config['CONFIG_INI']).getboolean( 'general', 'api_enabled') api_key = configparser_results(current_app.config['CONFIG_INI']).get( 'general', 'api_key') if api_enabled and request.headers.get( 'X-Salt-to-Taste-API-Key') and request.headers.get( 'X-Salt-to-Taste-API-Key') == api_key: return view_function(*args, **kwargs) else: abort(401)
def decorated_function(*args, **kwargs): authentication_enabled = configparser_results( current_app.config['CONFIG_INI']).getboolean( 'general', 'authentication_enabled') user_exists = get_user_by_id(1) userless_recipes = configparser_results( current_app.config['CONFIG_INI']).getboolean( 'general', 'userless_recipes') if (not userless_recipes and (authentication_enabled and user_exists)) and not current_user.is_authenticated: return current_app.login_manager.unauthorized() else: return view_function(*args, **kwargs)
def backup_image_file(filename): date = datetime.today().timestamp() config = configparser_results(current_app.config['CONFIG_INI']) root_ext = os.path.splitext(filename) backup_dir = f"{DATA_DIR}/backups/" backup_filename = f"{root_ext[0].lower()}.backup-{date}{root_ext[1].lower()}" files = [ name for name in os.listdir(backup_dir) if os.path.isfile(os.path.join(backup_dir, name)) if root_ext[0] in name if root_ext[1] in name ] # Delete oldest backup if the backup_count has been met if config.getboolean( 'general', 'backups_enabled') and config['general']['backup_count']: if files and config.getint('general', 'backup_count') <= len(files): os.remove(os.path.join(backup_dir, files[0])) print(f' + Backing up {filename}') try: shutil.copyfile(f"{current_app.config['RECIPE_IMAGES']}{filename}", f'{backup_dir}{backup_filename}') except Exception as e: print(f' * ERROR: Failed to backup {filename}') print(f' * {e}')
def read_config(): config = configparser_results(current_app.config['CONFIG_INI']) return dict( user_exists = get_user_by_id(1), authentication_enabled = config.getboolean('general', 'authentication_enabled'), api_enabled = config.getboolean('general', 'api_enabled'), backups_enabled = config.getboolean('general', 'backups_enabled'), custom_tags = decode_tags(config) )
def settings_json(): if request.method == 'GET': config = configparser_results(current_app.config['CONFIG_INI']) return jsonify({'settings': config._sections}) if request.method == 'PUT': config = request.get_json() update_file = update_configfile(config) if update_file: return jsonify({'success': 'settings updated'}) return jsonify({'error': 'settings were unable to be updated'})
def settings(): config = configparser_results(current_app.config['CONFIG_INI']) user_query = get_user_by_id(1) form = SettingsForm() if form.validate_on_submit(): config_data = form.data parsed_data = parse_settings(config, config_data) parsed_data['tags'] = encode_tags(config, form.tag_name.data, form.tag_icon.data, form.tag_color.data, form.tag_bcolor.data) if user_query and form.username.data != '' and form.password.data != ('**********' or ''): print ('user_query was true, form had username, password was not placeholder. UPDATING USER.') user_query.username = form.username.data user_query.password = form.password.data db.session.commit() elif not user_query and form.username.data != '' and form.password.data !='': print ('user_query was false, form had username, form had password. ADDING USER') user = User(username=form.username.data, password=form.password.data, role='admin') db.session.add(user) db.session.commit() elif user_query and form.username.data == '' and form.password.data == '': print ('username and password removed. DELETING USER') delete_user_by_id(user_query.id) update_configfile(parsed_data) flash('Settings saved.', 'success') return redirect(url_for('main.settings')) form.authentication_enabled.data = config['general']['authentication_enabled'] if user_query: form.username.data = user_query.username form.password.data = '**********' form.userless_recipes.data = config['general']['userless_recipes'] form.api_enabled.data = config['general']['api_enabled'] form.api_key.data = config['general']['api_key'] form.backups_enabled.data = config['general']['backups_enabled'] form.backup_count.data = config['general']['backup_count'] form.edamam_id.data = config['third_party']['edamam_id'] form.edamam_key.data = config['third_party']['edamam_key'] return render_template("settings.html", form=form)
def backup_config_file(): date = datetime.today().timestamp() config = configparser_results(current_app.config['CONFIG_INI']) backup_dir = f"{DATA_DIR}/backups/" files = [ name for name in os.listdir(backup_dir) if os.path.isfile(os.path.join(backup_dir, name)) if 'config' in name if '.ini' in name ] if config.getboolean( 'general', 'backups_enabled') and config['general']['backup_count']: if files and config.getint('general', 'backup_count') <= len(files): os.remove(os.path.join(backup_dir, files[0])) print(' + Backing up config.ini') shutil.copyfile(f'{DATA_DIR}/config.ini', f'{backup_dir}config.backup-{date}.ini')
def delete(recipe_id): recipe_query = get_recipe(recipe_id) config = configparser_results(current_app.config['CONFIG_INI']) if recipe_query: if config.getboolean('general', 'backups_enabled'): backup_recipe_file(recipe_query['filename']) backup_database_file() delete_file(current_app.config['RECIPE_FILES'], recipe_query['filename']) if 'image' in recipe_query and recipe_query['image'] != None: if config.getboolean('general', 'backups_enabled'): backup_image_file(recipe_query['image']) delete_file(current_app.config['RECIPE_IMAGES'], recipe_query['image']) delete_recipe(recipe_id) flash(f"Recipe \"{recipe_query['title']}\" deleted.", 'success') return redirect(url_for('main.index')) flash("Recipe not deleted, ID not found.", 'danger') return redirect(url_for('main.index'))
def delete_recipe_json(recipe_id): recipe_query = get_recipe(recipe_id) config = configparser_results(current_app.config['CONFIG_INI']) if recipe_query: if config.getboolean('general', 'backups_enabled'): backup_recipe_file(recipe_query['filename']) backup_database_file() delete_file(current_app.config['RECIPE_FILES'], recipe_query['filename']) if 'image' in recipe_query and recipe_query['image'] != None: if config.getboolean('general', 'backups_enabled'): backup_image_file(recipe_query['image']) delete_file(current_app.config['RECIPE_IMAGES'], recipe_query['image']) delete_recipe(recipe_id) return jsonify({'success': 'recipe deleted'}) return jsonify({'error': 'recipe ID not found'})
def backup_database_file(): date = datetime.today().timestamp() config = configparser_results(current_app.config['CONFIG_INI']) backup_dir = f"{DATA_DIR}/backups/" files = [ name for name in os.listdir(backup_dir) if os.path.isfile(os.path.join(backup_dir, name)) if 'database' in name if '.db' in name ] if config.getboolean( 'general', 'backups_enabled') and config['general']['backup_count']: if files and config.getint('general', 'backup_count') <= len(files): os.remove(os.path.join(backup_dir, files[0])) print(' + Backing up database.db') try: shutil.copyfile(f'{DATA_DIR}/database.db', f'{backup_dir}database.backup-{date}.db') except Exception as e: print(f' * ERROR: Failed to backup database') print(f' * {e}')
def update(recipe_id): # Class to convert recipe dict to obj class Struct: def __init__(self, **entries): self.__dict__.update(entries) config = configparser_results(current_app.config['CONFIG_INI']) recipe_query = get_recipe(recipe_id) if not recipe_query: abort(404) recipe_query['tags'] = ", ".join(recipe_query['tags']) recipe_obj = Struct(**recipe_query) form = UpdateRecipeForm(obj=recipe_obj) if form.validate_on_submit(): title_formatted = form.title.data.replace(" ", "-").lower() filename = f"{title_formatted}.yaml" image_data = form.image.data tags = form.tags.data.split(',') while("" in tags) : tags.remove("") if hasattr(image_data, 'filename'): if config.getboolean('general', 'backups_enabled'): backup_image_file(recipe_query['image']) ext_new = image_data.filename.rsplit('.', 1)[1].lower() ext_old = recipe_query['image'].rsplit('.', 1)[1].lower if ext_new != ext_old: delete_file(current_app.config['RECIPE_IMAGES'], recipe_query['image']) image_data.filename = f'{title_formatted}.{ext_new}' image = secure_filename(image_data.filename) save_image(current_app.config['RECIPE_IMAGES'], image, image_data) else: image = recipe_query['image'] recipe = OrderedDict({ 'layout' : form.layout.data or recipe_query['layout'], 'title' : form.title.data, 'title_formatted' : title_formatted, 'image' : image, 'imagecredit' : form.imagecredit.data or None, 'tags' : [tag.strip(' ').lower() for tag in tags], 'source' : form.source.data or None, 'prep' : form.prep.data or None, 'cook' : form.cook.data or None, 'ready' : form.ready.data or None, 'servings' : form.servings.data or None, 'calories' : form.calories.data or None, 'description' : form.description.data or None, 'ingredients' : [x for x in form.ingredients.data if x != ''], 'directions' : [x for x in form.directions.data if x != ''], 'notes' : [x for x in form.notes.data if x != ''], 'filename' : filename }) if recipe['title'] != recipe_query['title']: if recipe['title_formatted'] != recipe_query['title_formatted']: # Rename image ext = image.rsplit('.', 1)[1].lower() updated_filename = f'{title_formatted}.{ext}' secure_file = secure_filename(updated_filename) rename_file(os.path.join(current_app.config['RECIPE_IMAGES'], image), os.path.join(current_app.config['RECIPE_IMAGES'], secure_file)) recipe['image'] = secure_file # Rename file rename_file(os.path.join(current_app.config['RECIPE_FILES'], recipe_query['filename']), os.path.join(current_app.config['RECIPE_FILES'], recipe['filename'])) if config.getboolean('general', 'backups_enabled'): backup_recipe_file(recipe_query['filename']) backup_database_file() create_recipe_file(current_app.config['RECIPE_FILES'], recipe) file = os.path.join(current_app.config['RECIPE_FILES'], recipe['filename']) recipe['file_hash'] = hash_file(file) update_recipe(recipe_query['filename'], recipe_query['title'], recipe) return redirect(url_for('main.recipe', title_formatted=recipe['title_formatted'])) return render_template("update.html", form=form, id=recipe_query['id'], image=recipe_query['image'])
def fetch_recipe_nutrition(title, servings, ingredient_list): argument = argparser_results() DATA_DIR = os.path.abspath(argument['DATA_DIR']) config = configparser_results(f'{DATA_DIR}/config.ini') app_id = config.get('third_party', 'edamam_id') app_key = config.get('third_party', 'edamam_key') if not app_id or not app_key: return False print(f' * Searching for nutrition information on {title}') params = {'app_id': app_id, 'app_key': app_key} headers = {'Content-Type': 'application/json'} ingredient_dict = { 'title': title, 'yield': servings, 'ingr': ingredient_list } r = requests.post('https://api.edamam.com/api/nutrition-details', params=params, headers=headers, json=ingredient_dict) if r.status_code != 200: print( f" * ERROR: Could not fetch nutrition data. | Code:({r.status_code} Message:{r.json()['error']})" ) return False r_json = r.json() nutrition_dict = { 'weight': r_json.get('totalWeight'), 'nutrients': { 'calcium': r_json['totalNutrients'].get('CA'), 'carbs': r_json['totalNutrients'].get('CHOCDF'), 'cholesterol': r_json['totalNutrients'].get('CHOLE'), 'energy': r_json['totalNutrients'].get('ENERC_KCAL'), 'fat': r_json['totalNutrients'].get('FAT'), 'fiber': r_json['totalNutrients'].get('FIBTG'), 'folate_equivalent': r_json['totalNutrients'].get('FOLDFE'), 'folate_food': r_json['totalNutrients'].get('FOLFD'), 'iron': r_json['totalNutrients'].get('FE'), 'magnesium': r_json['totalNutrients'].get('MG'), 'monounsaturated': r_json['totalNutrients'].get('FAMS'), 'niacin_b3': r_json['totalNutrients'].get('NIA'), 'phosphorus': r_json['totalNutrients'].get('P'), 'polyunsaturated': r_json['totalNutrients'].get('FAPU'), 'potassium': r_json['totalNutrients'].get('K'), 'protein': r_json['totalNutrients'].get('PROCNT'), 'riboflavin_b2': r_json['totalNutrients'].get('RIBF'), 'saturated': r_json['totalNutrients'].get('FASAT'), 'sodium': r_json['totalNutrients'].get('NA'), 'sugars': r_json['totalNutrients'].get('SUGAR'), 'sugars_added': r_json['totalNutrients'].get('SUGAR.added'), 'thiamin_b1': r_json['totalNutrients'].get('THIA'), 'trans': r_json['totalNutrients'].get('FATRN'), 'vitamin_a': r_json['totalNutrients'].get('VITA_RAE'), 'vitamin_b12': r_json['totalNutrients'].get('VITB12'), 'vitamin_b6': r_json['totalNutrients'].get('VITB6A'), 'vitamin_c': r_json['totalNutrients'].get('VITC'), 'vitamin_d': r_json['totalNutrients'].get('VITD'), 'vitamin_e': r_json['totalNutrients'].get('TOCPHA'), 'vitamin_k': r_json['totalNutrients'].get('VITK1') }, 'daily': { 'calcium': r_json['totalDaily'].get('CA'), 'carbs': r_json['totalDaily'].get('CHOCDF'), 'cholesterol': r_json['totalDaily'].get('CHOLE'), 'energy': r_json['totalDaily'].get('ENERC_KCAL'), 'fat': r_json['totalDaily'].get('FAT'), 'fiber': r_json['totalDaily'].get('FIBTG'), 'folate_equivalent': r_json['totalDaily'].get('FOLDFE'), 'folate_food': r_json['totalDaily'].get('FOLFD'), 'iron': r_json['totalDaily'].get('FE'), 'magnesium': r_json['totalDaily'].get('MG'), 'monounsaturated': r_json['totalDaily'].get('FAMS'), 'niacin_b3': r_json['totalDaily'].get('NIA'), 'phosphorus': r_json['totalDaily'].get('P'), 'polyunsaturated': r_json['totalDaily'].get('FAPU'), 'potassium': r_json['totalDaily'].get('K'), 'protein': r_json['totalDaily'].get('PROCNT'), 'riboflavin_b2': r_json['totalDaily'].get('RIBF'), 'saturated': r_json['totalDaily'].get('FASAT'), 'sodium': r_json['totalDaily'].get('NA'), 'sugars': r_json['totalDaily'].get('SUGAR'), 'sugars_added': r_json['totalDaily'].get('SUGAR.added'), 'thiamin_b1': r_json['totalDaily'].get('THIA'), 'trans': r_json['totalDaily'].get('FATRN'), 'vitamin_a': r_json['totalDaily'].get('VITA_RAE'), 'vitamin_b12': r_json['totalDaily'].get('VITB12'), 'vitamin_b6': r_json['totalDaily'].get('VITB6A'), 'vitamin_c': r_json['totalDaily'].get('VITC'), 'vitamin_d': r_json['totalDaily'].get('VITD'), 'vitamin_e': r_json['totalDaily'].get('TOCPHA'), 'vitamin_k': r_json['totalDaily'].get('VITK1') } } # Only keep the quantity value for each item keys = nutrition_dict['nutrients'].keys() for section in ['nutrients', 'daily']: for key in keys: if nutrition_dict[section][key]: nutrition_dict[section][key] = nutrition_dict[section][key][ 'quantity'] return nutrition_dict
def update_recipes_json(recipe_id): data = request.get_json() downloadImage = request.args.get('downloadImage') config = configparser_results(current_app.config['CONFIG_INI']) recipe_query = get_recipe(recipe_id) if not recipe_query: return jsonify({'error': 'recipe ID not found'}) if not data.get('layout'): return jsonify({'error': 'layout missing'}) if not data.get('title'): return jsonify({'error': 'title missing'}) title_formatted = data.get('title').replace(" ", "-").lower() filename = f'{title_formatted}.yaml' if check_for_duplicate_title_f(recipe_id, title_formatted): return jsonify({'error': 'recipe name must be unique'}) recipe = OrderedDict({ 'layout': data.get('layout'), 'title': data.get('title'), 'title_formatted': title_formatted, 'image': data.get('image'), 'imagecredit': data.get('imagecredit'), 'tags': data.get('tags'), 'source': data.get('source'), 'prep': data.get('prep'), 'cook': data.get('cook'), 'ready': data.get('ready'), 'servings': data.get('servings'), 'calories': data.get('calories'), 'description': data.get('description'), 'ingredients': data.get('ingredients'), 'directions': data.get('directions'), 'notes': data.get('notes'), 'filename': filename }) if downloadImage: if downloadImage != "true": return jsonify( {'error': 'downloadImage set but has invalid value'}) if not recipe['imagecredit']: return jsonify( {'error': 'downloadImage set but imagecredit is empty'}) if config.getboolean('general', 'backups_enabled'): backup_image_file(recipe_query['image']) image = download_image(recipe['imagecredit'], current_app.config['RECIPE_IMAGES'], recipe['title_formatted']) if not image: return jsonify({ 'error': 'image download failed', 'message': 'check imagecredit value' }) recipe['image'] = image if recipe['title'] != recipe_query['title']: if recipe['title_formatted'] != recipe_query['title_formatted']: # Rename image ext = recipe['image'].rsplit('.', 1)[1].lower() updated_filename = f'{title_formatted}.{ext}' secure_file = secure_filename(updated_filename) rename_file( os.path.join(current_app.config['RECIPE_IMAGES'], recipe_query['image']), os.path.join(current_app.config['RECIPE_IMAGES'], secure_file)) recipe['image'] = secure_file # Rename file rename_file( os.path.join(current_app.config['RECIPE_FILES'], recipe_query['filename']), os.path.join(current_app.config['RECIPE_FILES'], recipe['filename'])) if config.getboolean('general', 'backups_enabled'): backup_recipe_file(recipe_query['filename']) create_recipe_file(current_app.config['RECIPE_FILES'], recipe) file = os.path.join(current_app.config['RECIPE_FILES'], recipe['filename']) recipe['file_hash'] = hash_file(file) update_recipe(recipe_query['filename'], recipe_query['title'], recipe) return jsonify({'success': 'recipe updated'})
def create_app(config_file='settings.py'): argument = argparser_results() DATA_DIR = os.path.abspath(argument['DATA_DIR']) app = Flask(__name__) app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True app.config['SQLALCHEMY_ECHO'] = False app.config['SQLALCHEMY_DATABASE_URI'] = f'sqlite:///{DATA_DIR}/database.db' app.config['WHOOSH_INDEX_PATH'] = f'{DATA_DIR}/whooshIndex' app.config['WHOOSH_ANALYZER'] = 'StemmingAnalyzer' app.config['DATA_DIR'] = DATA_DIR app.config['RECIPE_FILES'] = f'{DATA_DIR}/_recipes/' app.config['RECIPE_IMAGES'] = f'{DATA_DIR}/_images/' app.config['CONFIG_INI'] = f'{DATA_DIR}/config.ini' app.jinja_env.filters['capwords'] = capwords if not os.path.isfile(app.config['CONFIG_INI']): create_default_configfile() verify_configfile() config = configparser_results(app.config['CONFIG_INI']) try: app.config['SECRET_KEY'] = config.get('flask', 'secret_key') except TypeError: print('Error: Could not find Flask secret_key in config.ini.') sys.exit() # Register blueprints app.register_blueprint(main) app.register_blueprint(api, url_prefix='/api') # Create indexes of database tables wa.search_index(app, Recipe) wa.search_index(app, Tag) wa.search_index(app, Ingredient) wa.search_index(app, Direction) wa.search_index(app, Note) # Initalize and create the DB db.init_app(app) db.app = app db.create_all() # Initalize the login manager plugin login_manager.init_app(app) login_manager.login_view = 'main.login' login_manager.login_message_category = 'info' @login_manager.user_loader def load_user(user_id): return User.query.get(user_id) # Import phyiscal recipe files recipe_list = recipe_importer(app.config['RECIPE_FILES']) # Sync physical recipe files with database if not Recipe.query.first(): add_all_recipes(recipe_list) else: add_new_recipes(recipe_list) remove_missing_recipes(recipe_list) update_recipes(recipe_list) db_cleanup() return app