This repo is for my Python Live Project that I did as a student with The Tech Academy. Over the course of a two-week code sprint, I worked on a development team where we were each tasked with creating a database-focused web app within the Django framework. For my app, I chose to create a recipe app that primarily tracked desserts.
I was assigned the task of creating a website that would allow a user to view all available recipes within a database and view the details of any specific recipe of their choosing. Additionally, the user should be able to create new recipes, edit existing recipes, and delete recipes in a database. I also implemented other features including a search page that searches by category using an API and displays the results to the user, and a recipe scraper page that scrapes content from a chosen recipe site using the Beautiful Soup library and then displays those results to the user.
- Create Recipes
- Display Recipes
- Update Recipes
- Delete Recipes
- Scrape Recipes
- Search For Recipes
- Conclusion
- Create the basic Django App and register in main project settings.py
- Create the base, home, and navbar templates
- Create functions to render all templates in views.py
- Register all urls
- Apply basic styling to base and homepage templates using Bootstrap and CSS
- Create Recipe model with all applicable categories
- Create a model form that includes all applicable inputs required from user
- Create Add Recipe Template and register url
- Add views function to render the create page that utilizes the model form to save item to database
- Add basic styling to template using Bootstrap, CSS, and Crispy Forms
# render add_recipe page
def add_recipe(request):
form = RecipeForm(data=request.POST or None)
if request.method == 'POST':
if form.is_valid():
form.save()
return redirect('desserts_displayDb')
content = {'form': form}
return render(request, 'Desserts/desserts_add_recipe.html', content)
- Create Display Recipe database template and register url
- Add views function that gets all items from the database and displays the results on rendered template with appropriate labels/headers
- Create a Recipe Details template and register url
- Create a views function that will find a single item from the database and render the details on the Details Page template
- Add in a link for each item on the Display Recipes page that points to the details page for that item
- Add basic styling to both templates using Bootstrap and CSS
# render display_Db page, display all recipes in database
def display_recipe_items(request):
recipe_db = Recipe.Recipes.all()
content = {'recipe_db': recipe_db}
return render(request, 'Desserts/desserts_displayDb.html', content)
# render desserts_details page, display details of any single recipe in the database
def recipe_details(request, pk):
details = get_object_or_404(Recipe, pk=int(pk))
content = {'details': details}
return render(request, 'Desserts/desserts_details.html', content)
- Create Edit Recipe page to templates and register url
- Create edit_recipe function that displays the content from a single item in the database and allows the user to modify the existing content using model forms and instances, and then saves the recipe back to the database
- Add basic styling using Bootstrap, CSS, and Crispy Forms
- Template
- View:
def edit_recipe(request, pk):
item = get_object_or_404(Recipe, pk=int(pk)) # the recipe we want to modify
form = RecipeForm(data=request.POST or None, instance=item) # create form instance and bind data to it
if request.method == 'POST':
if form.is_valid():
form.save()
return redirect('desserts_displayDb') # return to database list
else:
print(form.errors)
content = {'form': form}
return render(request, 'Desserts/desserts_edit.html', content)
- Create Delete Recipe page to templates and register url
- Create delete_recipe function that deletes a single item in the database and saves the changes back to the database
- Create a modal in the Delete Recipe template that presents the user with a delete confirmation before the item is permanently removed from the database
- Add basic styling using Bootstrap and CSS
- Template
- View:
# render desserts_delete page, save to database
def delete_recipe(request, pk):
item = get_object_or_404(Recipe, pk=int(pk)) # the recipe we want to delete
form = RecipeForm(data=request.POST or None, instance=item) # create form instance and bind data to it
if request.method == 'POST':
item.delete()
return redirect('desserts_displayDb') # return to database list
content = {
'item': item,
'form': form,
}
return render(request, 'Desserts/desserts_delete.html', content)
- Create template to display information scraped from Spoon Fork Bacon, register url
- Create scrape_desserts function to scrape data from source site using Beautiful Soup and Requests libraries
- Use Beautiful Soup to parse HTML content then iterate through specified elements to extract chosen content (name, description, and recipe url)
- Append extracted data to corresponding lists, zip all lists together into dictionary
- Bind dictionary to context, send to rendered web scraping template
- Display all objects extracted in table on rendered template
- Create clickable links for each recipe that lead to details page on source site
- Add basic styling using Bootstrap and CSS
- Template
- Scraping Source
- View:
# scrape recipe data from external recipe page, package up and send to template to be rendered
def scrape_desserts(request):
names = [] # recipe name list
descriptions = [] # recipe description list
recipe_urls = [] # recipe_url list
url = 'https://www.spoonforkbacon.com/category/dessert-recipes/' # page to scrape data from
page = requests.get(url)
soup = BeautifulSoup(page.content, 'html.parser')
parental_soup = soup.find_all('article', class_="category-dessert-recipes") # parent to search
for i in parental_soup: # iterate through parental_soup through each article tag
name = i.h2.a.text # extract text from h2 hyperlink text as recipe name
description = i.div.p.text # extract text from first paragraph tag in the div inside parental soup
recipe_url = i.find('a')['href'] # extract recipe urls
names.append(name) # append name to names list
descriptions.append(description) # append description to descriptions list
recipe_urls.append(recipe_url) # append recipe urls to recipe_urls list
zipped_list = zip(names, descriptions, recipe_urls) # zip all extracted lists together
context = {'zipped_list': zipped_list} # bind zipped_list dictionary to context
return render(request, 'Desserts/desserts_bs.html', context)
- Create API search page template and register url
- Create category_search function to connect to API and render query response to template
- Use GET request to get category selection from user through form on search page template
- Append category to url, before connecting to API endpoint
- Request API response
- Parse results using JSON
- Iterate through parsed data to extract desired data and append to results_list
- Bind results_list and form to context, send to rendered API search page template
- Display all objects extracted on rendered template
- Create clickable links for each recipe that lead to details page
- Add basic styling using Bootstrap and CSS
- Template
- Cooking Recipe API
- View:
# search recipes by category using API, headers and key imported from creds
def category_search(request):
results_list = [] # store results in list
context = {}
form = SearchForm()
context['form'] = form
if request.GET:
temp = request.GET['category_type'] # get category type from template form, store in temp variable
category = temp.replace(' ', '%20') # prepare string for url, replace space with url encoded space '%20'
url = "https://cooking-recipe2.p.rapidapi.com/getbycat/{}".format(category) # api url, append category
response = requests.request("GET", url, headers=headers) # request API response
parsed_results = json.loads(response.text) # parse the results
for recipe in parsed_results: # iterate through parsed data, pull out the pieces we want
results = {
'name': recipe['title'], # get recipe name
'category': recipe['category'], # get recipe category
'recipe_url': recipe['url'], # get source url
'image': recipe['img'] # get image url
}
results_list.append(results) # append results to list
context = {'form': form, 'results_list': results_list} # package form and results_list in context
return render(request, 'Desserts/desserts_category_search.html', context)
Over the course of this project, I increased my knowledge and gained new coding and noncoding skills. I learned what it's like to work on a developer team in a professional setting.
Some of the new coding skills I developed during this project are:
- Effectively using the Beautiful Soup library to parse data and navigate the website’s data structure for web-scraping
- Utilizing API endpoints, Requests library, and JSON
Some of the non-coding related skills that I developed are:
-
Using Agile and Scrum methodologies:
- Attended a sprint planning session where we discussed what the project expectations were and how we would accomplish those expectations
- Attended daily standups where we discussed what we each worked on previously, what out workplans for the day were, and any roadblocks that we had that were impeding progress
- Attending weekly coding retrospectives where we discussed what was helpful and what was detrimental to productivity and team workflow
-
Using Azure DevOps to manage workflow and repos
-
Effectively using version control to make commits, merges, push/pulls while being mindful of minimizing merge conflicts