Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
vabene1111 committed Dec 30, 2021
2 parents aaaae5b + c50a89c commit af9a2a8
Show file tree
Hide file tree
Showing 18 changed files with 162 additions and 27 deletions.
4 changes: 4 additions & 0 deletions .env.template
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ SQL_DEBUG=0
ALLOWED_HOSTS=*

# random secret key, use for example `base64 /dev/urandom | head -c50` to generate one
# ---------------------------- REQUIRED -------------------------
SECRET_KEY=
# ---------------------------------------------------------------

# your default timezone See https://timezonedb.com/time-zones for a list of timezones
TIMEZONE=Europe/Berlin
Expand All @@ -18,7 +20,9 @@ DB_ENGINE=django.db.backends.postgresql
POSTGRES_HOST=db_recipes
POSTGRES_PORT=5432
POSTGRES_USER=djangouser
# ---------------------------- REQUIRED -------------------------
POSTGRES_PASSWORD=
# ---------------------------------------------------------------
POSTGRES_DB=djangodb

# database connection string, when used overrides other database settings.
Expand Down
2 changes: 1 addition & 1 deletion cookbook/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ class ViewLogAdmin(admin.ModelAdmin):

class InviteLinkAdmin(admin.ModelAdmin):
list_display = (
'group', 'valid_until',
'group', 'valid_until', 'space',
'created_by', 'created_at', 'used_by'
)

Expand Down
4 changes: 2 additions & 2 deletions cookbook/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ def ready(self):
with scopes_disabled():
try:
from cookbook.models import Keyword, Food
Keyword.fix_tree(fix_paths=True)
Food.fix_tree(fix_paths=True)
#Keyword.fix_tree(fix_paths=True) # disabled for now, causes to many unknown issues
#Food.fix_tree(fix_paths=True)
except OperationalError:
if DEBUG:
traceback.print_exc()
Expand Down
3 changes: 2 additions & 1 deletion cookbook/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,13 +152,14 @@ class ImportExportBase(forms.Form):
OPENEATS = 'OPENEATS'
PLANTOEAT = 'PLANTOEAT'
COOKBOOKAPP = 'COOKBOOKAPP'
COPYMETHAT = 'COPYMETHAT'

type = forms.ChoiceField(choices=(
(DEFAULT, _('Default')), (PAPRIKA, 'Paprika'), (NEXTCLOUD, 'Nextcloud Cookbook'),
(MEALIE, 'Mealie'), (CHOWDOWN, 'Chowdown'), (SAFRON, 'Safron'), (CHEFTAP, 'ChefTap'),
(PEPPERPLATE, 'Pepperplate'), (RECETTETEK, 'RecetteTek'), (RECIPESAGE, 'Recipe Sage'), (DOMESTICA, 'Domestica'),
(MEALMASTER, 'MealMaster'), (REZKONV, 'RezKonv'), (OPENEATS, 'Openeats'), (RECIPEKEEPER, 'Recipe Keeper'),
(PLANTOEAT, 'Plantoeat'), (COOKBOOKAPP, 'CookBookApp'),
(PLANTOEAT, 'Plantoeat'), (COOKBOOKAPP, 'CookBookApp'), (COPYMETHAT, 'CopyMeThat'),
))


Expand Down
11 changes: 6 additions & 5 deletions cookbook/helper/recipe_html_import.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import json
import re
from json import JSONDecodeError
from urllib.parse import unquote

from bs4 import BeautifulSoup
from bs4.element import Tag
from recipe_scrapers._utils import get_host_name, normalize_string

from cookbook.helper import recipe_url_import as helper
from cookbook.helper.scrapers.scrapers import text_scraper
from json import JSONDecodeError
from recipe_scrapers._utils import get_host_name, normalize_string
from urllib.parse import unquote


def get_recipe_from_source(text, url, request):
Expand Down Expand Up @@ -58,7 +59,7 @@ def get_children_list(children):
return kid_list

recipe_json = {
'name': '',
'name': '',
'url': '',
'description': '',
'image': '',
Expand Down Expand Up @@ -188,6 +189,6 @@ def remove_graph(el):
for x in el['@graph']:
if '@type' in x and x['@type'] == 'Recipe':
el = x
except TypeError:
except (TypeError, JSONDecodeError):
pass
return el
84 changes: 84 additions & 0 deletions cookbook/integration/copymethat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import re
from io import BytesIO
from zipfile import ZipFile

from bs4 import BeautifulSoup

from cookbook.helper.ingredient_parser import IngredientParser
from cookbook.helper.recipe_html_import import get_recipe_from_source
from cookbook.helper.recipe_url_import import iso_duration_to_minutes, parse_servings
from cookbook.integration.integration import Integration
from cookbook.models import Recipe, Step, Ingredient, Keyword
from recipes.settings import DEBUG


class CopyMeThat(Integration):

def import_file_name_filter(self, zip_info_object):
if DEBUG:
print("testing", zip_info_object.filename, zip_info_object.filename == 'recipes.html')
return zip_info_object.filename == 'recipes.html'

def get_recipe_from_file(self, file):
# 'file' comes is as a beautifulsoup object
recipe = Recipe.objects.create(name=file.find("div", {"id": "name"}).text.strip(), created_by=self.request.user, internal=True, space=self.request.space, )

for category in file.find_all("span", {"class": "recipeCategory"}):
keyword, created = Keyword.objects.get_or_create(name=category.text, space=self.request.space)
recipe.keywords.add(keyword)

try:
recipe.servings = parse_servings(file.find("a", {"id": "recipeYield"}).text.strip())
recipe.working_time = iso_duration_to_minutes(file.find("span", {"meta": "prepTime"}).text.strip())
recipe.waiting_time = iso_duration_to_minutes(file.find("span", {"meta": "cookTime"}).text.strip())
recipe.save()
except AttributeError:
pass

step = Step.objects.create(instruction='', space=self.request.space, )

ingredient_parser = IngredientParser(self.request, True)
for ingredient in file.find_all("li", {"class": "recipeIngredient"}):
if ingredient.text == "":
continue
amount, unit, ingredient, note = ingredient_parser.parse(ingredient.text.strip())
f = ingredient_parser.get_food(ingredient)
u = ingredient_parser.get_unit(unit)
step.ingredients.add(Ingredient.objects.create(
food=f, unit=u, amount=amount, note=note, space=self.request.space,
))

for s in file.find_all("li", {"class": "instruction"}):
if s.text == "":
continue
step.instruction += s.text.strip() + ' \n\n'

for s in file.find_all("li", {"class": "recipeNote"}):
if s.text == "":
continue
step.instruction += s.text.strip() + ' \n\n'

try:
if file.find("a", {"id": "original_link"}).text != '':
step.instruction += "\n\nImported from: " + file.find("a", {"id": "original_link"}).text
step.save()
except AttributeError:
pass

recipe.steps.add(step)

# import the Primary recipe image that is stored in the Zip
try:
for f in self.files:
if '.zip' in f['name']:
import_zip = ZipFile(f['file'])
self.import_recipe_image(recipe, BytesIO(import_zip.read(file.find("img", class_="recipeImage").get("src"))), filetype='.jpeg')
except Exception as e:
print(recipe.name, ': failed to import image ', str(e))

recipe.save()
return recipe

def split_recipe_file(self, file):
soup = BeautifulSoup(file, "html.parser")
return soup.find_all("div", {"class": "recipe"})
13 changes: 11 additions & 2 deletions cookbook/integration/integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from io import BytesIO, StringIO
from zipfile import ZipFile, BadZipFile

from bs4 import Tag
from django.core.exceptions import ObjectDoesNotExist
from django.core.files import File
from django.db import IntegrityError
Expand All @@ -16,7 +17,7 @@
from cookbook.forms import ImportExportBase
from cookbook.helper.image_processing import get_filetype, handle_image
from cookbook.models import Keyword, Recipe
from recipes.settings import DATABASES, DEBUG
from recipes.settings import DEBUG


class Integration:
Expand Down Expand Up @@ -153,9 +154,17 @@ def do_import(self, files, il, import_duplicates):
file_list.append(z)
il.total_recipes += len(file_list)

import cookbook
if isinstance(self, cookbook.integration.copymethat.CopyMeThat):
file_list = self.split_recipe_file(BytesIO(import_zip.read('recipes.html')))
il.total_recipes += len(file_list)

for z in file_list:
try:
recipe = self.get_recipe_from_file(BytesIO(import_zip.read(z.filename)))
if isinstance(z, Tag):
recipe = self.get_recipe_from_file(z)
else:
recipe = self.get_recipe_from_file(BytesIO(import_zip.read(z.filename)))
recipe.keywords.add(self.keyword)
il.msg += f'{recipe.pk} - {recipe.name} \n'
self.handle_duplicates(recipe, import_duplicates)
Expand Down
2 changes: 1 addition & 1 deletion cookbook/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
</button>

{% if not request.user.is_authenticated or request.user.userpreference.theme == request.user.userpreference.TANDOOR %}
<a class="navbar-brand p-0 me-2 justify-content-center" href="/" aria-label="Tandoor">
<a class="navbar-brand p-0 me-2 justify-content-center" href="{% base_path request 'base' %}" aria-label="Tandoor">
<img class="brand-icon" src="{% static 'assets/brand_logo.png' %}" alt="" style="height: 5vh;">
</a>
{% endif %}
Expand Down
1 change: 1 addition & 0 deletions cookbook/templates/url_import.html
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ <h2> {% trans 'Import' %}</h2>
<option value="CHEFTAP">Cheftap</option>
<option value="CHOWDOWN">Chowdown</option>
<option value="COOKBOOKAPP">CookBookApp</option>
<option value="COPYMETHAT">CopyMeThat</option>
<option value="DOMESTICA">Domestica</option>
<option value="MEALIE">Mealie</option>
<option value="MEALMASTER">Mealmaster</option>
Expand Down
3 changes: 3 additions & 0 deletions cookbook/views/import_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from cookbook.forms import ExportForm, ImportForm, ImportExportBase
from cookbook.helper.permission_helper import group_required
from cookbook.integration.cookbookapp import CookBookApp
from cookbook.integration.copymethat import CopyMeThat
from cookbook.integration.pepperplate import Pepperplate
from cookbook.integration.cheftap import ChefTap
from cookbook.integration.chowdown import Chowdown
Expand Down Expand Up @@ -65,6 +66,8 @@ def get_integration(request, export_type):
return Plantoeat(request, export_type)
if export_type == ImportExportBase.COOKBOOKAPP:
return CookBookApp(request, export_type)
if export_type == ImportExportBase.COPYMETHAT:
return CopyMeThat(request, export_type)


@group_required('user')
Expand Down
5 changes: 4 additions & 1 deletion cookbook/views/new.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,10 @@ class InviteLinkCreate(GroupRequiredMixin, CreateView):
def form_valid(self, form):
obj = form.save(commit=False)
obj.created_by = self.request.user
obj.space = self.request.space

# verify given space is actually owned by the user creating the link
if obj.space.created_by != self.request.user:
obj.space = self.request.space
obj.save()
if obj.email:
try:
Expand Down
5 changes: 5 additions & 0 deletions docs/features/import_export.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ Overview of the capabilities of the different integrations.
| OpenEats | ✔️ |||
| Plantoeat | ✔️ |||
| CookBookApp | ✔️ || ✔️ |
| CopyMeThat | ✔️ || ✔️ |

✔ = implemented, ❌ = not implemented and not possible/planned, ⌚ = not yet implemented

Expand Down Expand Up @@ -218,3 +219,7 @@ Plan to eat allows you to export a text file containing all your recipes. Simply
## CookBookApp

CookBookApp can export .zip files containing .html files. Upload the entire ZIP to Tandoor to import all included recipes.

## CopyMeThat

CopyMeThat can export .zip files containing an `.html` file as well as a folder containing all the images. Upload the entire ZIP to Tandoor to import all included recipes.
24 changes: 23 additions & 1 deletion docs/install/docker.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ There are different versions (tags) released on docker hub.
The main, and also recommended, installation option is to install this application using Docker Compose.

1. Choose your `docker-compose.yml` from the examples below.
2. Download the `.env` configuration file with `wget`, then **edit it accordingly**.
2. Download the `.env` configuration file with `wget`, then **edit it accordingly** (you NEED to set `SECRET_KEY` and `POSTGRES_PASSWORD`).
```shell
wget https://raw.githubusercontent.com/vabene1111/recipes/develop/.env.template -O .env
```
Expand Down Expand Up @@ -111,6 +111,28 @@ wget https://raw.githubusercontent.com/vabene1111/recipes/develop/docs/install/d
{% include "./docker/nginx-proxy/docker-compose.yml" %}
~~~

#### Nginx Swag by LinuxServer
[This container](https://github.com/linuxserver/docker-swag) is an all in one solution created by LinuxServer.io.

It contains templates for popular apps, including Tandoor Recipes, so you don't have to manually configure nginx and discard the template provided in Tandoor repo. Tandoor config is called `recipes.subdomain.conf.sample` which you can adapt for your instance.

If you're running Swag on the default port, you'll just need to change the container name to yours.

If your running Swag on a custom port, some headers must be changed:

- Create a copy of `proxy.conf`
- Replace `proxy_set_header X-Forwarded-Host $host;` and `proxy_set_header Host $host;` to
- `proxy_set_header X-Forwarded-Host $http_host;` and `proxy_set_header Host $http_host;`
- Update `recipes.subdomain.conf` to use the new file
- Restart the linuxserver/swag container and Recipes will work correctly

More information [here](https://github.com/TandoorRecipes/recipes/issues/959#issuecomment-962648627).


In both cases, also make sure to mount `/media/` in your swag container to point to your Tandoor Recipes Media directory.

Please refer to the [appropriate documentation](https://github.com/linuxserver/docker-swag#usage) for the container setup.

## Additional Information

### Nginx vs Gunicorn
Expand Down
5 changes: 3 additions & 2 deletions recipes/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@

LOGIN_REDIRECT_URL = "index"
LOGOUT_REDIRECT_URL = "index"
ACCOUNT_LOGOUT_REDIRECT_URL = "index"

SESSION_EXPIRE_AT_BROWSER_CLOSE = False
SESSION_COOKIE_AGE = 365 * 60 * 24 * 60
Expand Down Expand Up @@ -163,12 +164,12 @@
AUTHENTICATION_BACKENDS = []

# LDAP
LDAP_AUTH=bool(os.getenv('LDAP_AUTH', False))
LDAP_AUTH = bool(os.getenv('LDAP_AUTH', False))
if LDAP_AUTH:
import ldap
from django_auth_ldap.config import LDAPSearch
AUTHENTICATION_BACKENDS.append('django_auth_ldap.backend.LDAPBackend')
AUTH_LDAP_SERVER_URI = os.getenv('AUTH_LDAP_SERVER_URI')
AUTH_LDAP_SERVER_URI = os.getenv('AUTH_LDAP_SERVER_URI')
AUTH_LDAP_BIND_DN = os.getenv('AUTH_LDAP_BIND_DN')
AUTH_LDAP_BIND_PASSWORD = os.getenv('AUTH_LDAP_BIND_PASSWORD')
AUTH_LDAP_USER_SEARCH = LDAPSearch(
Expand Down
2 changes: 1 addition & 1 deletion vue/src/apps/ModelListView/ModelListView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,7 @@ export default {
getRecipes: function (col, item) {
let parent = {}
// TODO: make this generic
let params = { pageSize: 50 }
let params = { pageSize: 50, random: true }
params[this.this_recipe_param] = item.id
console.log("RECIPE PARAM", this.this_recipe_param, params, item.id)
this.genericAPI(this.Models.RECIPE, this.Actions.LIST, params)
Expand Down
15 changes: 8 additions & 7 deletions vue/src/apps/RecipeEditView/RecipeEditView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,13 @@

<div class="col-md-6 mt-1">
<label for="id_name"> {{ $t('Preparation') }} {{ $t('Time') }} ({{ $t('min') }})</label>
<input class="form-control" id="id_prep_time" v-model="recipe.working_time">
<input class="form-control" id="id_prep_time" v-model="recipe.working_time" type="number">
<br/>
<label for="id_name"> {{ $t('Waiting') }} {{ $t('Time') }} ({{ $t('min') }})</label>
<input class="form-control" id="id_wait_time" v-model="recipe.waiting_time">
<input class="form-control" id="id_wait_time" v-model="recipe.waiting_time" type="number">
<br/>
<label for="id_name"> {{ $t('Servings') }}</label>
<input class="form-control" id="id_servings" v-model="recipe.servings">
<input class="form-control" id="id_servings" v-model="recipe.servings" type="number">
<br/>
<label for="id_name"> {{ $t('Servings') }} {{ $t('Text') }}</label>
<input class="form-control" id="id_servings_text" v-model="recipe.servings_text" maxlength="32">
Expand Down Expand Up @@ -343,7 +343,7 @@
</div>
<div class="small-padding"
v-bind:class="{ 'col-lg-4 col-md-6': !ingredient.is_header, 'col-lg-12 col-md-12': ingredient.is_header }">
<input class="form-control"
<input class="form-control" maxlength="256"
v-model="ingredient.note"
v-bind:placeholder="$t('Note')"
v-on:keydown.tab="event => {if(step.ingredients.indexOf(ingredient) === (step.ingredients.length -1)){event.preventDefault();addIngredient(step)}}">
Expand Down Expand Up @@ -623,9 +623,10 @@ export default {
this.sortIngredients(s)
}
if (this.recipe.waiting_time === ''){ this.recipe.waiting_time = 0}
if (this.recipe.working_time === ''){ this.recipe.working_time = 0}
if (this.recipe.servings === ''){ this.recipe.servings = 0}
if (this.recipe.waiting_time === '' || isNaN(this.recipe.waiting_time)){ this.recipe.waiting_time = 0}
if (this.recipe.working_time === ''|| isNaN(this.recipe.working_time)){ this.recipe.working_time = 0}
if (this.recipe.servings === ''|| isNaN(this.recipe.servings)){ this.recipe.servings = 0}
apiFactory.updateRecipe(this.recipe_id, this.recipe,
{}).then((response) => {
Expand Down
2 changes: 1 addition & 1 deletion vue/src/apps/RecipeSearchView/RecipeSearchView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ export default {
this.settings?.search_keywords?.length === 0 &&
this.settings?.search_foods?.length === 0 &&
this.settings?.search_books?.length === 0 &&
this.settings?.pagination_page === 1 &&
// this.settings?.pagination_page === 1 &&
!this.random_search &&
this.settings?.search_ratings === undefined
) {
Expand Down
Loading

0 comments on commit af9a2a8

Please sign in to comment.