def profile(username): """ User Profile Page. Users can view their own recipes, view their saved recipes from others, update their password, or delete their account. """ username = get_user_lower(session["user"])["username"] # find all recipes belonging to user user = get_user_lower(session["user"])["_id"] user_recipes = recipes_collection.find({"author": user})\ .sort([("recipe_name", 1)]) # find all recipes that the user saved user_favs_list = get_user_lower(session["user"])["user_favs"] user_favs = recipes_collection.find({"_id": {"$in": user_favs_list}})\ .sort([("recipe_name", 1)]) # get the user's avatar user_avatar = get_user_lower(session["user"])["user_avatar"] # ADMIN-only: get list of all database users admin_list = users_collection.find().sort([("username", 1)]) return render_template("profile.html", username=username, user_recipes=user_recipes, user_favs=user_favs, user_avatar=user_avatar, admin_list=admin_list)
def admin_delete_user(username): """ ADMIN: Delete users if necessary. The ADMIN account has option to delete users from database if it needs to. This would delete all of that user's recipes, too. """ user = get_user_lower(username) # find all recipes belonging to user being deleted user_recipes = [recipe for recipe in user.get("user_recipes")] # for each user recipe, remove it from database and pull from others' favs for recipe in user_recipes: recipes_collection.remove({"_id": recipe}) users_collection.update_many({}, {"$pull": {"user_favs": recipe}}) # find all recipes that the user saved user_favs = [recipe for recipe in user.get("user_favs")] # decrement each recipe the user liked by -1 for recipe in user_favs: recipes_collection.update_one({"_id": recipe}, {"$inc": { "user_favs": -1 }}) flash( Markup(f"<i class='fas fa-user-times red-text'></i>\ User has been successfully deleted.")) # delete the user from database using .remove() users_collection.remove({"_id": user.get("_id")}) return redirect(url_for("users.profile", username="******"))
def profile_change_password(username): """ Update user hashed password. Each user has the option to update their password. User must first verify their current password though. """ user = get_user_lower(session["user"]) # check if stored hashed password matches current password in form if check_password_hash(user["user_password"], request.form.get("current_password")): flash( Markup(f"<i class='far fa-check-circle green-text'></i>\ Your password has been updated successfully!")) users_collection.update_one( {"username_lower": session["user"].lower()}, { "$set": { "user_password": generate_password_hash(request.form.get("new_password")) } }) else: # password does not match flash( Markup(f"<i class='fas fa-exclamation-circle red-text'></i>\ Whoops! Looks like your <span class='pink-text text-lighten-2'>\ password</span> is incorrect. Please try again.")) # redirect back to profile return redirect(url_for("users.profile", username=username))
def desserts_delete(recipe_id, slugUrl): """ Delete recipe from database. Remove the recipe from the collection, pull the recipe from the user's recipe list, and pull the recipe from all other users' favorites. """ if "user" in session: # users must be logged in recipe = get_recipe(recipe_id) session_user = get_user_lower(session["user"])["username"] recipe_author = users_collection.find_one( {"_id": ObjectId(recipe.get("author"))})["username"] # check that someone isn't brute-forcing the url to delete recipes if session_user == recipe_author or session_user == "Admin": author = users_collection.find_one_and_update( {"_id": ObjectId(recipe.get("author"))}, {"$pull": {"user_recipes": ObjectId(recipe_id)}}) users_collection.update_many( {}, {"$pull": {"user_favs": ObjectId(recipe_id)}}) recipes_collection.remove({"_id": ObjectId(recipe_id)}) flash(Markup( f"<i class='fas fa-trash-alt red-text'></i>\ Your recipe has been deleted.")) else: flash(Markup( f"<i class='far fa-sad-tear yellow-text'></i>\ You are not authorized to delete this recipe!")) else: # no user in session flash(Markup( f"<i class='far fa-sad-tear yellow-text'></i>\ You must be logged in to delete this recipe.")) return redirect(url_for("recipes.desserts"))
def profile(username): """ User Profile Page. Users can view their own recipes, view their saved recipes from others, update their password, or delete their account. """ username = get_user_lower(session["user"])["username"] # find all recipes belonging to user user = get_user_lower(session["user"])["_id"] user_recipes = recipes_collection.find({"author": user})\ .sort([("recipe_name", 1)]) # find all recipes that the user saved user_favs_list = get_user_lower(session["user"])["user_favs"] user_favs = recipes_collection.find({"_id": {"$in": user_favs_list}})\ .sort([("recipe_name", 1)]) # get the user's avatar user_avatar = get_user_lower(session["user"])["user_avatar"] # ADMIN-only: get list of all database users and visitors admin_list = users_collection.find().sort([("_id", 1)]) date_joined = [user["_id"].generation_time.date().strftime( "%d %b %Y") for user in deepcopy(admin_list)] zip_data = zip(admin_list, date_joined) visitors = list(visitors_collection.find()) # get total views and total views by grouped country total_views = list(visitors_collection.aggregate([ {"$facet": { "total": [ {"$group": {"_id": "", "total": {"$sum": "$visits"}}} ], "countries": [ {"$group": {"_id": "$country", "total": {"$sum": "$visits"}}} ] }} ])) # end ADMIN-ONLY section return render_template( "profile.html", username=username, user_recipes=user_recipes, user_favs=user_favs, user_avatar=user_avatar, admin_list=admin_list, date_joined=date_joined, zip_data=zip_data, visitors=visitors, total_views=total_views)
def logout(): """ Remove the user from 'Session' cookies with session.pop(). """ username = get_user_lower(session["user"])["username"] flash( Markup(f"<i class='far fa-sad-tear yellow-text'></i>\ Missing you already, <span class='pink-text text-lighten-2 bold'>" + username + "</span>!")) session.pop("user") return redirect(url_for("main.home"))
def profile_delete_account(username): """ Delete entire user account if desired. Each user has the option to delete their account. This would delete all of their recipes, too. """ user = get_user_lower(session["user"]) # check if stored hashed password matches current password in form if check_password_hash(user["user_password"], request.form.get("verify_password")): # find all recipes belonging to user user_recipes = [recipe for recipe in user.get("user_recipes")] # for each user recipe, remove from database and pull from others' favs for recipe in user_recipes: recipes_collection.remove({"_id": recipe}) users_collection.update_many({}, {"$pull": {"user_favs": recipe}}) # find all recipes that the user saved user_favs = [recipe for recipe in user.get("user_favs")] # decrement each recipe the user liked by -1 for recipe in user_favs: recipes_collection.update_one({"_id": recipe}, {"$inc": { "user_favs": -1 }}) flash( Markup(f"<i class='fas fa-user-times red-text'></i>\ Your account and recipes have been successfully deleted.")) # remove user from session cookies and delete user from database session.pop("user") users_collection.remove({"_id": user.get("_id")}) return redirect(url_for("main.home")) else: # password does not match, so redirect back to profile flash( Markup(f"<i class='fas fa-exclamation-circle red-text'></i>\ Whoops! Looks like your <span class='pink-text text-lighten-2'>\ password</span> is incorrect. Please try again.")) return redirect(url_for("users.profile", username=username))
def login(): """ Log In to user account. Authentication and Authorization checks that user exists, user hashed password matches, then logs in to account if all successful. """ if request.method == "POST": # check if username is in database existing_user = get_user_lower(request.form.get("username")) if existing_user: # ensure hashed password matches user input if check_password_hash(existing_user["user_password"], request.form.get("password")): session["user"] = request.form.get("username").lower() return redirect( url_for("users.profile", username=session["user"])) else: # invalid password match flash( Markup( f"<i class='fas fa-exclamation-circle red-text'></i>\ Whoops! Looks like the\ <span class='pink-text text-lighten-2'>username</span>\ or <span class='pink-text text-lighten-2'>password</span>\ is incorrect.")) return redirect(url_for("users.login")) else: # username doesn't exist flash( Markup(f"<i class='fas fa-exclamation-circle red-text'></i>\ Hmm... username <span class='pink-text text-lighten-2'>\ {request.form.get('username')}</span>\ doesn't seem to exist.")) return redirect(url_for("users.login")) return render_template("log_reg.html")
def register(): """ Register a new account. Authentication and Authorization checks that user does not already exist, username does not contain 'test' or most special characters, length of username and password, and assigns an avatar from a list of 16 avatars. """ if request.method == "POST": # check if username already taken existing_user = get_user_lower(request.form.get("new_username")) if existing_user: flash( Markup(f"<i class='fas fa-exclamation-circle red-text'>\ </i> <span class='pink-text text-lighten-2'>\ {request.form.get('new_username')}</span>\ is an excellent choice! (but it's already taken)")) return redirect(url_for("users.register")) # check if username is alphanumeric or contains 'test' username_input = request.form.get("new_username").lower() username_check = re.search(r"(?!\-)[\W]|t+e+s+t+", username_input, re.I) if username_check: if " " in {username_check.group(0)}: flash( Markup( f"<i class='fas fa-exclamation-circle red-text'></i>\ Usernames containing\ <span class='pink-text text-lighten-2'>spaces</span>\ are not permitted.")) return redirect(url_for("users.register")) else: flash( Markup( f"<i class='fas fa-exclamation-circle red-text'></i>\ Usernames containing\ <span class='pink-text text-lighten-2'>\ {username_check.group(0).upper()}</span>\ are not permitted.")) return redirect(url_for("users.register")) # username should be 3-5 alphanumeric if len(request.form.get("new_username")) < 3 or len( request.form.get("new_username")) > 15: flash( Markup(f"<i class='fas fa-exclamation-circle red-text'></i>\ Usernames should be <span class='pink-text text-lighten-2'>\ 3-15 characters</span> long.")) return redirect(url_for("users.register")) # password should be 5-15 characters if len(request.form.get("new_password")) < 5 or len( request.form.get("new_password")) > 15: flash( Markup(f"<i class='fas fa-exclamation-circle red-text'></i>\ Passwords should be <span class='pink-text text-lighten-2'>\ 5-15 characters</span> long.")) return redirect(url_for("users.register")) # assign random avatar to user avatars = [ "birthday-cake", "cherry-cake", "cherry-flan", "flan", "ice-lolly-bear", "ice-lolly-panda", "lemon-pie", "macaroon-blue", "macaroon-green", "macaroon-pink", "mousse-pie", "neapolitan-torte", "raspberry-cheesecake", "raspberry-chocolate-cream-cake", "strawberry-cream-pie", "tiramisu-mousse" ] user_avatar = random.choice(avatars) # add successful user to database register = { "username": request.form.get("new_username"), "username_lower": request.form.get("new_username").lower(), "user_password": generate_password_hash(request.form.get("new_password")), "user_avatar": user_avatar, "user_recipes": [], "user_favs": [] } users_collection.insert_one(register) # put the user in 'session' session["user"] = request.form.get("new_username").lower() return redirect(url_for("users.profile", username=session["user"])) return render_template("log_reg.html")
def desserts_new(): """ Create recipe for database. Inject all form data to new recipe document on submit. """ if request.method == "GET": allergen_list = dropdown_allergens() dessert_list = dropdown_dessert_type() measurement_list = dropdown_measurement() return render_template( "desserts_new.html", allergens=allergen_list, desserts=dessert_list, measurements=measurement_list) if request.method == "POST": # get today's date and date recipe was last edited today = datetime.now().strftime("%d %B, %Y") last_edit = int(datetime.now().strftime("%Y%m%d")) # get user / author details session_user = get_user_lower(session["user"])["username"] author = users_collection.find_one({"username": session_user})["_id"] # get and convert total time hours = int(request.form.get( "total_hrs")) * 60 if request.form.get( "total_hrs") else "" total_time = int(request.form.get( "total_mins")) + hours if hours else int(request.form.get( "total_mins")) # slugify url to be user-friendly slugUrl = slugify(request.form.get("recipe_name")) # get form data prior to submitting submit = { "recipe_name": request.form.get("recipe_name"), "recipe_slug": slugUrl, "description": request.form.get("description"), "dessert_type": request.form.get("dessert_type"), "ingredient_amount": request.form.getlist("ingredient_amount"), "ingredient_measurement": request.form.getlist( "ingredient_measurement"), "ingredient_name": request.form.getlist("ingredient_name"), "directions": request.form.getlist("directions"), "total_hrs": request.form.get("total_hrs"), "total_mins": request.form.get("total_mins"), "total_time": total_time, "allergens": request.form.getlist("allergens"), "img_src": request.form.get("img_src"), "author": author, "date_added": today, "date_updated": today, "last_edit": last_edit, "views": 0, "user_favs": 0 } # get the new _id being created on submit newID = recipes_collection.insert_one(submit) # add recipe _id to user's recipe list users_collection.update_one( {"_id": ObjectId(author)}, {"$push": {"user_recipes": newID.inserted_id}}) flash(Markup( f"<i class='far fa-check-circle green-text'></i>\ Sounds delicious! Thanks for adding this recipe!")) # if selected, add recipe to user-favs as well if request.form.get("add_favs"): users_collection.update_one( {"_id": ObjectId(author)}, {"$push": {"user_favs": newID.inserted_id}}) # increase number of favorites on this recipe by +1 recipes_collection.update_one( {"_id": newID.inserted_id}, {"$inc": {"user_favs": 1}}) # # send me an email when a recipe gets added (personal backup) # msg = MIMEMultipart() # msg["From"] = MY_ADDRESS # msg["To"] = SEND_TO # msg["Subject"] = "2BN-Desserts | New Recipe Added: " + slugUrl # formatEmail = "<br><br>".join(["%s: %s" % kv for kv in submit.items()]) # msg.attach(MIMEText(str(formatEmail), "html")) # must convert to str() # smtpserver = smtplib.SMTP("smtp.gmail.com", 587) # access server # smtpserver.ehlo() # identify ourselves to smtp gmail client # smtpserver.starttls() # secure our email with tls encryption # smtpserver.ehlo() # re-identify ourselves as an encrypted connection # smtpserver.login(MY_ADDRESS, PASSWORD) # login to the server # smtpserver.send_message(msg) # send the message # smtpserver.quit() # quit the server # add recipe to admin's profile as back-up (in lieu of email) users_collection.update_one( {"username": "******"}, {"$push": {"new_recipes": submit}}) return redirect(url_for( "recipes.desserts_recipe", recipe_id=newID.inserted_id, slugUrl=slugUrl))
def desserts_recipe(recipe_id, slugUrl): """ Read recipe in database. Gather details from document for displaying to user. """ recipe = get_recipe(recipe_id) author = users_collection.find_one( {"_id": ObjectId(recipe.get("author"))})["username"] user_avatar = users_collection.find_one( {"_id": ObjectId(recipe.get("author"))})["user_avatar"] amounts = recipe.get("ingredient_amount") measurements = recipe.get("ingredient_measurement") ingredients = recipe.get("ingredient_name") amount = [] measurement = [] units = [] """ if amount contains valid unicode fraction, convert it (ie: input 1/2 converts to ½ for display) """ fractions = ( ["1/2", "1/3", "1/4", "1/5", "1/6", "1/8", "2/3", "2/5", "3/4", "3/5", "3/8", "4/5", "5/6", "5/8", "7/8"]) for num in amounts: if "/" in num: if any(frac in num for frac in fractions): frac_match = re.match(r"^(.*?)(\d\/\d)(.*?)$", num) new_num = ( frac_match.group(1) + "&frac" + re.sub("/", "", frac_match.group(2)) + ";" + frac_match.group(3)) amount.append(html.unescape(new_num)) else: amount.append(num) else: amount.append(num) # only display the abbreviated unit of measurement for unit in measurements: units.append(unit) match = re.search(r"\(([a-zA-Z]+)\)$", unit) if match: measurement.append(match.group(1)) else: measurement.append(unit) # zip ingredient items into single full_ingredient full_ingredient = zip(amount, measurement, ingredients) # get user favorites (if available) try: user_favs = get_user_lower(session["user"])["user_favs"] except: user_favs = [] # increment number of views by +1 recipes_collection.update_one( {"_id": ObjectId(recipe_id)}, {"$inc": {"views": 1}}) """ Display Recommendations: Get the Previous and Next recipes in collection to display. If currently on last document, then get the first document for display. If currently on first document, then get the last document for display. """ first_recipe = recipes_collection.find().sort("_id", 1).limit(1) last_recipe = recipes_collection.find().sort("_id", -1).limit(1) previous_recipe = recipes_collection.find( {"_id": {"$lt": ObjectId(recipe_id)}})\ .sort([("_id", -1)])\ .limit(1) if str(recipe_id) != str(first_recipe[0]["_id"])\ else last_recipe next_recipe = recipes_collection.find( {"_id": {"$gt": ObjectId(recipe_id)}})\ .sort([("_id", 1)])\ .limit(1) if str(recipe_id) != str(last_recipe[0]["_id"])\ else first_recipe return render_template( "desserts_recipe.html", recipe=recipe, full_ingredient=full_ingredient, units=units, author=author, user_favs=user_favs, user_avatar=user_avatar, previous_recipe=previous_recipe, next_recipe=next_recipe)
def desserts(): """ Read all recipes from database. Display all recipes initially, with option to Search. Search function works by reading url args for existing data. """ # show correct authors on cards authors = [author for author in users_collection.find({}, {"username": 1})] # dropdowns for search allergen_list = dropdown_allergens() dessert_list = dropdown_dessert_type() # arg variables args = request.args.get args_list = request.args.getlist # URL args : search / sort / order / pagination search_keyword_args = args(str( "search_keyword")) if args(str( "search_keyword")) else "" search_dessert_args = args(str( "search_dessert")) if args(str( "search_dessert")) else "" search_allergen_args = args_list( "search_allergen") if args_list( "search_allergen") else [] sort_args = args(str("sort")) if args(str("sort")) else "recipe_name" order_args = int(args("order")) if args("order") else 1 page_args = int(args("page")) if args("page") else 1 limit_args = int(args("limit")) if args( "limit") else int(args("limit")) if args("limit") else 12 # prepare form data for searching search_keyword = ( search_keyword_args.split() if search_keyword_args is not None else "") search_dessert = ( search_dessert_args if search_dessert_args is not None else "") search_allergen = ( search_allergen_args if search_allergen_args != [] else "") # pagination settings for sorting all_recipes_count = ( range(1, (math.ceil(recipes_collection.count() / limit_args)) + 1)) all_recipes_pages = [page for page in all_recipes_count] previous_page = page_args - 1 if page_args != 1 else 1 next_page = ( page_args + 1 if page_args < all_recipes_pages[-1] else page_args) # show results - without search sorting = recipes_collection.find().sort( [(sort_args, order_args)])\ .skip((page_args * limit_args) - limit_args)\ .limit(limit_args) # string search items together and search new_search = ( '"' + '" "'.join(search_keyword) + '" "' + ''.join(search_dessert) + '"' + ' -' + ' -'.join(search_allergen)) if not search_keyword and not search_dessert and not search_allergen: search_results = "" else: if not args("limit"): # get all results on single page if user selects 'All' search_results = recipes_collection.find( {"$text": {"$search": new_search}})\ .sort([(sort_args, order_args)]) else: # otherwise, get the limit they've selected, or the default of 12 search_results = recipes_collection.find( {"$text": {"$search": new_search}})\ .sort([(sort_args, order_args)])\ .skip((page_args * limit_args) - limit_args)\ .limit(limit_args) # get search results count results_count = search_results.count() if search_results else "" # pagination for search search_recipes_count = ( range(1, (math.ceil(int( results_count) / limit_args)) + 1) if results_count else "") search_recipes_pages = ([ page for page in search_recipes_count] if search_recipes_count else "") # get the next page variables if not search_recipes_pages or search_recipes_pages == []: next_page_search = "" else: next_page_search = ( page_args + 1 if page_args < search_recipes_pages[-1] else page_args) # get total of recipes to display per page # (without search) count_display = ( page_args * limit_args if ( page_args * limit_args) < sorting.count() else sorting.count()) # (with search) if not search_results: count_display_search = "" else: count_display_search = ( page_args * limit_args if ( page_args * limit_args) < search_results.count( ) else search_results.count()) """ Get visitor's IP and Location for Admin tracking. Get last item in 'X-Forwarded-For' list to avoid getting the Heroku server IP address instead https://stackoverflow.com/a/37061471 """ # https://stackoverflow.com/a/35123097 (excellent!!) # http://httpbin.org/ip | http://icanhazip.com # https://ipapi.co/json/ or https://ipapi.co/<ip>/json/ (10k/mo) # https://ipinfo.io/json or https://ipinfo.io/<ip>/json (1k/day) # check if guest or registered user username = get_user_lower( session["user"])["username"] if "user" in session else "guest" if os.environ.get("DEVELOPMENT"): # local development url = "https://ipapi.co/json/" response = requests.get(url).json() client_ip = response["ip"] else: # live server on Heroku client_ip = request.access_route[-1] url = "https://ipapi.co/" + client_ip + "/json/" url2 = "https://ipinfo.io/" + client_ip + "/json" response = requests.get(url).json() if response: datetimenow = datetime.now().strftime("%d %B, %Y @ %H:%M") pattern = "^\-?[0-9]*\.?[0-9]*$" lat = str(response["latitude"]) lon = str(response["longitude"]) if bool( re.match(rf"{pattern}", lat)) and bool( re.match(rf"{pattern}", lon)): latitude = lat longitude = lon proceed = True else: response2 = requests.get(url2).json() if response2: latitude = str(response2["loc"].split(",")[0]) longitude = str(response2["loc"].split(",")[1]) proceed = True # check if existing ip visitor already exists if proceed and visitors_collection.count_documents( {"ip": client_ip}, limit=1) == 0: visitor = { "ip": client_ip, "username": username, "city": response["city"], "region": response["region"], "country": response["country_name"], "iso2": response["country_code"].lower(), "iso3": response["country_code_iso3"].lower(), "latitude": latitude, "longitude": longitude, "timezone": response["timezone"], "utc_offset": response["utc_offset"], "datetime": [datetimenow], "visits": 1 } visitors_collection.insert_one(visitor) elif proceed: # update username from guest to session user if logged in user = visitors_collection.find_one({"ip": client_ip})["username"] username = user if user != "guest" else username # if existing visitor ip, then increment their view count visitors_collection.update_one( {"ip": client_ip}, {"$push": {"datetime": datetimenow}, "$set": {"username": username}, "$inc": {"visits": 1}}) # render results on page and pass all data to template return render_template( "desserts.html", recipes_start=sorting, recipes_search=search_results, authors=authors, allergens=allergen_list, desserts=dessert_list, search_keyword_args=search_keyword_args, search_dessert_args=search_dessert_args, search_allergen_args=search_allergen_args, sort_args=sort_args, order_args=order_args, limit_args=limit_args, all_recipes_pages=all_recipes_pages, search_recipes_pages=search_recipes_pages, previous_page=previous_page, next_page=next_page, next_page_search=next_page_search, results_count=results_count, page_args=page_args, count_display=count_display, count_display_search=count_display_search)
def desserts_new(): """ Create recipe for database. Inject all form data to new recipe document on submit. """ if request.method == "GET": allergen_list = dropdown_allergens() dessert_list = dropdown_dessert_type() measurement_list = dropdown_measurement() return render_template("desserts_new.html", allergens=allergen_list, desserts=dessert_list, measurements=measurement_list) if request.method == "POST": # get today's date and date recipe was last edited today = datetime.now().strftime("%d %B, %Y") last_edit = int(datetime.now().strftime("%Y%m%d")) # get user / author details session_user = get_user_lower(session["user"])["username"] author = users_collection.find_one({"username": session_user})["_id"] # get and convert total time hours = int(request.form.get("total_hrs")) * 60 if request.form.get( "total_hrs") else "" total_time = int( request.form.get("total_mins")) + hours if hours else int( request.form.get("total_mins")) # slugify url to be user-friendly slugUrl = slugify(request.form.get("recipe_name")) # get form data prior to submitting submit = { "recipe_name": request.form.get("recipe_name"), "recipe_slug": slugUrl, "description": request.form.get("description"), "dessert_type": request.form.get("dessert_type"), "ingredient_amount": request.form.getlist("ingredient_amount"), "ingredient_measurement": request.form.getlist("ingredient_measurement"), "ingredient_name": request.form.getlist("ingredient_name"), "directions": request.form.getlist("directions"), "total_hrs": request.form.get("total_hrs"), "total_mins": request.form.get("total_mins"), "total_time": total_time, "allergens": request.form.getlist("allergens"), "img_src": request.form.get("img_src"), "author": author, "date_added": today, "date_updated": today, "last_edit": last_edit, "views": 0, "user_favs": 0 } # get the new _id being created on submit newID = recipes_collection.insert_one(submit) # add recipe _id to user's recipe list users_collection.update_one( {"_id": ObjectId(author)}, {"$push": { "user_recipes": newID.inserted_id }}) flash( Markup(f"<i class='far fa-check-circle green-text'></i>\ Sounds delicious! Thanks for adding this recipe!")) # if selected, add recipe to user-favs as well if request.form.get("add_favs"): users_collection.update_one( {"_id": ObjectId(author)}, {"$push": { "user_favs": newID.inserted_id }}) # increase number of favorites on this recipe by +1 recipes_collection.update_one({"_id": newID.inserted_id}, {"$inc": { "user_favs": 1 }}) return redirect( url_for("recipes.desserts_recipe", recipe_id=newID.inserted_id, slugUrl=slugUrl))