+
+
+
+
\ No newline at end of file
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 000000000..aea0c3a51
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,13 @@
+# generated files
+api.ts
+vue/src/apps/*.js
+vue/node_modules
+staticfiles/
+docs/reports/
+/vue3/src/openapi/
+
+# ignored files - prettier interferes with django templates and github actions
+*.html
+*.yml
+*.yaml
+
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 000000000..956e17f3c
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,7 @@
+{
+ "printWidth": 179,
+ "trailingComma": "es5",
+ "tabWidth": 2,
+ "semi": false,
+ "experimentalTernaries": true
+}
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 1f900399c..c3e6602d3 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -3,5 +3,12 @@
"cookbook/tests"
],
"python.testing.unittestEnabled": false,
- "python.testing.pytestEnabled": true
-}
\ No newline at end of file
+ "python.testing.pytestEnabled": true,
+ "editor.formatOnSave": true,
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
+ "[python]": {
+ "editor.defaultFormatter": "eeyore.yapf",
+ },
+ "yapf.args": [],
+ "isort.args": []
+}
diff --git a/Dockerfile b/Dockerfile
index 3f17d0f11..c5856bcfa 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -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
diff --git a/boot.sh b/boot.sh
index ae3dbb51d..c14d66aab 100644
--- a/boot.sh
+++ b/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
diff --git a/cookbook/admin.py b/cookbook/admin.py
index 402d6f3f4..88a1cd79a 100644
--- a/cookbook/admin.py
+++ b/cookbook/admin.py
@@ -185,7 +185,7 @@ class StepAdmin(admin.ModelAdmin):
@admin.display(description="Name")
def recipe_and_name(obj):
if not obj.recipe_set.exists():
- return f"Orphaned Step{'':s if not obj.name else f': {obj.name}'}"
+ return f"Orphaned Step{'' if not obj.name else f': {obj.name}'}"
return f"{obj.recipe_set.first().name}: {obj.name}" if obj.name else obj.recipe_set.first().name
@@ -376,10 +376,17 @@ class ShareLinkAdmin(admin.ModelAdmin):
admin.site.register(ShareLink, ShareLinkAdmin)
+@admin.action(description='Delete all properties with type')
+def delete_properties_with_type(modeladmin, request, queryset):
+ for pt in queryset:
+ Property.objects.filter(property_type=pt).delete()
+
+
class PropertyTypeAdmin(admin.ModelAdmin):
- search_fields = ('space',)
+ search_fields = ('name',)
list_display = ('id', 'space', 'name', 'fdc_id')
+ actions = [delete_properties_with_type]
admin.site.register(PropertyType, PropertyTypeAdmin)
diff --git a/cookbook/helper/recipe_url_import.py b/cookbook/helper/recipe_url_import.py
index aa0bb2d87..eca6813aa 100644
--- a/cookbook/helper/recipe_url_import.py
+++ b/cookbook/helper/recipe_url_import.py
@@ -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\d+)Y)?((?P\d+)M)?((?P\d+)W)?((?P\d+)D)?T((?P\d+)H)?((?P\d+)M)?((?P\d+)S)?',
- string
- ).groupdict()
+ match = re.match(r'P((?P\d+)Y)?((?P\d+)M)?((?P\d+)W)?((?P\d+)D)?T((?P\d+)H)?((?P\d+)M)?((?P\d+)S)?', string).groupdict()
return int(match['days'] or 0) * 24 * 60 + int(match['hours'] or 0) * 60 + int(match['minutes'] or 0)
diff --git a/cookbook/helper/scrapers/scrapers.py b/cookbook/helper/scrapers/scrapers.py
index 7d6c08b15..0cf01333b 100644
--- a/cookbook/helper/scrapers/scrapers.py
+++ b/cookbook/helper/scrapers/scrapers.py
@@ -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
diff --git a/cookbook/tests/other/test_automations.py b/cookbook/tests/other/test_automations.py
index 8b7b5568f..810a73f74 100644
--- a/cookbook/tests/other/test_automations.py
+++ b/cookbook/tests/other/test_automations.py
@@ -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)
diff --git a/cookbook/views/api.py b/cookbook/views/api.py
index a0ceada12..f890caeaa 100644
--- a/cookbook/views/api.py
+++ b/cookbook/views/api.py
@@ -35,7 +35,7 @@ from django.utils.translation import gettext as _
from django_scopes import scopes_disabled
from icalendar import Calendar, Event
from oauth2_provider.models import AccessToken
-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
@@ -1437,8 +1437,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:
diff --git a/docs/contribute.md b/docs/contribute.md
deleted file mode 100644
index 545ded755..000000000
--- a/docs/contribute.md
+++ /dev/null
@@ -1,117 +0,0 @@
-If you like this application and want it to improve, feel free to contribute to its development.
-
-!!! success "Contribution List"
- If you help bring this project forward you deserve to be credited for it.
- Feel free to add yourself to `CONTRIBUTERS.md` or message me to add you if you have contributed anything.
-
-## Issues
-The most basic but also very important way of contributing is reporting issues and commenting on ideas and feature requests
-over at [GitHub issues](https://github.com/vabene1111/recipes/issues).
-
-Without feedback improvement can't happen, so don't hesitate to say what you want to say.
-
-## Contributing Code
-If you want to contribute bug fixes or small tweaks then your pull requests are always welcome!
-
-!!! danger "Discuss First!"
- If you want to contribute larger features that introduce more complexity to the project please
- make sure to **first submit a technical description** outlining what and how you want to do it.
- This allows me and the community to give feedback and manage the complexity of the overall
- application. If you don't do this please don't be mad if I reject your PR
-
-!!! info
- The dev setup is a little messy as this application combines the best (at least in my opinion) of both Django and Vue.js.
-
-### Devcontainer Setup
-There is a [devcontainer](https://containers.dev) set up to ease development. It is optimized for VSCode, but should be able to
-be used by other editors as well. Once the container is running, you can do things like start a Django dev server, start a Vue.js
-dev server, run python tests, etc. by either using the VSCode tasks below, or manually running commands described in the individual
-technology sections below.
-
-In VSCode, simply check out the git repository, and then via the command palette, choose `Dev Containers: Reopen in container`.
-
-If you need to change python dependencies (requierments.txt) or OS packages, you will need to rebuild the container. If you are
-changing OS package requirements, you will need to update both the main `Dockerfile` and the `.devcontainer/Dockerfile`.
-
-### VSCode Tasks
-If you use VSCode, there are a number of tasks that are available. Here are a few of the key ones:
-
-* `Setup Dev Server` - Runs all the prerequisite steps so that the dev server can be run inside VSCode.
-* `Setup Tests` - Runs all prerequisites so tests can be run inside VSCode.
-
-Once these are run, you should be able to run/debug a django server in VSCode as well as run/debug tests directly through VSCode.
-There are also a few other tasks specified in case you have specific development needs:
-
-* `Run Dev Server` - Runs a django development server not connected to VSCode.
-* `Run all pytests` - Runs all the pytests outside of VSCode.
-* `Yarn Serve` - Runs development Vue.js server not connected to VSCode. Useful if you want to make Vue changes and see them in realtime.
-* `Serve Documentation` - Runs a documentation server. Useful if you want to see how changes to documentation show up.
-
-### Django
-This application is developed using the Django framework for Python. They have excellent
-[documentation](https://www.djangoproject.com/start/) on how to get started, so I will only give you the basics here.
-
-1. Clone this repository wherever you like and install the Python language for your OS (I recommend using version 3.10 or above).
-2. Open it in your favorite editor/IDE (e.g. PyCharm).
- a. If you want, create a virtual environment for all your packages.
-3. Install all required packages: `pip install -r requirements.txt`.
-4. Run the migrations: `python manage.py migrate`.
-5. Start the development server: `python manage.py runserver`.
-
-There is **no** need to set any environment variables. By default, a simple SQLite database is used and all settings are
-populated from default values.
-
-### Vue.js
-Most new frontend pages are build using [Vue.js](https://vuejs.org/).
-
-In order to work on these pages, you will have to install a Javascript package manager of your choice. The following examples use yarn.
-
-In the `vue` folder run `yarn install` to install the dependencies. After that you can use `yarn serve` to start the development server,
-and proceed to test your changes. If you do not wish to work on those pages, but instead want the application to work properly during
-development, run `yarn build` to build the frontend pages once.
-
-#### API Client
-The API Client is generated automatically from the OpenAPI interface provided by the Django REST framework.
-For this [openapi-generator](https://github.com/OpenAPITools/openapi-generator) is used.
-
-Install it using your desired setup method. (For example, using `npm install @openapitools/openapi-generator-cli -g`.)
-
-Navigate to `vue/src/utils/openapi`.
-
-Generate the schema using `openapi-generator-cli generate -g typescript-axios -i http://127.0.0.1:8000/openapi/`. (Replace your dev server url if required.)
-
-## Contribute Documentation
-The documentation is built from the markdown files in the [docs](https://github.com/vabene1111/recipes/tree/develop/docs)
-folder of the GitHub repository.
-
-In order to contribute to the documentation, you can fork the repository and edit the markdown files in the browser.
-
-Now install mkdocs and dependencies: `pip install mkdocs-material mkdocs-include-markdown-plugin`.
-
-If you want to test the documentation, locally run `mkdocs serve` from the project root.
-
-## Contribute Translations
-
-If you know any foreign languages that the project has not been completely translated to yet, feel free to contribute translations.
-
-Translations are managed on [translate.tandoor.dev](https://translate.tandoor.dev/), a self hosted instance of [Weblate](https://weblate.org/de/).
-
-You can simply register an account and then follow these steps to add translations:
-
-1. After registering, you are asked to select your languages. This is optional but allows weblate to only show you relevant translations.
-2. In the navigation click on `Projects` and then `Browse all projects`.
-3. Select Tandoor and on the top-right hand corner, select `Watch project Tandoor` (click on `Not watching`).
-4. Go back to the dashboard. It now shows you the relevant translations for your languages. Click on the pencil icon to get started.
-
-!!! info "Creating a new language"
- To create a new language you must first select Tandoor (the project) and then a component.
- Here you will have the option to add the language. Afterwards you can also simply add it to the other components as well.
- Once a new language is (partially) finished let me know on GitHub so I can add it to the language-switcher in Tandoor itself.
-
-There is also [a lot of documentation](https://docs.weblate.org/en/latest/user/translating.html) available from Weblate directly.
-
-
-
-It is also possible to provide the translations directly by creating a new language
-using `manage.py makemessages -l -i venv`. Once finished, simply open a PR with the changed files. This sometimes causes issues merging
-with weblate, so I would prefer the use of weblate.
diff --git a/docs/contribute/assets/flake8_watcher.png b/docs/contribute/assets/flake8_watcher.png
new file mode 100644
index 000000000..f5bf77be0
Binary files /dev/null and b/docs/contribute/assets/flake8_watcher.png differ
diff --git a/docs/contribute/assets/isort_watcher.png b/docs/contribute/assets/isort_watcher.png
new file mode 100644
index 000000000..eb9c1d787
Binary files /dev/null and b/docs/contribute/assets/isort_watcher.png differ
diff --git a/docs/contribute/assets/linting_error.png b/docs/contribute/assets/linting_error.png
new file mode 100644
index 000000000..0acf97f3f
Binary files /dev/null and b/docs/contribute/assets/linting_error.png differ
diff --git a/docs/contribute/assets/prettier_watcher.png b/docs/contribute/assets/prettier_watcher.png
new file mode 100644
index 000000000..893732d9f
Binary files /dev/null and b/docs/contribute/assets/prettier_watcher.png differ
diff --git a/docs/contribute/assets/yapf_watcher.png b/docs/contribute/assets/yapf_watcher.png
new file mode 100644
index 000000000..9ddd968cd
Binary files /dev/null and b/docs/contribute/assets/yapf_watcher.png differ
diff --git a/docs/contribute/contribute.md b/docs/contribute/contribute.md
new file mode 100644
index 000000000..26e6db4bc
--- /dev/null
+++ b/docs/contribute/contribute.md
@@ -0,0 +1,55 @@
+If you like this application and want it to give back, there are many ways to contribute.
+
+!!! success "Contribution List"
+If you help bring this project forward you deserve to be credited for it.
+Feel free to add yourself to `CONTRIBUTERS.md` or message me to add you if you have contributed anything.
+
+## Translations
+
+If you know any foreign languages you can:
+Improve the translations for any of the existing languages.
+
+Add a new language to the long list of existing translations.
+
+- Armenian
+- Bulgarian
+- Catalan
+- Czech
+- Danish
+- Dutch
+- English
+- French
+- German
+- Hungarian
+- Italian
+- Latvian
+- Norwegian
+- Polish
+- Russian
+- Spanish
+- Swedish
+
+See [here](/contribute/translations) for further information on how to contribute translation to Tandoor.
+
+## Issues and Feature Requests
+
+The most basic but also very important way of contributing is reporting issues and commenting on ideas and feature requests
+over at [GitHub issues](https://github.com/vabene1111/recipes/issues).
+
+Without feedback improvement can't happen, so don't hesitate to say what you want to say.
+
+## Documentation
+
+Helping improve the documentation for Tandoor is one of the easiest ways to give back and doesn't even require deep technical knowledge.
+You can write guides on how to install and configure Tandoor expanding our repository of non-standard configuations.
+Or you can write how-to guides using some of Tandoor's advanced features such as authentication or automation.
+
+See [here](/contribute/documentation) for more information on how to add documentation to Tandoor.
+
+## Contributing Code
+
+For the truly ambitious, you can help write code to fix issues, add additional features, or write your own scripts using
+Tandoor's extensive API and share your work with the community.
+
+Before writing any code, please make sure that you review [contribution guidelines](/contribute/guidelines) and
+[VSCode](/contribute/vscode) or [PyCharm](/contribute/pycharm) specific configurations.
diff --git a/docs/contribute/documentation.md b/docs/contribute/documentation.md
new file mode 100644
index 000000000..3587f9bc0
--- /dev/null
+++ b/docs/contribute/documentation.md
@@ -0,0 +1,26 @@
+The documentation is built from the markdown files in the [docs](https://github.com/vabene1111/recipes/tree/develop/docs)
+folder of the GitHub repository.
+
+In order to contribute to the documentation, there are a couple of methods that you can use.
+
+## Directly on GitHub
+
+You can fork the develop repository and edit the markdown files directly on the GitHub website.
+
+## With an IDE
+
+You can fork the develop repository and edit the markdown files from your favorite IDE such as VSCode or PyCharm.
+One advantage of using as IDE is that you can preview your changes by:
+
+### Installing mkdocs
+
+Now install mkdocs and dependencies: `pip install mkdocs-material mkdocs-include-markdown-plugin`.
+
+### Serving Documetation
+
+If you want to test the documentation, locally run `mkdocs serve` from the project root.
+
+## Super Low Tech
+
+Create your documentation in any work processor (or even create a video!) and [open a feature request](https://github.com/vabene1111/recipes/issues)
+attaching your document and requesting that someone add the documentation to Tandoor.
diff --git a/docs/contribute/guidelines.md b/docs/contribute/guidelines.md
new file mode 100644
index 000000000..f1b3eea9e
--- /dev/null
+++ b/docs/contribute/guidelines.md
@@ -0,0 +1,63 @@
+If you want to contribute bug fixes or small tweaks then your pull requests are always welcome!
+
+!!! danger "Discuss First!"
+If you want to contribute larger features that introduce more complexity to the project please
+make sure to **first submit a technical description** outlining what and how you want to do it.
+This allows me and the community to give feedback and manage the complexity of the overall
+application. If you don't do this please don't be mad if I reject your PR.
+
+## License
+
+Contributing to Tandoor requires signing a Contributor License Agreement. You can review the CLA [here](https://cla-assistant.io/TandoorRecipes/recipes).
+
+## Linting & Formatting
+
+Tandoor uses a number of libraries to maintain style and formatting consistency.
+To contribute to the project you are required to use the following packages with the project defined configurations:
+
+- flake8
+- yapf
+- isort
+- prettier
+
+!!! tip "Manual Formatting"
+ It is possible to run formatting manually, but it is recommended to setup your IDE to format on save.
+ ``` bash
+ flake8 file.py --ignore=E501 | isort -q file.py | yapf -i file.py
+ prettier --write file.vue
+ ```
+
+## Testing
+Django uses pytest-django to implement a full suite of testing. If you make any functional changes, please implment the appropriate
+tests.
+
+Tandoor is also actively soliciting contribors willing to setup vue3 testing. If you have knowledge in this area it would be greatly appreciated.
+
+## API Client
+
+Tandoor uses [django-rest-framework](https://www.django-rest-framework.org/) for API implementation. Making contributions that impact the API requires an understanding of
+Viewsets and Serializers.
+
+Also double check that your changes are actively reflected in the schema so that client apis are generated accurately.
+
+The API Client is generated automatically from the OpenAPI interface provided by the Django REST framework.
+For this [openapi-generator](https://github.com/OpenAPITools/openapi-generator) is used.
+
+Install it using your desired setup method. (For example, using `npm install @openapitools/openapi-generator-cli -g`.)
+
+### Vue
+
+Navigate to `vue/src/utils/openapi`.
+
+Generate the schema using `openapi-generator-cli generate -g typescript-axios -i http://127.0.0.1:8000/openapi/`. (Replace your dev server url if required.)
+
+### Vue3
+
+Navigate to `vue3/src/openapi`.
+
+Generate the schema using `openapi-generator-cli generate -g typescript-fetch -i http://127.0.0.1:8000/openapi/`. (Replace your dev server url if required.)
+
+## Install and Configuration
+
+Instructions for [VSCode](/contribute/vscode)
+Instructions for [PyCharm](/contribute/pycharm)
diff --git a/docs/contribute/installation.md b/docs/contribute/installation.md
new file mode 100644
index 000000000..39eaba5a8
--- /dev/null
+++ b/docs/contribute/installation.md
@@ -0,0 +1,39 @@
+!!! info
+The dev setup is a little messy as this application combines the best (at least in my opinion) of both Django and Vue.js.
+
+### Devcontainer Setup
+
+There is a [devcontainer](https://containers.dev) set up to ease development. It is optimized for VSCode, but should be able to
+be used by other editors as well. Once the container is running, you can do things like start a Django dev server, start a Vue.js
+dev server, run python tests, etc. by either using the VSCode tasks below, or manually running commands described in the individual
+technology sections below.
+
+In VSCode, simply check out the git repository, and then via the command palette, choose `Dev Containers: Reopen in container`.
+
+If you need to change python dependencies (requierments.txt) or OS packages, you will need to rebuild the container. If you are
+changing OS package requirements, you will need to update both the main `Dockerfile` and the `.devcontainer/Dockerfile`.
+
+### Django
+
+This application is developed using the Django framework for Python. They have excellent
+[documentation](https://www.djangoproject.com/start/) on how to get started, so I will only give you the basics here.
+
+1. Clone this repository wherever you like and install the Python language for your OS (I recommend using version 3.10 or above).
+2. Open it in your favorite editor/IDE (e.g. PyCharm).
+ a. If you want, create a virtual environment for all your packages.
+3. Install all required packages: `pip install -r requirements.txt`.
+4. Run the migrations: `python manage.py migrate`.
+5. Start the development server: `python manage.py runserver`.
+
+There is **no** need to set any environment variables. By default, a simple SQLite database is used and all settings are
+populated from default values.
+
+### Vue.js
+
+Most new frontend pages are build using [Vue.js](https://vuejs.org/).
+
+In order to work on these pages, you will have to install a Javascript package manager of your choice. The following examples use yarn.
+
+In the `vue` folder run `yarn install` to install the dependencies. After that you can use `yarn serve` to start the development server,
+and proceed to test your changes. If you do not wish to work on those pages, but instead want the application to work properly during
+development, run `yarn build` to build the frontend pages once.
diff --git a/docs/contribute/pycharm.md b/docs/contribute/pycharm.md
new file mode 100644
index 000000000..927e2ecbc
--- /dev/null
+++ b/docs/contribute/pycharm.md
@@ -0,0 +1,62 @@
+PyCharm can be configured to format and lint on save. Doing so requires some manual configuration as outlined below.
+
+## Setup File Watchers
+
+1. Navigate to File -> Settings -> Plugins
+2. Download and install [File Watchers](https://plugins.jetbrains.com/plugin/7177-file-watchers)
+3. Navigate to File -> Settings -> Tools -> Black
+4. Confirm 'Use Black Formatter' is unchecked for both 'On code reformat' and 'On save'
+
+## Setup flake8 Watcher
+
+1. Navigate to File -> Settings -> Tools -> File Watchers
+2. Click the '+' to add a new watcher.
+3. Configure the watcher as below.
+
+ 
+
+4. Navigate to File -> Settings -> Editor -> Inspections -> File watcher problems
+5. Under Severity select 'Edit Severities'
+6. Click the '+' to add a severity calling it 'Linting Error'
+7. Configure a background and effect as below.
+
+ 
+
+## Setup isort
+
+1. Navigate to File -> Settings -> Tools -> File Watchers
+2. Click the '+' to add a new watcher.
+3. Configure the watcher as below.
+
+ 
+
+## Setup yapf
+
+1. Navigate to File -> Settings -> Tools -> File Watchers
+2. Click the '+' to add a new watcher.
+3. Configure the watcher as below.
+
+ 
+
+
+!!! hint
+ Adding a comma at the end of a list will trigger yapf to put each element of the list on a new line
+
+## Setup prettier
+
+1. Navigate to File -> Settings -> Tools -> File Watchers
+2. Click the '+' to add a new watcher.
+3. Change 'File Type' to 'Any'.
+4. Click the three dots next to 'Scope' to create a custom scope.
+5. Click '+' to add a new scope
+
+- Name: prettier
+- Pattern: `file:vue/src//*||file:vue3/src//*||file:docs//*`
+
+6. Configure the watcher as below.
+
+ 
+
+- Arguments: `--cwd $ProjectFileDir$\vue prettier -w --config $ProjectFileDir$\.prettierrc $FilePath$`
+
+## Setup Volar??
diff --git a/docs/contribute/related.md b/docs/contribute/related.md
new file mode 100644
index 000000000..ad2cce45a
--- /dev/null
+++ b/docs/contribute/related.md
@@ -0,0 +1,25 @@
+## Recipe Scraper
+
+While not directly related to Tandoor, we make extensive use of the brilliant [recipe-scrapers](https://github.com/hhursev/recipe-scrapers)
+package by hhursev.
+
+If you have the skills to add new sites or help resolve issues you are indirectly helping Tandoor.
+
+## Unofficial mobile app
+
+Maintained by [phantomate](https://github.com/phantomate/Untare)
+
+[iPhone](https://apps.apple.com/nl/app/untare/id6448643329?l=en&platform=iphone)
+[Android](https://play.google.com/store/apps/details?id=unofficial.tandoor.recipes)
+
+## GPT Recipe
+
+Maintained by [John Pedrie](https://github.com/jdpedrie/gpt-recipe)
+
+Convert pictures of recipes to a structure that can be imported to Tandoor with ChatGPT.
+
+## Tandoor Menu Generator
+
+Maintained by [smilerz](https://github.com/smilerz/tandoor-menu-generator)
+
+Generate a mealplan tbased on complex criteria and optionally insert it into an SVG menu template.
diff --git a/docs/contribute/translations.md b/docs/contribute/translations.md
new file mode 100644
index 000000000..cd8d451c2
--- /dev/null
+++ b/docs/contribute/translations.md
@@ -0,0 +1,21 @@
+Translations are managed on [translate.tandoor.dev](https://translate.tandoor.dev/), a self hosted instance of [Weblate](https://weblate.org/de/).
+
+You can simply register an account and then follow these steps to add translations:
+
+1. After registering, you are asked to select your languages. This is optional but allows weblate to only show you relevant translations.
+2. In the navigation click on `Projects` and then `Browse all projects`.
+3. Select Tandoor and on the top-right hand corner, select `Watch project Tandoor` (click on `Not watching`).
+4. Go back to the dashboard. It now shows you the relevant translations for your languages. Click on the pencil icon to get started.
+
+!!! info "Creating a new language"
+To create a new language you must first select Tandoor (the project) and then a component.
+Here you will have the option to add the language. Afterwards you can also simply add it to the other components as well.
+Once a new language is (partially) finished let me know on GitHub so I can add it to the language-switcher in Tandoor itself.
+
+There is also [a lot of documentation](https://docs.weblate.org/en/latest/user/translating.html) available from Weblate directly.
+
+
+
+It is also possible to provide the translations directly by creating a new language
+using `manage.py makemessages -l -i venv`. Once finished, simply open a PR with the changed files. This sometimes causes issues merging
+with weblate, so I would prefer the use of weblate.
diff --git a/docs/contribute/vscode.md b/docs/contribute/vscode.md
new file mode 100644
index 000000000..ab938d0ed
--- /dev/null
+++ b/docs/contribute/vscode.md
@@ -0,0 +1,45 @@
+Configurations for debugging django, volar, testing, linting and formatting are all include in the project files.
+
+## Extensions
+
+VSCode can be configured to format and lint on save instead of manually formatting files before submitting a pull request.
+To enable auto-formatting and linting install the following extensions in VSCode:
+
+Name: Flake8
+Publisher: Microsoft
+VS Marketplace Link: https://marketplace.visualstudio.com/items?itemName=ms-python.flake8
+
+Name: yapf
+Publisher: EeyoreLee
+VS Marketplace Link: https://marketplace.visualstudio.com/items?itemName=eeyore.yapf
+
+Name: isort
+Publisher: Microsoft
+VS Marketplace Link: https://marketplace.visualstudio.com/items?itemName=ms-python.isort
+
+Name: Vue - Official
+Publisher: Vue
+VS Marketplace Link: https://marketplace.visualstudio.com/items?itemName=Vue.volar
+
+Name: Prettier - Code formatter
+Publisher: Prettier
+VS Marketplace Link: https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode
+
+
+!!! hint
+ Adding a comma at the end of a list will trigger yapf to put each element of the list on a new line
+
+## VSCode Tasks
+
+There are a number of built in tasks that are available. Here are a few of the key ones:
+
+- `Setup Dev Server` - Runs all the prerequisite steps so that the dev server can be run inside VSCode.
+- `Setup Tests` - Runs all prerequisites so tests can be run inside VSCode.
+
+Once these are run, you should be able to run/debug a django server in VSCode as well as run/debug tests directly through VSCode.
+There are also a few other tasks specified in case you have specific development needs:
+
+- `Run Dev Server` - Runs a django development server not connected to VSCode.
+- `Run all pytests` - Runs all the pytests outside of VSCode.
+- `Yarn Serve` - Runs development Vue.js server not connected to VSCode. Useful if you want to make Vue changes and see them in realtime.
+- `Serve Documentation` - Runs a documentation server. Useful if you want to see how changes to documentation show up.
diff --git a/docs/system/settings.md b/docs/system/settings.md
deleted file mode 100644
index 990090fd8..000000000
--- a/docs/system/settings.md
+++ /dev/null
@@ -1,27 +0,0 @@
-Following is a description of the different settings for a space
-
-!!! WARNING WIP
- Some settings and especially this page is work in Progress and the settings may
- behave differently the described here.
-
-## Use Plural form
-
-Default Value: `off`
-
-This setting enables tandoor to display a plural form of a food or unit, if the
-plural version is entered for the food or unit. The plural version is displayed if the
-amount needed for a recipe is greater than 1 and will be adjusted to the current amount.
-
-In addition, this setting enables two new settings for an ingredient:
-
-- Always show the plural version of the food: This will always display the plural version for
-a food, even if the amount is below or equal to 1. Requirement for this setting to activate
-is a plural version available in the database.
-- Always show the plural version of the unit: This will always display the plural version for
-a unit, even if the amount is below or equal to 1. Requirement for this setting to activate
-is a plural version available in the database.
-
-!!! WARNING Note
- This setting is only meant to be a very simple version to enable some kind of pluralization
- for food and units. This feature may not meet your needs, but pluralization is a difficult
- topic and was discussed [here](https://github.com/TandoorRecipes/recipes/pull/1860).
diff --git a/mkdocs.yml b/mkdocs.yml
index 68d2b70a9..f51e36471 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -36,6 +36,7 @@ nav:
- TrueNAS Portainer: install/truenas_portainer.md
- WSL: install/wsl.md
- ArchLinux: install/archlinux.md
+ - HomeAssistant: install/homeassistant.md
- Manual: install/manual.md
- Other setups: install/other.md
- Features:
@@ -46,10 +47,21 @@ nav:
- Connectors: features/connectors.md
- Storages and Sync: features/external_recipes.md
- Import/Export: features/import_export.md
+ - Telegram bot: features/telegram_bot.md
- System:
- Configuration: system/configuration.md
- Updating: system/updating.md
+ - Migrate sqlite to postgres: system/migration_sqlite-postgres.md
- Permission System: system/permissions.md
- Backup: system/backup.md
- - Contributing: contribute.md
+ - Contributing:
+ - Overview: contribute/contribute.md
+ - Translations: contribute/translations.md
+ - Documentation: contribute/documentation.md
+ - Code: contribute/guidelines.md
+ - Installation: contribute/installation.md
+ - IDE Setup:
+ - VSCode: contribute/vscode.md
+ - PyCharm: contribute/pycharm.md
+ - Related Projects: contribute/related.md
- FAQ: faq.md
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 000000000..33bc62172
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,12 @@
+[tool.yapf]
+column_limit = 179
+based_on_style = "pep8"
+DISABLE_ENDING_COMMA_HEURISTIC = false
+COALESCE_BRACKETS = true
+DEDENT_CLOSING_BRACKETS = true
+FORCE_MULTILINE_DICT = false
+
+[tool.isort]
+multi_line_output = 5
+skip = [".gitignore", ".dockerignore"]
+line_length = 179
diff --git a/recipes/settings.py b/recipes/settings.py
index 268c8f68b..d9b2b6b24 100644
--- a/recipes/settings.py
+++ b/recipes/settings.py
@@ -104,10 +104,30 @@ MESSAGE_TAGS = {messages.ERROR: 'danger'}
# Application definition
INSTALLED_APPS = [
- 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages',
- 'django.contrib.sites', 'django.contrib.staticfiles', 'django.contrib.postgres', 'oauth2_provider', 'django_tables2', 'corsheaders', 'crispy_forms',
- 'crispy_bootstrap4', 'rest_framework', 'rest_framework.authtoken', 'django_cleanup.apps.CleanupConfig', 'webpack_loader', 'django_js_reverse', 'hcaptcha', 'allauth',
- 'allauth.account', 'allauth.socialaccount', 'cookbook.apps.CookbookConfig', 'treebeard',
+ 'django.contrib.admin',
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ 'django.contrib.messages',
+ 'django.contrib.sites',
+ 'django.contrib.staticfiles',
+ 'django.contrib.postgres',
+ 'oauth2_provider',
+ 'django_tables2',
+ 'corsheaders',
+ 'crispy_forms',
+ 'crispy_bootstrap4',
+ 'rest_framework',
+ 'rest_framework.authtoken',
+ 'django_cleanup.apps.CleanupConfig',
+ 'webpack_loader',
+ 'django_js_reverse',
+ 'hcaptcha',
+ 'allauth',
+ 'allauth.account',
+ 'allauth.socialaccount',
+ 'cookbook.apps.CookbookConfig',
+ 'treebeard',
]
PLUGINS_DIRECTORY = os.path.join(BASE_DIR, 'recipes', 'plugins')
@@ -177,10 +197,18 @@ ENABLE_PDF_EXPORT = bool(int(os.getenv('ENABLE_PDF_EXPORT', False)))
EXPORT_FILE_CACHE_DURATION = int(os.getenv('EXPORT_FILE_CACHE_DURATION', 600))
MIDDLEWARE = [
- 'corsheaders.middleware.CorsMiddleware', 'django.middleware.security.SecurityMiddleware', 'whitenoise.middleware.WhiteNoiseMiddleware',
- 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware',
- 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.locale.LocaleMiddleware',
- 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'cookbook.helper.scope_middleware.ScopeMiddleware', 'allauth.account.middleware.AccountMiddleware',
+ 'corsheaders.middleware.CorsMiddleware',
+ 'django.middleware.security.SecurityMiddleware',
+ 'whitenoise.middleware.WhiteNoiseMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.middleware.common.CommonMiddleware',
+ 'django.middleware.csrf.CsrfViewMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+ 'django.contrib.messages.middleware.MessageMiddleware',
+ 'django.middleware.locale.LocaleMiddleware',
+ 'django.middleware.clickjacking.XFrameOptionsMiddleware',
+ 'cookbook.helper.scope_middleware.ScopeMiddleware',
+ 'allauth.account.middleware.AccountMiddleware',
]
if DEBUG_TOOLBAR:
@@ -211,7 +239,11 @@ if LDAP_AUTH:
AUTH_LDAP_START_TLS = bool(int(os.getenv('AUTH_LDAP_START_TLS', False)))
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(os.getenv('AUTH_LDAP_USER_SEARCH_BASE_DN'), ldap.SCOPE_SUBTREE, os.getenv('AUTH_LDAP_USER_SEARCH_FILTER_STR', '(uid=%(user)s)'), )
+ AUTH_LDAP_USER_SEARCH = LDAPSearch(
+ os.getenv('AUTH_LDAP_USER_SEARCH_BASE_DN'),
+ ldap.SCOPE_SUBTREE,
+ os.getenv('AUTH_LDAP_USER_SEARCH_FILTER_STR', '(uid=%(user)s)'),
+ )
AUTH_LDAP_USER_ATTR_MAP = ast.literal_eval(os.getenv('AUTH_LDAP_USER_ATTR_MAP')) if os.getenv('AUTH_LDAP_USER_ATTR_MAP') else {
'first_name': 'givenName',
'last_name': 'sn',
@@ -238,7 +270,10 @@ if LDAP_AUTH:
},
}
-AUTHENTICATION_BACKENDS += ['django.contrib.auth.backends.ModelBackend', 'allauth.account.auth_backends.AuthenticationBackend', ]
+AUTHENTICATION_BACKENDS += [
+ 'django.contrib.auth.backends.ModelBackend',
+ 'allauth.account.auth_backends.AuthenticationBackend',
+]
# django allauth site id
SITE_ID = int(os.getenv('ALLAUTH_SITE_ID', 1))
@@ -252,15 +287,20 @@ if REMOTE_USER_AUTH:
# Password validation
# https://docs.djangoproject.com/en/2.0/ref/settings/#auth-password-validators
-AUTH_PASSWORD_VALIDATORS = [{
- 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
-}, {
- 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
-}, {
- 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
-}, {
- 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
-}, ]
+AUTH_PASSWORD_VALIDATORS = [
+ {
+ 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
+ },
+]
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
@@ -269,25 +309,35 @@ READ_SCOPE = 'read'
WRITE_SCOPE = 'write'
REST_FRAMEWORK = {
- 'DEFAULT_AUTHENTICATION_CLASSES':
- ('rest_framework.authentication.SessionAuthentication', 'oauth2_provider.contrib.rest_framework.OAuth2Authentication', 'rest_framework.authentication.BasicAuthentication',
- ),
- 'DEFAULT_PERMISSION_CLASSES': ['rest_framework.permissions.IsAuthenticated', ],
+ 'DEFAULT_AUTHENTICATION_CLASSES': (
+ 'rest_framework.authentication.SessionAuthentication',
+ 'oauth2_provider.contrib.rest_framework.OAuth2Authentication',
+ 'rest_framework.authentication.BasicAuthentication',
+ ),
+ 'DEFAULT_PERMISSION_CLASSES': [
+ 'rest_framework.permissions.IsAuthenticated',
+ ],
}
ROOT_URLCONF = 'recipes.urls'
-TEMPLATES = [{
- 'BACKEND': 'django.template.backends.django.DjangoTemplates',
- 'DIRS': [os.path.join(BASE_DIR, 'templates'), os.path.join(BASE_DIR, 'cookbook', 'templates')],
- 'APP_DIRS': True,
- 'OPTIONS': {
- 'context_processors': [
- 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth',
- 'django.contrib.messages.context_processors.messages', 'django.template.context_processors.media', 'cookbook.helper.context_processors.context_settings',
- ],
+TEMPLATES = [
+ {
+ 'BACKEND': 'django.template.backends.django.DjangoTemplates',
+ 'DIRS': [os.path.join(BASE_DIR, 'templates'), os.path.join(BASE_DIR, 'cookbook', 'templates')],
+ 'APP_DIRS': True,
+ 'OPTIONS': {
+ 'context_processors': [
+ 'django.template.context_processors.debug',
+ 'django.template.context_processors.request',
+ 'django.contrib.auth.context_processors.auth',
+ 'django.contrib.messages.context_processors.messages',
+ 'django.template.context_processors.media',
+ 'cookbook.helper.context_processors.context_settings',
+ ],
+ },
},
-}, ]
+]
WSGI_APPLICATION = 'recipes.wsgi.application'
@@ -358,7 +408,12 @@ def setup_database(db_url=None, db_options=None, db_engine=None, pg_host=None, p
DATABASES = setup_database()
-CACHES = {'default': {'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', 'LOCATION': 'default', }}
+CACHES = {
+ 'default': {
+ 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
+ 'LOCATION': 'default',
+ }
+}
# Vue webpack settings
VUE_DIR = os.path.join(BASE_DIR, 'vue')
@@ -402,9 +457,25 @@ USE_L10N = True
USE_TZ = True
-LANGUAGES = [('hy', _('Armenian ')), ('bg', _('Bulgarian')), ('ca', _('Catalan')), ('cs', _('Czech')), ('da', _('Danish')), ('nl', _('Dutch')), ('en', _('English')),
- ('fr', _('French')), ('de', _('German')), ('hu', _('Hungarian')), ('it', _('Italian')), ('lv', _('Latvian')), ('nb', _('Norwegian ')), ('pl', _('Polish')),
- ('ru', _('Russian')), ('es', _('Spanish')), ('sv', _('Swedish')), ]
+LANGUAGES = [
+ ('hy', _('Armenian ')),
+ ('bg', _('Bulgarian')),
+ ('ca', _('Catalan')),
+ ('cs', _('Czech')),
+ ('da', _('Danish')),
+ ('nl', _('Dutch')),
+ ('en', _('English')),
+ ('fr', _('French')),
+ ('de', _('German')),
+ ('hu', _('Hungarian')),
+ ('it', _('Italian')),
+ ('lv', _('Latvian')),
+ ('nb', _('Norwegian ')),
+ ('pl', _('Polish')),
+ ('ru', _('Russian')),
+ ('es', _('Spanish')),
+ ('sv', _('Swedish')),
+]
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.0/howto/static-files/
@@ -470,7 +541,13 @@ ACCOUNT_EMAIL_SUBJECT_PREFIX = os.getenv('ACCOUNT_EMAIL_SUBJECT_PREFIX', '[Tando
ACCOUNT_FORMS = {'signup': 'cookbook.forms.AllAuthSignupForm', 'reset_password': 'cookbook.forms.CustomPasswordResetForm'}
ACCOUNT_EMAIL_UNKNOWN_ACCOUNTS = False
-ACCOUNT_RATE_LIMITS = {"change_password": "1/m/user", "reset_password": "1/m/ip,1/m/key", "reset_password_from_key": "1/m/ip", "signup": "5/m/ip", "login": "5/m/ip", }
+ACCOUNT_RATE_LIMITS = {
+ "change_password": "1/m/user",
+ "reset_password": "1/m/ip,1/m/key",
+ "reset_password_from_key": "1/m/ip",
+ "signup": "5/m/ip",
+ "login": "5/m/ip",
+}
DISABLE_EXTERNAL_CONNECTORS = bool(int(os.getenv('DISABLE_EXTERNAL_CONNECTORS', False)))
EXTERNAL_CONNECTORS_QUEUE_SIZE = int(os.getenv('EXTERNAL_CONNECTORS_QUEUE_SIZE', 100))
diff --git a/requirements.txt b/requirements.txt
index b457d2c46..51189602f 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -10,7 +10,7 @@ drf-writable-nested==0.7.0
django-oauth-toolkit==2.3.0
django-debug-toolbar==4.3.0
bleach==6.0.0
-gunicorn==21.2.0
+gunicorn==22.0.0
lxml==5.1.0
Markdown==3.5.1
Pillow==10.3.0
@@ -30,7 +30,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
@@ -43,7 +43,7 @@ django-auth-ldap==4.6.0
pyppeteer==2.0.0
validators==0.20.0
pytube==15.0.0
-aiohttp==3.9.3
+aiohttp==3.9.4
# Development
pytest==8.0.0
@@ -53,3 +53,6 @@ pytest-factoryboy==2.6.0
pytest-html==4.1.1
pytest-asyncio==0.23.5
pytest-xdist==3.5.0
+autopep8==2.0.4
+flake8==6.1.0
+yapf==0.40.2
diff --git a/vue/package.json b/vue/package.json
index 02c8fe1fc..98d8f337b 100644
--- a/vue/package.json
+++ b/vue/package.json
@@ -61,6 +61,7 @@
"babel-eslint": "^10.1.0",
"eslint": "^8.46.0",
"eslint-plugin-vue": "^8.7.1",
+ "prettier": "^3.2.5",
"typescript": "~5.3.3",
"vue-cli-plugin-i18n": "^2.3.2",
"webpack-bundle-tracker": "3.0.1",
diff --git a/vue/src/components/PropertyViewComponent.vue b/vue/src/components/PropertyViewComponent.vue
index 6cceeccd7..b7c1a871a 100644
--- a/vue/src/components/PropertyViewComponent.vue
+++ b/vue/src/components/PropertyViewComponent.vue
@@ -67,7 +67,7 @@
- {{ f.value }} {{ selected_property.unit }}
+ {{ roundDecimals(f.value) }} {{ selected_property.unit }}
diff --git a/vue/yarn.lock b/vue/yarn.lock
index 663c77a06..eed173f13 100644
--- a/vue/yarn.lock
+++ b/vue/yarn.lock
@@ -9136,6 +9136,11 @@ prepend-http@^2.0.0:
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da"
integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==
+prettier@^3.2.5:
+ version "3.2.5"
+ resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.2.5.tgz#e52bc3090586e824964a8813b09aba6233b28368"
+ integrity sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==
+
pretty-bytes@^5.3.0, pretty-bytes@^5.4.1:
version "5.6.0"
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb"