diff --git a/.gitignore b/.gitignore
index c4d38e4181..ddc9e96930 100644
--- a/.gitignore
+++ b/.gitignore
@@ -84,3 +84,5 @@ vue3/.vite
# Configs
vetur.config.js
venv/
+.idea/easy-i18n.xml
+cookbook/static/vue3
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
index 4b52f246da..d2b92c1a5e 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -35,6 +35,12 @@ RUN apk add --no-cache --virtual .build-deps gcc musl-dev postgresql-dev zlib-de
#Copy project and execute it.
COPY . ./
+HEALTHCHECK --interval=30s \
+ --timeout=5s \
+ --start-period=10s \
+ --retries=3 \
+ CMD [ "/usr/bin/wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:8080/openapi" ]
+
# collect information from git repositories
RUN /opt/recipes/venv/bin/python version.py
# delete git repositories to reduce image size
diff --git a/cookbook/forms.py b/cookbook/forms.py
index bda48e3dd5..071431a4f0 100644
--- a/cookbook/forms.py
+++ b/cookbook/forms.py
@@ -1,6 +1,8 @@
from datetime import datetime
+
from allauth.account.forms import ResetPasswordForm, SignupForm
+from allauth.socialaccount.forms import SignupForm as SocialSignupForm
from django import forms
from django.conf import settings
from django.core.exceptions import ValidationError
@@ -14,15 +16,13 @@
class SelectWidget(widgets.Select):
-
class Media:
- js = ('custom/js/form_select.js', )
+ js = ('custom/js/form_select.js',)
class MultiSelectWidget(widgets.SelectMultiple):
-
class Media:
- js = ('custom/js/form_multiselect.js', )
+ js = ('custom/js/form_multiselect.js',)
# Yes there are some stupid browsers that still dont support this but
@@ -139,7 +139,7 @@ class CommentForm(forms.ModelForm):
class Meta:
model = Comment
- fields = ('text', )
+ fields = ('text',)
labels = {'text': _('Add your comment: '), }
widgets = {'text': forms.Textarea(attrs={'rows': 2, 'cols': 15}), }
@@ -161,7 +161,6 @@ class Meta:
help_texts = {'url': _('Leave empty for dropbox and enter only base url for nextcloud (/remote.php/webdav/ is added automatically)'), }
-
class ConnectorConfigForm(forms.ModelForm):
enabled = forms.BooleanField(
help_text="Is the connector enabled",
@@ -315,6 +314,18 @@ def signup(self, request, user):
pass
+class AllAuthSocialSignupForm(SocialSignupForm):
+ terms = forms.BooleanField(label=_('Accept Terms and Privacy'))
+
+ def __init__(self, **kwargs):
+ super().__init__(**kwargs)
+ if settings.PRIVACY_URL == '' and settings.TERMS_URL == '':
+ self.fields.pop('terms')
+
+ def signup(self, request, user):
+ pass
+
+
class CustomPasswordResetForm(ResetPasswordForm):
captcha = hCaptchaField()
@@ -345,12 +356,13 @@ class Meta:
help_texts = {
'search': _('Select type method of search. Click here for full description of choices.'), 'lookup':
- _('Use fuzzy matching on units, keywords and ingredients when editing and importing recipes.'), 'unaccent':
- _('Fields to search ignoring accents. Selecting this option can improve or degrade search quality depending on language'), 'icontains':
- _("Fields to search for partial matches. (e.g. searching for 'Pie' will return 'pie' and 'piece' and 'soapie')"), 'istartswith':
- _("Fields to search for beginning of word matches. (e.g. searching for 'sa' will return 'salad' and 'sandwich')"), 'trigram':
- _("Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find 'recipe'.) Note: this option will conflict with 'web' and 'raw' methods of search."), 'fulltext':
- _("Fields to full text search. Note: 'web', 'phrase', and 'raw' search methods only function with fulltext fields."),
+ _('Use fuzzy matching on units, keywords and ingredients when editing and importing recipes.'), 'unaccent':
+ _('Fields to search ignoring accents. Selecting this option can improve or degrade search quality depending on language'), 'icontains':
+ _("Fields to search for partial matches. (e.g. searching for 'Pie' will return 'pie' and 'piece' and 'soapie')"), 'istartswith':
+ _("Fields to search for beginning of word matches. (e.g. searching for 'sa' will return 'salad' and 'sandwich')"), 'trigram':
+ _("Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find 'recipe'.) Note: this option will conflict with 'web' and 'raw' methods of search."),
+ 'fulltext':
+ _("Fields to full text search. Note: 'web', 'phrase', and 'raw' search methods only function with fulltext fields."),
}
labels = {
@@ -360,5 +372,5 @@ class Meta:
widgets = {
'search': SelectWidget, 'unaccent': MultiSelectWidget, 'icontains': MultiSelectWidget, 'istartswith': MultiSelectWidget, 'trigram': MultiSelectWidget, 'fulltext':
- MultiSelectWidget,
+ MultiSelectWidget,
}
diff --git a/cookbook/locale/it/LC_MESSAGES/django.po b/cookbook/locale/it/LC_MESSAGES/django.po
index cc06bd4ae6..e66531254f 100644
--- a/cookbook/locale/it/LC_MESSAGES/django.po
+++ b/cookbook/locale/it/LC_MESSAGES/django.po
@@ -12,7 +12,7 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-08-01 15:04+0200\n"
-"PO-Revision-Date: 2024-11-01 06:58+0000\n"
+"PO-Revision-Date: 2024-12-09 00:58+0000\n"
"Last-Translator: Vincenzo Reale \n"
"Language-Team: Italian \n"
@@ -21,7 +21,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
-"X-Generator: Weblate 5.6.2\n"
+"X-Generator: Weblate 5.8.4\n"
#: .\cookbook\forms.py:45
msgid ""
@@ -520,7 +520,7 @@ msgstr "Web"
#: .\cookbook\models.py:1411 .\cookbook\templates\search_info.html:47
msgid "Raw"
-msgstr "Raw"
+msgstr "Crudo"
#: .\cookbook\models.py:1467
msgid "Food Alias"
@@ -1440,8 +1440,9 @@ msgid ""
"\"noreferrer noopener\" target=\"_blank\">this one."
msgstr ""
"Le tabelle in markdown sono difficili da creare a mano. Si raccomanda "
-"l'utilizzo di un editor di come questo."
+"l'utilizzo di un editor di tabelle come questo."
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:157
@@ -2203,8 +2204,8 @@ msgstr ""
" Le migrazioni non andate a buon fine probabilmente causeranno il "
"malfunzionamento di parti importanti dell'applicazione.\n"
" Se una migrazione non riesce, assicurati di avere la versione "
-"più recente e, in tal caso, pubblica il registro della migrazione e la "
-"panoramica di seguito in una segnalazione di problema su GitHub.\n"
+"più recente e, in tal caso, pubblica il registro della migrazione e il "
+"riepilogo che segue in una segnalazione di problema su GitHub.\n"
" "
#: .\cookbook\templates\system.html:182
@@ -2765,7 +2766,7 @@ msgid ""
"but not recommended as some features only work with postgres databases."
msgstr ""
"Questa applicazione non è in esecuzione con un database Postgres. Va bene, "
-"ma non è consigliato perché alcune funzionalità sono disponibili solo con un "
+"ma non è consigliato perché alcune funzionalità sono disponibili solo con "
"database Postgres."
#: .\cookbook\views\views.py:360
diff --git a/cookbook/templates/account/signup.html b/cookbook/templates/account/signup.html
index baceb2cb15..fc29ca2b2f 100644
--- a/cookbook/templates/account/signup.html
+++ b/cookbook/templates/account/signup.html
@@ -1,6 +1,5 @@
{% extends "base.html" %}
{% load crispy_forms_filters %}
-{% load crispy_forms_filters %}
{% load i18n %}
{% block title %}{% trans 'Register' %}{% endblock %}
diff --git a/cookbook/templates/socialaccount/signup.html b/cookbook/templates/socialaccount/signup.html
index 6779752fa2..79608c1ffd 100644
--- a/cookbook/templates/socialaccount/signup.html
+++ b/cookbook/templates/socialaccount/signup.html
@@ -16,7 +16,6 @@
{% trans "Sign Up" %}
{% if redirect_field_value %}
{% endif %}
-
{{ form.username |as_crispy_field }}
@@ -30,7 +29,7 @@
{% trans "Sign Up" %}
{{ form.terms |as_crispy_field }}
- {% trans 'I accept the follwoing' %}
+ {% trans 'I accept the following' %}
{% if TERMS_URL != '' %}
{% trans 'Terms and Conditions' %}
diff --git a/cookbook/templates/system.html b/cookbook/templates/system.html
index e3b7d20243..8f7d83910d 100644
--- a/cookbook/templates/system.html
+++ b/cookbook/templates/system.html
@@ -12,9 +12,9 @@
{% trans 'System' %}
{% blocktrans %}
- Django Recipes is an open source free software application. It can be found on
- GitHub.
- Changelogs can be found here.
+ Tandoor Recipes is an open source free software application. It can be found on
+ GitHub.
+ Changelogs can be found here.
{% endblocktrans %}
{% trans 'System Information' %}
diff --git a/cookbook/views/api.py b/cookbook/views/api.py
index 003e7b9bd2..ab6ef27e37 100644
--- a/cookbook/views/api.py
+++ b/cookbook/views/api.py
@@ -1455,13 +1455,21 @@ def post(self, request, *args, **kwargs):
url = serializer.validated_data.get('url', None)
data = unquote(serializer.validated_data.get('data', None))
+
+ duplicate = False
+ if url:
+ # Check for existing recipes with provided url
+ existing_recipe = Recipe.objects.filter(source_url=url).first()
+ if existing_recipe:
+ duplicate = True
+
if not url and not data:
return Response({'error': True, 'msg': _('Nothing to do.')}, status=status.HTTP_400_BAD_REQUEST)
elif url and not data:
if re.match('^(https?://)?(www\\.youtube\\.com|youtu\\.be)/.+$', url):
if validate_import_url(url):
- return Response({'recipe_json': get_from_youtube_scraper(url, request), 'recipe_images': [], }, status=status.HTTP_200_OK)
+ return Response({'recipe_json': get_from_youtube_scraper(url, request), 'recipe_images': [], 'duplicate': duplicate}, status=status.HTTP_200_OK)
if re.match('^(.)*/view/recipe/[0-9]+/[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$', url):
recipe_json = requests.get(
url.replace('/view/recipe/', '/api/recipe/').replace(re.split('/view/recipe/[0-9]+', url)[1], '') + '?share='
@@ -1476,7 +1484,7 @@ def post(self, request, *args, **kwargs):
filetype=pathlib.Path(recipe_json['image']).suffix),
name=f'{uuid.uuid4()}_{recipe.pk}{pathlib.Path(recipe_json["image"]).suffix}')
recipe.save()
- return Response({'link': request.build_absolute_uri(reverse('view_recipe', args={recipe.pk}))}, status=status.HTTP_201_CREATED)
+ return Response({'link': request.build_absolute_uri(reverse('view_recipe', args={recipe.pk})), 'duplicate': duplicate}, status=status.HTTP_201_CREATED)
else:
try:
if validate_import_url(url):
@@ -1511,6 +1519,7 @@ def post(self, request, *args, **kwargs):
return Response({
'recipe_json': helper.get_from_scraper(scrape, request),
'recipe_images': list(dict.fromkeys(get_images_from_soup(scrape.soup, url))),
+ 'duplicate': duplicate
},
status=status.HTTP_200_OK)
diff --git a/docs/install/manual.md b/docs/install/manual.md
index c6ec34abab..c5cd7412bb 100644
--- a/docs/install/manual.md
+++ b/docs/install/manual.md
@@ -3,7 +3,7 @@
These instructions are inspired from a standard django/gunicorn/postgresql instructions ([for example](https://www.digitalocean.com/community/tutorials/how-to-set-up-django-with-postgres-nginx-and-gunicorn-on-ubuntu-16-04))
!!! warning
- Make sure to use Python 3.10 or higher, and ensure that `pip` is associated with Python 3. Depending on your system configuration, using `python` or `pip` might default to Python 2. Make sure your machine has at least 2048 MB of memory; otherwise, the `yarn build` process may fail with the error: `FATAL ERROR: Reached heap limit - Allocation failed: JavaScript heap out of memory`.
+ Make sure to use at least Python 3.10 (although 3.12 is preferred) or higher, and ensure that `pip` is associated with Python 3. Depending on your system configuration, using `python` or `pip` might default to Python 2. Make sure your machine has at least 2048 MB of memory; otherwise, the `yarn build` process may fail with the error: `FATAL ERROR: Reached heap limit - Allocation failed: JavaScript heap out of memory`.
## Prerequisites
diff --git a/recipes/settings.py b/recipes/settings.py
index 061add493f..1d2a72f024 100644
--- a/recipes/settings.py
+++ b/recipes/settings.py
@@ -66,7 +66,6 @@
},
}
-
# allow djangos wsgi server to server mediafiles
GUNICORN_MEDIA = bool(int(os.getenv('GUNICORN_MEDIA', False)))
@@ -247,14 +246,14 @@
]
if DEBUG_TOOLBAR:
- MIDDLEWARE += ('debug_toolbar.middleware.DebugToolbarMiddleware', )
- INSTALLED_APPS += ('debug_toolbar', )
+ MIDDLEWARE += ('debug_toolbar.middleware.DebugToolbarMiddleware',)
+ INSTALLED_APPS += ('debug_toolbar',)
SORT_TREE_BY_NAME = bool(int(os.getenv('SORT_TREE_BY_NAME', False)))
DISABLE_TREE_FIX_STARTUP = bool(int(os.getenv('DISABLE_TREE_FIX_STARTUP', False)))
if bool(int(os.getenv('SQL_DEBUG', False))):
- MIDDLEWARE += ('recipes.middleware.SqlPrintingMiddleware', )
+ MIDDLEWARE += ('recipes.middleware.SqlPrintingMiddleware',)
if ENABLE_METRICS:
MIDDLEWARE += 'django_prometheus.middleware.PrometheusAfterMiddleware',
@@ -294,7 +293,6 @@
"handlers": ["console"]
}
-
AUTHENTICATION_BACKENDS += [
'django.contrib.auth.backends.ModelBackend',
'allauth.account.auth_backends.AuthenticationBackend',
@@ -564,6 +562,9 @@ def setup_database(db_url=None, db_options=None, db_engine=None, pg_host=None, p
# ACCOUNT_SIGNUP_FORM_CLASS = 'cookbook.forms.AllAuthSignupForm'
ACCOUNT_FORMS = {'signup': 'cookbook.forms.AllAuthSignupForm', 'reset_password': 'cookbook.forms.CustomPasswordResetForm'}
+SOCIALACCOUNT_FORMS = {
+ 'signup': 'cookbook.forms.AllAuthSocialSignupForm',
+}
ACCOUNT_EMAIL_UNKNOWN_ACCOUNTS = False
ACCOUNT_RATE_LIMITS = {
diff --git a/requirements.txt b/requirements.txt
index 5273135cb3..39c18c6187 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,9 +1,9 @@
-Django==4.2.16
+Django==4.2.17
cryptography===43.0.1
django-annoying==0.10.6
django-cleanup==8.0.0
django-crispy-forms==2.3
-crispy-bootstrap4==2024.1
+crispy-bootstrap4==2024.10
django-tables2==2.7.0
djangorestframework==3.15.2
drf-writable-nested==0.7.0
@@ -20,13 +20,13 @@ requests==2.32.3
six==1.16.0
webdavclient3==3.14.6
whitenoise==6.7.0
-icalendar==5.0.11
+icalendar==6.1.0
pyyaml==6.0.2
uritemplate==4.1.1
beautifulsoup4==4.12.3
microdata==0.8.0
mock==5.1.0
-Jinja2==3.1.4
+Jinja2==3.1.5
django-webpack-loader==3.0.1
git+https://github.com/BITSOLVER/django-js-reverse@071e304fd600107bc64bbde6f2491f1fe049ec82
django-allauth==0.61.1
@@ -48,9 +48,9 @@ redis==5.2.0
# Development
pytest==8.0.0
-pytest-django==4.8.0
+pytest-django==4.9.0
pytest-cov===5.0.0
-pytest-factoryboy==2.6.0
+pytest-factoryboy==2.7.0
pytest-html==4.1.1
pytest-asyncio==0.23.5
pytest-xdist==3.6.1
diff --git a/vue/src/apps/ImportView/ImportView.vue b/vue/src/apps/ImportView/ImportView.vue
index b1743944ce..fbd5a0402b 100644
--- a/vue/src/apps/ImportView/ImportView.vue
+++ b/vue/src/apps/ImportView/ImportView.vue
@@ -83,6 +83,11 @@
+
+
+ {{ duplicateWarning }}
+
+
@@ -463,6 +468,7 @@ export default {
},
// URL import
LS_IMPORT_RECENT: 'import_recent_urls', //TODO use central helper to manage all local storage keys (and maybe even access)
+ duplicateWarning: '',
website_url: '',
website_url_list: '',
import_multiple: false,
@@ -643,6 +649,12 @@ export default {
return
}
+ if ('duplicate' in response.data && response.data['duplicate']) {
+ this.duplicateWarning = "A recipe with this URL already exists.";
+ } else {
+ this.duplicateWarning = "";
+ }
+
this.loading = false
this.recipe_json = response.data['recipe_json'];
@@ -763,6 +775,16 @@ export default {