mirror of
https://github.com/TandoorRecipes/recipes.git
synced 2025-12-24 02:39:20 -05:00
Merge branch 'develop' into feature/vue3
# Conflicts: # cookbook/views/api.py
This commit is contained in:
31
.idea/watcherTasks.xml
generated
Normal file
31
.idea/watcherTasks.xml
generated
Normal file
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectTasksOptions">
|
||||
<TaskOptions isEnabled="true">
|
||||
<option name="arguments" value="-m flake8 $FilePath$ --config $ContentRoot$\.flake8" />
|
||||
<option name="checkSyntaxErrors" value="true" />
|
||||
<option name="description" />
|
||||
<option name="exitCodeBehavior" value="ALWAYS" />
|
||||
<option name="fileExtension" value="py" />
|
||||
<option name="immediateSync" value="false" />
|
||||
<option name="name" value="Flake8 Watcher" />
|
||||
<option name="output" value="$FilePath$" />
|
||||
<option name="outputFilters">
|
||||
<array>
|
||||
<FilterInfo>
|
||||
<option name="description" value="" />
|
||||
<option name="name" value="" />
|
||||
<option name="regExp" value="$FILE_PATH$:$LINE$:$COLUMN$: $MESSAGE$" />
|
||||
</FilterInfo>
|
||||
</array>
|
||||
</option>
|
||||
<option name="outputFromStdout" value="false" />
|
||||
<option name="program" value="$PyInterpreterDirectory$/python" />
|
||||
<option name="runOnExternalChanges" value="false" />
|
||||
<option name="scopeName" value="Current File" />
|
||||
<option name="trackOnlyRoot" value="false" />
|
||||
<option name="workingDir" value="" />
|
||||
<envs />
|
||||
</TaskOptions>
|
||||
</component>
|
||||
</project>
|
||||
@@ -6,6 +6,8 @@ RUN apk add --no-cache postgresql-libs postgresql-client gettext zlib libjpeg li
|
||||
#Print all logs without buffering it.
|
||||
ENV PYTHONUNBUFFERED 1
|
||||
|
||||
ENV DOCKER true
|
||||
|
||||
#This port will be used by gunicorn.
|
||||
EXPOSE 8080
|
||||
|
||||
@@ -33,6 +35,12 @@ RUN apk add --no-cache --virtual .build-deps gcc musl-dev postgresql-dev zlib-de
|
||||
#Copy project and execute it.
|
||||
COPY . ./
|
||||
|
||||
# collect the static files
|
||||
RUN /opt/recipes/venv/bin/python manage.py collectstatic_js_reverse
|
||||
RUN /opt/recipes/venv/bin/python manage.py collectstatic --noinput
|
||||
# copy the collected static files to a different location, so they can be moved into a potentially mounted volume
|
||||
RUN mv /opt/recipes/staticfiles /opt/recipes/staticfiles-collect
|
||||
|
||||
# collect information from git repositories
|
||||
RUN /opt/recipes/venv/bin/python version.py
|
||||
# delete git repositories to reduce image size
|
||||
|
||||
21
boot.sh
21
boot.sh
@@ -67,12 +67,25 @@ echo "Migrating database"
|
||||
|
||||
python manage.py migrate
|
||||
|
||||
echo "Generating static files"
|
||||
if [[ "${DOCKER}" == "true" ]]; then
|
||||
if [[ -d "/opt/recipes/staticfiles-collect" ]]; then
|
||||
echo "Copying cached static files from docker build"
|
||||
|
||||
python manage.py collectstatic_js_reverse
|
||||
python manage.py collectstatic --noinput
|
||||
mkdir -p /opt/recipes/staticfiles
|
||||
rm -rf /opt/recipes/staticfiles/*
|
||||
mv /opt/recipes/staticfiles-collect/* /opt/recipes/staticfiles
|
||||
rm -rf /opt/recipes/staticfiles-collect
|
||||
else
|
||||
echo "Static files are already up to date"
|
||||
fi
|
||||
else
|
||||
echo "Collecting static files, this may take a while..."
|
||||
|
||||
echo "Done"
|
||||
python manage.py collectstatic_js_reverse
|
||||
python manage.py collectstatic --noinput
|
||||
|
||||
echo "Done"
|
||||
fi
|
||||
|
||||
chmod -R 755 /opt/recipes/mediafiles
|
||||
|
||||
|
||||
@@ -15,12 +15,9 @@ from cookbook.models import Automation, Keyword, PropertyType
|
||||
|
||||
|
||||
def get_from_scraper(scrape, request):
|
||||
# converting the scrape_me object to the existing json format based on ld+json
|
||||
# converting the scrape_html object to the existing json format based on ld+json
|
||||
|
||||
recipe_json = {
|
||||
'steps': [],
|
||||
'internal': True
|
||||
}
|
||||
recipe_json = {'steps': [], 'internal': True}
|
||||
keywords = []
|
||||
|
||||
# assign source URL
|
||||
@@ -157,11 +154,18 @@ def get_from_scraper(scrape, request):
|
||||
# assign steps
|
||||
try:
|
||||
for i in parse_instructions(scrape.instructions()):
|
||||
recipe_json['steps'].append({'instruction': i, 'ingredients': [], 'show_ingredients_table': request.user.userpreference.show_step_ingredients, })
|
||||
recipe_json['steps'].append({
|
||||
'instruction': i,
|
||||
'ingredients': [],
|
||||
'show_ingredients_table': request.user.userpreference.show_step_ingredients,
|
||||
})
|
||||
except Exception:
|
||||
pass
|
||||
if len(recipe_json['steps']) == 0:
|
||||
recipe_json['steps'].append({'instruction': '', 'ingredients': [], })
|
||||
recipe_json['steps'].append({
|
||||
'instruction': '',
|
||||
'ingredients': [],
|
||||
})
|
||||
|
||||
recipe_json['description'] = recipe_json['description'][:512]
|
||||
if len(recipe_json['description']) > 256: # split at 256 as long descriptions don't look good on recipe cards
|
||||
@@ -182,20 +186,20 @@ def get_from_scraper(scrape, request):
|
||||
'original_text': x
|
||||
}
|
||||
if unit:
|
||||
ingredient['unit'] = {'name': unit, }
|
||||
ingredient['unit'] = {
|
||||
'name': unit,
|
||||
}
|
||||
recipe_json['steps'][0]['ingredients'].append(ingredient)
|
||||
except Exception:
|
||||
recipe_json['steps'][0]['ingredients'].append(
|
||||
{
|
||||
'amount': 0,
|
||||
'unit': None,
|
||||
'food': {
|
||||
'name': x,
|
||||
},
|
||||
'note': '',
|
||||
'original_text': x
|
||||
}
|
||||
)
|
||||
recipe_json['steps'][0]['ingredients'].append({
|
||||
'amount': 0,
|
||||
'unit': None,
|
||||
'food': {
|
||||
'name': x,
|
||||
},
|
||||
'note': '',
|
||||
'original_text': x
|
||||
})
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@@ -248,14 +252,16 @@ def get_from_youtube_scraper(url, request):
|
||||
'working_time': 0,
|
||||
'waiting_time': 0,
|
||||
'image': "",
|
||||
'keywords': [{'name': kw.name, 'label': kw.name, 'id': kw.pk}],
|
||||
'keywords': [{
|
||||
'name': kw.name,
|
||||
'label': kw.name,
|
||||
'id': kw.pk
|
||||
}],
|
||||
'source_url': url,
|
||||
'steps': [
|
||||
{
|
||||
'ingredients': [],
|
||||
'instruction': ''
|
||||
}
|
||||
]
|
||||
'steps': [{
|
||||
'ingredients': [],
|
||||
'instruction': ''
|
||||
}]
|
||||
}
|
||||
|
||||
try:
|
||||
@@ -452,10 +458,7 @@ def normalize_string(string):
|
||||
|
||||
|
||||
def iso_duration_to_minutes(string):
|
||||
match = re.match(
|
||||
r'P((?P<years>\d+)Y)?((?P<months>\d+)M)?((?P<weeks>\d+)W)?((?P<days>\d+)D)?T((?P<hours>\d+)H)?((?P<minutes>\d+)M)?((?P<seconds>\d+)S)?',
|
||||
string
|
||||
).groupdict()
|
||||
match = re.match(r'P((?P<years>\d+)Y)?((?P<months>\d+)M)?((?P<weeks>\d+)W)?((?P<days>\d+)D)?T((?P<hours>\d+)H)?((?P<minutes>\d+)M)?((?P<seconds>\d+)S)?', string).groupdict()
|
||||
return int(match['days'] or 0) * 24 * 60 + int(match['hours'] or 0) * 60 + int(match['minutes'] or 0)
|
||||
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ def text_scraper(text, url=None):
|
||||
html=None,
|
||||
url=None,
|
||||
):
|
||||
self.wild_mode = False
|
||||
self.supported_only = False
|
||||
self.meta_http_equiv = False
|
||||
self.soup = BeautifulSoup(html, "html.parser")
|
||||
self.url = url
|
||||
|
||||
@@ -4,10 +4,10 @@ import pytest
|
||||
from django.contrib import auth
|
||||
from django.test import RequestFactory
|
||||
from django_scopes import scope
|
||||
from recipe_scrapers import scrape_html
|
||||
|
||||
from cookbook.helper.automation_helper import AutomationEngine
|
||||
from cookbook.helper.recipe_url_import import get_from_scraper
|
||||
from cookbook.helper.scrapers.scrapers import text_scraper
|
||||
from cookbook.models import Automation
|
||||
|
||||
DATA_DIR = "cookbook/tests/other/test_data/"
|
||||
@@ -73,12 +73,14 @@ def test_unit_automation(u1_s1, arg):
|
||||
assert (automation.apply_unit_automation(arg[0]) == target_name) is True
|
||||
|
||||
|
||||
@pytest.mark.parametrize("arg", [
|
||||
[[1, 'egg', 'white'], '', [1, '', 'egg', 'white']],
|
||||
[[1, 'Egg', 'white'], '', [1, '', 'Egg', 'white']],
|
||||
[[1, 'êgg', 'white'], '', [1, 'êgg', 'white']],
|
||||
[[1, 'egg', 'white'], 'whole', [1, 'whole', 'egg', 'white']],
|
||||
])
|
||||
@pytest.mark.parametrize(
|
||||
"arg", [
|
||||
[[1, 'egg', 'white'], '', [1, '', 'egg', 'white']],
|
||||
[[1, 'Egg', 'white'], '', [1, '', 'Egg', 'white']],
|
||||
[[1, 'êgg', 'white'], '', [1, 'êgg', 'white']],
|
||||
[[1, 'egg', 'white'], 'whole', [1, 'whole', 'egg', 'white']],
|
||||
]
|
||||
)
|
||||
def test_never_unit_automation(u1_s1, arg):
|
||||
user = auth.get_user(u1_s1)
|
||||
space = user.userspace_set.first().space
|
||||
@@ -97,13 +99,15 @@ def test_never_unit_automation(u1_s1, arg):
|
||||
['.*allrecipes.*', True],
|
||||
['.*google.*', False],
|
||||
])
|
||||
@pytest.mark.parametrize("arg", [
|
||||
[Automation.DESCRIPTION_REPLACE],
|
||||
[Automation.INSTRUCTION_REPLACE],
|
||||
[Automation.NAME_REPLACE],
|
||||
[Automation.FOOD_REPLACE],
|
||||
[Automation.UNIT_REPLACE],
|
||||
])
|
||||
@pytest.mark.parametrize(
|
||||
"arg", [
|
||||
[Automation.DESCRIPTION_REPLACE],
|
||||
[Automation.INSTRUCTION_REPLACE],
|
||||
[Automation.NAME_REPLACE],
|
||||
[Automation.FOOD_REPLACE],
|
||||
[Automation.UNIT_REPLACE],
|
||||
]
|
||||
)
|
||||
def test_regex_automation(u1_s1, arg, source):
|
||||
user = auth.get_user(u1_s1)
|
||||
space = user.userspace_set.first().space
|
||||
@@ -124,11 +128,13 @@ def test_regex_automation(u1_s1, arg, source):
|
||||
assert (automation.apply_regex_replace_automation(fail, arg[0]) == target) == False
|
||||
|
||||
|
||||
@pytest.mark.parametrize("arg", [
|
||||
['second first', 'first second'],
|
||||
['longer string second first longer string', 'longer string first second longer string'],
|
||||
['second fails first', 'second fails first'],
|
||||
])
|
||||
@pytest.mark.parametrize(
|
||||
"arg", [
|
||||
['second first', 'first second'],
|
||||
['longer string second first longer string', 'longer string first second longer string'],
|
||||
['second fails first', 'second fails first'],
|
||||
]
|
||||
)
|
||||
def test_transpose_automation(u1_s1, arg):
|
||||
user = auth.get_user(u1_s1)
|
||||
space = user.userspace_set.first().space
|
||||
@@ -160,7 +166,7 @@ def test_url_import_regex_replace(u1_s1):
|
||||
else:
|
||||
test_file = os.path.join(os.getcwd(), 'cookbook', 'tests', 'other', 'test_data', recipe)
|
||||
with open(test_file, 'r', encoding='UTF-8') as d:
|
||||
scrape = text_scraper(text=d.read(), url="https://www.allrecipes.com")
|
||||
scrape = scrape_html(html=d.read(), org_url="https://testrecipe.test", supported_only=False)
|
||||
with scope(space=space):
|
||||
for t in types:
|
||||
Automation.objects.get_or_create(name=t, type=t, param_1='.*', param_2=find_text, param_3='', created_by=user, space=space)
|
||||
|
||||
@@ -36,8 +36,7 @@ from drf_spectacular.types import OpenApiTypes
|
||||
from drf_spectacular.utils import OpenApiParameter, extend_schema, extend_schema_view
|
||||
from icalendar import Calendar, Event
|
||||
from oauth2_provider.models import AccessToken
|
||||
from PIL import UnidentifiedImageError
|
||||
from recipe_scrapers import scrape_me
|
||||
from recipe_scrapers import scrape_html
|
||||
from recipe_scrapers._exceptions import NoSchemaFoundInWildMode
|
||||
from requests.exceptions import MissingSchema
|
||||
from rest_framework import decorators, status, viewsets
|
||||
@@ -1445,8 +1444,8 @@ class RecipeUrlImportView(APIView):
|
||||
else:
|
||||
try:
|
||||
if validators.url(url, public=True):
|
||||
scrape = scrape_me(url_path=url, wild_mode=True)
|
||||
|
||||
html = requests.get(url).content
|
||||
scrape = scrape_html(org_url=url, html=html, supported_only=False)
|
||||
else:
|
||||
return Response({'error': True, 'msg': _('Invalid Url')}, status=status.HTTP_400_BAD_REQUEST)
|
||||
except NoSchemaFoundInWildMode:
|
||||
|
||||
@@ -32,7 +32,7 @@ Jinja2==3.1.3
|
||||
django-webpack-loader==3.0.1
|
||||
git+https://github.com/BITSOLVER/django-js-reverse@071e304fd600107bc64bbde6f2491f1fe049ec82
|
||||
django-allauth==0.61.1
|
||||
recipe-scrapers==14.53.0
|
||||
recipe-scrapers==15.0.0-rc2
|
||||
django-scopes==2.0.0
|
||||
django-treebeard==4.7
|
||||
django-cors-headers==4.3.1
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
<i class="text-warning fas fa-exclamation-triangle"></i>
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ f.value }} {{ selected_property.unit }}
|
||||
{{ roundDecimals(f.value) }} {{ selected_property.unit }}
|
||||
</template>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
Reference in New Issue
Block a user