Skip to content

Commit b1028f4

Browse files
committed
Merge branch 'develop'
2 parents 4e13fb3 + 6d0cc96 commit b1028f4

File tree

141 files changed

+10799
-10665
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

141 files changed

+10799
-10665
lines changed

.env.template

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ GUNICORN_MEDIA=0
9797
# ACCOUNT_EMAIL_SUBJECT_PREFIX=
9898

9999
# allow authentication via reverse proxy (e.g. authelia), leave off if you dont know what you are doing
100-
# see docs for more information https://vabene1111.github.io/recipes/features/authentication/
100+
# see docs for more information https://docs.tandoor.dev/features/authentication/
101101
# when unset: 0 (false)
102102
REVERSE_PROXY_AUTH=0
103103

@@ -126,7 +126,7 @@ REVERSE_PROXY_AUTH=0
126126
# ENABLE_METRICS=0
127127

128128
# allows you to setup OAuth providers
129-
# see docs for more information https://vabene1111.github.io/recipes/features/authentication/
129+
# see docs for more information https://docs.tandoor.dev/features/authentication/
130130
# SOCIAL_PROVIDERS = allauth.socialaccount.providers.github, allauth.socialaccount.providers.nextcloud,
131131

132132
# Should a newly created user from a social provider get assigned to the default space and given permission by default ?

.github/workflows/ci.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: Continuous Integration
22

3-
on: [push]
3+
on: [push, pull_request]
44

55
jobs:
66
build:
@@ -14,13 +14,13 @@ jobs:
1414
steps:
1515
- uses: actions/checkout@v1
1616
- name: Set up Python 3.10
17-
uses: actions/setup-python@v1
17+
uses: actions/setup-python@v4
1818
with:
1919
python-version: '3.10'
2020
# Build Vue frontend
21-
- uses: actions/setup-node@v2
21+
- uses: actions/setup-node@v3
2222
with:
23-
node-version: '14'
23+
node-version: '16'
2424
- name: Install Vue dependencies
2525
working-directory: ./vue
2626
run: yarn install
@@ -30,7 +30,7 @@ jobs:
3030
- name: Install Django dependencies
3131
run: |
3232
sudo apt-get -y update
33-
sudo apt-get install -y libsasl2-dev python-dev libldap2-dev libssl-dev
33+
sudo apt-get install -y libsasl2-dev python3-dev libldap2-dev libssl-dev
3434
python -m pip install --upgrade pip
3535
pip install -r requirements.txt
3636
python3 manage.py collectstatic --noinput

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,4 @@ cookbook/static/vue
8484
vue/webpack-stats.json
8585
cookbook/templates/sw.js
8686
.prettierignore
87+
vue/.yarn

.idea/recipes.iml

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

SECURITY.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@ Since this software is still considered beta/WIP support is always only given fo
66

77
## Reporting a Vulnerability
88

9-
Please open a normal public issue if you have any security related concerns. If you feel like the issue should not be discussed in public just open a generic issue and we will discuss further communication there (since GitHub does not allow everyone to create a security advisory :/).
9+
Please use GitHub Security Advisories to report any kind of security vulnerabilities.

cookbook/admin.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def delete_space_action(modeladmin, request, queryset):
3636

3737

3838
class SpaceAdmin(admin.ModelAdmin):
39-
list_display = ('name', 'created_by', 'max_recipes', 'max_users', 'max_file_storage_mb', 'allow_sharing')
39+
list_display = ('name', 'created_by', 'max_recipes', 'max_users', 'max_file_storage_mb', 'allow_sharing', 'use_plural')
4040
search_fields = ('name', 'created_by__username')
4141
list_filter = ('max_recipes', 'max_users', 'max_file_storage_mb', 'allow_sharing')
4242
date_hierarchy = 'created_at'

cookbook/forms.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ class ImportExportBase(forms.Form):
154154
COOKBOOKAPP = 'COOKBOOKAPP'
155155
COPYMETHAT = 'COPYMETHAT'
156156
COOKMATE = 'COOKMATE'
157+
REZEPTSUITEDE = 'REZEPTSUITEDE'
157158
PDF = 'PDF'
158159

159160
type = forms.ChoiceField(choices=(
@@ -162,7 +163,7 @@ class ImportExportBase(forms.Form):
162163
(PEPPERPLATE, 'Pepperplate'), (RECETTETEK, 'RecetteTek'), (RECIPESAGE, 'Recipe Sage'), (DOMESTICA, 'Domestica'),
163164
(MEALMASTER, 'MealMaster'), (REZKONV, 'RezKonv'), (OPENEATS, 'Openeats'), (RECIPEKEEPER, 'Recipe Keeper'),
164165
(PLANTOEAT, 'Plantoeat'), (COOKBOOKAPP, 'CookBookApp'), (COPYMETHAT, 'CopyMeThat'), (PDF, 'PDF'), (MELARECIPES, 'Melarecipes'),
165-
(COOKMATE, 'Cookmate')
166+
(COOKMATE, 'Cookmate'), (REZEPTSUITEDE, 'Recipesuite.de')
166167
))
167168

168169

@@ -533,11 +534,13 @@ def __init__(self, *args, **kwargs):
533534
class Meta:
534535
model = Space
535536

536-
fields = ('food_inherit', 'reset_food_inherit', 'show_facet_count')
537+
fields = ('food_inherit', 'reset_food_inherit', 'show_facet_count', 'use_plural')
537538

538539
help_texts = {
539540
'food_inherit': _('Fields on food that should be inherited by default.'),
540-
'show_facet_count': _('Show recipe counts on search filters'), }
541+
'show_facet_count': _('Show recipe counts on search filters'),
542+
'use_plural': _('Use the plural form for units and food inside this space.'),
543+
}
541544

542545
widgets = {
543546
'food_inherit': MultiSelectWidget

cookbook/helper/ingredient_parser.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def __init__(self, request, cache_mode, ignore_automations=False):
2828
self.food_aliases = c
2929
caches['default'].touch(FOOD_CACHE_KEY, 30)
3030
else:
31-
for a in Automation.objects.filter(space=self.request.space, disabled=False, type=Automation.FOOD_ALIAS).only('param_1', 'param_2').all():
31+
for a in Automation.objects.filter(space=self.request.space, disabled=False, type=Automation.FOOD_ALIAS).only('param_1', 'param_2').order_by('order').all():
3232
self.food_aliases[a.param_1] = a.param_2
3333
caches['default'].set(FOOD_CACHE_KEY, self.food_aliases, 30)
3434

@@ -37,7 +37,7 @@ def __init__(self, request, cache_mode, ignore_automations=False):
3737
self.unit_aliases = c
3838
caches['default'].touch(UNIT_CACHE_KEY, 30)
3939
else:
40-
for a in Automation.objects.filter(space=self.request.space, disabled=False, type=Automation.UNIT_ALIAS).only('param_1', 'param_2').all():
40+
for a in Automation.objects.filter(space=self.request.space, disabled=False, type=Automation.UNIT_ALIAS).only('param_1', 'param_2').order_by('order').all():
4141
self.unit_aliases[a.param_1] = a.param_2
4242
caches['default'].set(UNIT_CACHE_KEY, self.unit_aliases, 30)
4343
else:
@@ -59,7 +59,7 @@ def apply_food_automation(self, food):
5959
except KeyError:
6060
return food
6161
else:
62-
if automation := Automation.objects.filter(space=self.request.space, type=Automation.FOOD_ALIAS, param_1=food, disabled=False).first():
62+
if automation := Automation.objects.filter(space=self.request.space, type=Automation.FOOD_ALIAS, param_1=food, disabled=False).order_by('order').first():
6363
return automation.param_2
6464
return food
6565

@@ -78,7 +78,7 @@ def apply_unit_automation(self, unit):
7878
except KeyError:
7979
return unit
8080
else:
81-
if automation := Automation.objects.filter(space=self.request.space, type=Automation.UNIT_ALIAS, param_1=unit, disabled=False).first():
81+
if automation := Automation.objects.filter(space=self.request.space, type=Automation.UNIT_ALIAS, param_1=unit, disabled=False).order_by('order').first():
8282
return automation.param_2
8383
return unit
8484

@@ -235,6 +235,10 @@ def parse(self, ingredient):
235235
# leading spaces before commas result in extra tokens, clean them out
236236
ingredient = ingredient.replace(' ,', ',')
237237

238+
# handle "(from) - (to)" amounts by using the minimum amount and adding the range to the description
239+
# "10.5 - 200 g XYZ" => "100 g XYZ (10.5 - 200)"
240+
ingredient = re.sub("^(\d+|\d+[\\.,]\d+) - (\d+|\d+[\\.,]\d+) (.*)", "\\1 \\3 (\\1 - \\2)", ingredient)
241+
238242
# if amount and unit are connected add space in between
239243
if re.match('([0-9])+([A-z])+\s', ingredient):
240244
ingredient = re.sub(r'(?<=([a-z])|\d)(?=(?(1)\d|[a-z]))', ' ', ingredient)

cookbook/helper/mdx_urlize.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
u'<p>del.icio.us</p>'
3636
3737
"""
38+
from xml.etree.ElementTree import Element
3839

3940
import markdown
4041

@@ -64,7 +65,7 @@ def handleMatch(self, m):
6465
else:
6566
url = 'http://' + url
6667

67-
el = markdown.util.etree.Element("a")
68+
el = Element("a")
6869
el.set('href', url)
6970
el.text = markdown.util.AtomicString(text)
7071
return el

cookbook/helper/recipe_url_import.py

Lines changed: 50 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212

1313
from cookbook.helper import recipe_url_import as helper
1414
from cookbook.helper.ingredient_parser import IngredientParser
15-
from cookbook.models import Keyword
15+
from cookbook.models import Keyword, Automation
16+
1617

1718
# from recipe_scrapers._utils import get_minutes ## temporary until/unless upstream incorporates get_minutes() PR
1819

@@ -121,7 +122,13 @@ def get_from_scraper(scrape, request):
121122
try:
122123
keywords.append(source_url.replace('http://', '').replace('https://', '').split('/')[0])
123124
except Exception:
124-
pass
125+
recipe_json['source_url'] = ''
126+
127+
try:
128+
if scrape.author():
129+
keywords.append(scrape.author())
130+
except:
131+
pass
125132

126133
try:
127134
recipe_json['keywords'] = parse_keywords(list(set(map(str.casefold, keywords))), request.space)
@@ -139,42 +146,58 @@ def get_from_scraper(scrape, request):
139146
if len(recipe_json['steps']) == 0:
140147
recipe_json['steps'].append({'instruction': '', 'ingredients': [], })
141148

142-
if len(parse_description(description)) > 256: # split at 256 as long descriptions dont look good on recipe cards
143-
recipe_json['steps'][0]['instruction'] = f'*{parse_description(description)}* \n\n' + recipe_json['steps'][0]['instruction']
149+
parsed_description = parse_description(description)
150+
# TODO notify user about limit if reached
151+
# limits exist to limit the attack surface for dos style attacks
152+
automations = Automation.objects.filter(type=Automation.DESCRIPTION_REPLACE, space=request.space, disabled=False).only('param_1', 'param_2', 'param_3').all().order_by('order')[:512]
153+
for a in automations:
154+
if re.match(a.param_1, (recipe_json['source_url'])[:512]):
155+
parsed_description = re.sub(a.param_2, a.param_3, parsed_description, count=1)
156+
157+
if len(parsed_description) > 256: # split at 256 as long descriptions don't look good on recipe cards
158+
recipe_json['steps'][0]['instruction'] = f'*{parsed_description}* \n\n' + recipe_json['steps'][0]['instruction']
144159
else:
145-
recipe_json['description'] = parse_description(description)[:512]
160+
recipe_json['description'] = parsed_description[:512]
146161

147162
try:
148163
for x in scrape.ingredients():
149-
try:
150-
amount, unit, ingredient, note = ingredient_parser.parse(x)
151-
ingredient = {
152-
'amount': amount,
153-
'food': {
154-
'name': ingredient,
155-
},
156-
'unit': None,
157-
'note': note,
158-
'original_text': x
159-
}
160-
if unit:
161-
ingredient['unit'] = {'name': unit, }
162-
recipe_json['steps'][0]['ingredients'].append(ingredient)
163-
except Exception:
164-
recipe_json['steps'][0]['ingredients'].append(
165-
{
166-
'amount': 0,
167-
'unit': None,
164+
if x.strip() != '':
165+
try:
166+
amount, unit, ingredient, note = ingredient_parser.parse(x)
167+
ingredient = {
168+
'amount': amount,
168169
'food': {
169-
'name': x,
170+
'name': ingredient,
170171
},
171-
'note': '',
172+
'unit': None,
173+
'note': note,
172174
'original_text': x
173175
}
174-
)
176+
if unit:
177+
ingredient['unit'] = {'name': unit, }
178+
recipe_json['steps'][0]['ingredients'].append(ingredient)
179+
except Exception:
180+
recipe_json['steps'][0]['ingredients'].append(
181+
{
182+
'amount': 0,
183+
'unit': None,
184+
'food': {
185+
'name': x,
186+
},
187+
'note': '',
188+
'original_text': x
189+
}
190+
)
175191
except Exception:
176192
pass
177193

194+
if recipe_json['source_url']:
195+
automations = Automation.objects.filter(type=Automation.INSTRUCTION_REPLACE, space=request.space, disabled=False).only('param_1', 'param_2', 'param_3').order_by('order').all()[:512]
196+
for a in automations:
197+
if re.match(a.param_1, (recipe_json['source_url'])[:512]):
198+
for s in recipe_json['steps']:
199+
s['instruction'] = re.sub(a.param_2, a.param_3, s['instruction'])
200+
178201
return recipe_json
179202

180203

0 commit comments

Comments
 (0)