mirror of
https://github.com/TandoorRecipes/recipes.git
synced 2026-01-01 04:10:06 -05:00
Merge branch 'feature/shopping_list_v2' of https://github.com/vabene1111/recipes into feature/shopping_list_v2
# Conflicts: # cookbook/static/django_js_reverse/reverse.js # cookbook/tests/api/test_api_shopping_recipe.py # vue/src/apps/ShoppingListView/ShoppingListView.vue
This commit is contained in:
@@ -155,13 +155,14 @@ class ImportExportBase(forms.Form):
|
|||||||
OPENEATS = 'OPENEATS'
|
OPENEATS = 'OPENEATS'
|
||||||
PLANTOEAT = 'PLANTOEAT'
|
PLANTOEAT = 'PLANTOEAT'
|
||||||
COOKBOOKAPP = 'COOKBOOKAPP'
|
COOKBOOKAPP = 'COOKBOOKAPP'
|
||||||
|
COPYMETHAT = 'COPYMETHAT'
|
||||||
|
|
||||||
type = forms.ChoiceField(choices=(
|
type = forms.ChoiceField(choices=(
|
||||||
(DEFAULT, _('Default')), (PAPRIKA, 'Paprika'), (NEXTCLOUD, 'Nextcloud Cookbook'),
|
(DEFAULT, _('Default')), (PAPRIKA, 'Paprika'), (NEXTCLOUD, 'Nextcloud Cookbook'),
|
||||||
(MEALIE, 'Mealie'), (CHOWDOWN, 'Chowdown'), (SAFRON, 'Safron'), (CHEFTAP, 'ChefTap'),
|
(MEALIE, 'Mealie'), (CHOWDOWN, 'Chowdown'), (SAFRON, 'Safron'), (CHEFTAP, 'ChefTap'),
|
||||||
(PEPPERPLATE, 'Pepperplate'), (RECETTETEK, 'RecetteTek'), (RECIPESAGE, 'Recipe Sage'), (DOMESTICA, 'Domestica'),
|
(PEPPERPLATE, 'Pepperplate'), (RECETTETEK, 'RecetteTek'), (RECIPESAGE, 'Recipe Sage'), (DOMESTICA, 'Domestica'),
|
||||||
(MEALMASTER, 'MealMaster'), (REZKONV, 'RezKonv'), (OPENEATS, 'Openeats'), (RECIPEKEEPER, 'Recipe Keeper'),
|
(MEALMASTER, 'MealMaster'), (REZKONV, 'RezKonv'), (OPENEATS, 'Openeats'), (RECIPEKEEPER, 'Recipe Keeper'),
|
||||||
(PLANTOEAT, 'Plantoeat'), (COOKBOOKAPP, 'CookBookApp'),
|
(PLANTOEAT, 'Plantoeat'), (COOKBOOKAPP, 'CookBookApp'), (COPYMETHAT, 'CopyMeThat'),
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
84
cookbook/integration/copymethat.py
Normal file
84
cookbook/integration/copymethat.py
Normal file
@@ -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"})
|
||||||
@@ -5,6 +5,7 @@ import uuid
|
|||||||
from io import BytesIO, StringIO
|
from io import BytesIO, StringIO
|
||||||
from zipfile import BadZipFile, ZipFile
|
from zipfile import BadZipFile, ZipFile
|
||||||
|
|
||||||
|
from bs4 import Tag
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from django.core.files import File
|
from django.core.files import File
|
||||||
from django.db import IntegrityError
|
from django.db import IntegrityError
|
||||||
@@ -16,7 +17,7 @@ from django_scopes import scope
|
|||||||
from cookbook.forms import ImportExportBase
|
from cookbook.forms import ImportExportBase
|
||||||
from cookbook.helper.image_processing import get_filetype, handle_image
|
from cookbook.helper.image_processing import get_filetype, handle_image
|
||||||
from cookbook.models import Keyword, Recipe
|
from cookbook.models import Keyword, Recipe
|
||||||
from recipes.settings import DATABASES, DEBUG
|
from recipes.settings import DEBUG
|
||||||
|
|
||||||
|
|
||||||
class Integration:
|
class Integration:
|
||||||
@@ -153,9 +154,17 @@ class Integration:
|
|||||||
file_list.append(z)
|
file_list.append(z)
|
||||||
il.total_recipes += len(file_list)
|
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:
|
for z in file_list:
|
||||||
try:
|
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)
|
recipe.keywords.add(self.keyword)
|
||||||
il.msg += f'{recipe.pk} - {recipe.name} \n'
|
il.msg += f'{recipe.pk} - {recipe.name} \n'
|
||||||
self.handle_duplicates(recipe, import_duplicates)
|
self.handle_duplicates(recipe, import_duplicates)
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -67,7 +67,7 @@
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
{% if not request.user.is_authenticated or request.user.userpreference.theme == request.user.userpreference.TANDOOR %}
|
{% 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;">
|
<img class="brand-icon" src="{% static 'assets/brand_logo.png' %}" alt="" style="height: 5vh;">
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -76,6 +76,7 @@
|
|||||||
<option value="CHEFTAP">Cheftap</option>
|
<option value="CHEFTAP">Cheftap</option>
|
||||||
<option value="CHOWDOWN">Chowdown</option>
|
<option value="CHOWDOWN">Chowdown</option>
|
||||||
<option value="COOKBOOKAPP">CookBookApp</option>
|
<option value="COOKBOOKAPP">CookBookApp</option>
|
||||||
|
<option value="COPYMETHAT">CopyMeThat</option>
|
||||||
<option value="DOMESTICA">Domestica</option>
|
<option value="DOMESTICA">Domestica</option>
|
||||||
<option value="MEALIE">Mealie</option>
|
<option value="MEALIE">Mealie</option>
|
||||||
<option value="MEALMASTER">Mealmaster</option>
|
<option value="MEALMASTER">Mealmaster</option>
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ from datetime import timedelta
|
|||||||
|
|
||||||
import factory
|
import factory
|
||||||
import pytest
|
import pytest
|
||||||
|
# work around for bug described here https://stackoverflow.com/a/70312265/15762829
|
||||||
|
from django.conf import settings
|
||||||
from django.contrib import auth
|
from django.contrib import auth
|
||||||
from django.forms import model_to_dict
|
from django.forms import model_to_dict
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
@@ -14,6 +16,11 @@ from cookbook.models import Food, Ingredient, ShoppingListEntry, Step
|
|||||||
from cookbook.tests.factories import (IngredientFactory, MealPlanFactory, RecipeFactory,
|
from cookbook.tests.factories import (IngredientFactory, MealPlanFactory, RecipeFactory,
|
||||||
StepFactory, UserFactory)
|
StepFactory, UserFactory)
|
||||||
|
|
||||||
|
if settings.DATABASES['default']['ENGINE'] in ['django.db.backends.postgresql_psycopg2',
|
||||||
|
'django.db.backends.postgresql']:
|
||||||
|
from django.db.backends.postgresql.features import DatabaseFeatures
|
||||||
|
DatabaseFeatures.can_defer_constraint_checks = False
|
||||||
|
|
||||||
SHOPPING_LIST_URL = 'api:shoppinglistentry-list'
|
SHOPPING_LIST_URL = 'api:shoppinglistentry-list'
|
||||||
SHOPPING_RECIPE_URL = 'api:recipe-shopping'
|
SHOPPING_RECIPE_URL = 'api:recipe-shopping'
|
||||||
|
|
||||||
@@ -43,7 +50,7 @@ def recipe(request, space_1, u1_s1):
|
|||||||
# steps__food_recipe_count = params.get('steps__food_recipe_count', {})
|
# steps__food_recipe_count = params.get('steps__food_recipe_count', {})
|
||||||
params['created_by'] = params.get('created_by', auth.get_user(u1_s1))
|
params['created_by'] = params.get('created_by', auth.get_user(u1_s1))
|
||||||
params['space'] = space_1
|
params['space'] = space_1
|
||||||
return RecipeFactory.create(**params)
|
return RecipeFactory(**params)
|
||||||
|
|
||||||
# return RecipeFactory.create(
|
# return RecipeFactory.create(
|
||||||
# steps__recipe_count=steps__recipe_count,
|
# steps__recipe_count=steps__recipe_count,
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ from django.utils.translation import gettext as _
|
|||||||
from cookbook.forms import ExportForm, ImportForm, ImportExportBase
|
from cookbook.forms import ExportForm, ImportForm, ImportExportBase
|
||||||
from cookbook.helper.permission_helper import group_required
|
from cookbook.helper.permission_helper import group_required
|
||||||
from cookbook.integration.cookbookapp import CookBookApp
|
from cookbook.integration.cookbookapp import CookBookApp
|
||||||
|
from cookbook.integration.copymethat import CopyMeThat
|
||||||
from cookbook.integration.pepperplate import Pepperplate
|
from cookbook.integration.pepperplate import Pepperplate
|
||||||
from cookbook.integration.cheftap import ChefTap
|
from cookbook.integration.cheftap import ChefTap
|
||||||
from cookbook.integration.chowdown import Chowdown
|
from cookbook.integration.chowdown import Chowdown
|
||||||
@@ -65,6 +66,8 @@ def get_integration(request, export_type):
|
|||||||
return Plantoeat(request, export_type)
|
return Plantoeat(request, export_type)
|
||||||
if export_type == ImportExportBase.COOKBOOKAPP:
|
if export_type == ImportExportBase.COOKBOOKAPP:
|
||||||
return CookBookApp(request, export_type)
|
return CookBookApp(request, export_type)
|
||||||
|
if export_type == ImportExportBase.COPYMETHAT:
|
||||||
|
return CopyMeThat(request, export_type)
|
||||||
|
|
||||||
|
|
||||||
@group_required('user')
|
@group_required('user')
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ Overview of the capabilities of the different integrations.
|
|||||||
| OpenEats | ✔️ | ❌ | ⌚ |
|
| OpenEats | ✔️ | ❌ | ⌚ |
|
||||||
| Plantoeat | ✔️ | ❌ | ✔ |
|
| Plantoeat | ✔️ | ❌ | ✔ |
|
||||||
| CookBookApp | ✔️ | ⌚ | ✔️ |
|
| CookBookApp | ✔️ | ⌚ | ✔️ |
|
||||||
|
| CopyMeThat | ✔️ | ❌ | ✔️ |
|
||||||
|
|
||||||
✔ = implemented, ❌ = not implemented and not possible/planned, ⌚ = not yet implemented
|
✔ = implemented, ❌ = not implemented and not possible/planned, ⌚ = not yet implemented
|
||||||
|
|
||||||
@@ -218,3 +219,7 @@ Plan to eat allows you to export a text file containing all your recipes. Simply
|
|||||||
## CookBookApp
|
## CookBookApp
|
||||||
|
|
||||||
CookBookApp can export .zip files containing .html files. Upload the entire ZIP to Tandoor to import all included recipes.
|
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.
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
!!! success "Recommended Installation"
|
!!! success "Recommended Installation"
|
||||||
Setting up this application using Docker is recommended. This does not mean that other options are bad, just that
|
Setting up this application using Docker is recommended. This does not mean that other options are bad, just that
|
||||||
support is much easier for this setup.
|
support is much easier for this setup.
|
||||||
|
|
||||||
It is possible to install this application using many Docker configurations.
|
It is possible to install this application using many Docker configurations.
|
||||||
|
|
||||||
@@ -34,17 +34,17 @@ file in the GitHub repository to verify if additional environment variables are
|
|||||||
|
|
||||||
### Versions
|
### Versions
|
||||||
|
|
||||||
There are different versions (tags) released on docker hub.
|
There are different versions (tags) released on docker hub.
|
||||||
|
|
||||||
- **latest** Default image. The one you should use if you don't know that you need anything else.
|
- **latest** Default image. The one you should use if you don't know that you need anything else.
|
||||||
- **beta** Partially stable version that gets updated every now and then. Expect to have some problems.
|
- **beta** Partially stable version that gets updated every now and then. Expect to have some problems.
|
||||||
- **develop** If you want the most bleeding edge version with potentially many breaking changes feel free to use this version (I don't recommend it!).
|
- **develop** If you want the most bleeding edge version with potentially many breaking changes feel free to use this version (I don't recommend it!).
|
||||||
- **X.Y.Z** each released version has its own image. If you need to revert to an old version or want to make sure you stay on one specific use these tags.
|
- **X.Y.Z** each released version has its own image. If you need to revert to an old version or want to make sure you stay on one specific use these tags.
|
||||||
|
|
||||||
!!! danger "No Downgrading"
|
!!! danger "No Downgrading"
|
||||||
There is currently no way to migrate back to an older version as there is no mechanism to downgrade the database.
|
There is currently no way to migrate back to an older version as there is no mechanism to downgrade the database.
|
||||||
You could probably do it but I cannot help you with that. Choose wisely if you want to use the unstable images.
|
You could probably do it but I cannot help you with that. Choose wisely if you want to use the unstable images.
|
||||||
That said **beta** should usually be working if you like frequent updates and new stuff.
|
That said **beta** should usually be working if you like frequent updates and new stuff.
|
||||||
|
|
||||||
## Docker Compose
|
## Docker Compose
|
||||||
|
|
||||||
@@ -52,9 +52,9 @@ The main, and also recommended, installation option is to install this applicati
|
|||||||
|
|
||||||
1. Choose your `docker-compose.yml` from the examples below.
|
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**.
|
||||||
```shell
|
```shell
|
||||||
wget https://raw.githubusercontent.com/vabene1111/recipes/develop/.env.template -O .env
|
wget https://raw.githubusercontent.com/vabene1111/recipes/develop/.env.template -O .env
|
||||||
```
|
```
|
||||||
3. Start your container using `docker-compose up -d`.
|
3. Start your container using `docker-compose up -d`.
|
||||||
|
|
||||||
### Plain
|
### Plain
|
||||||
@@ -65,29 +65,30 @@ This configuration exposes the application through an nginx web server on port 8
|
|||||||
wget https://raw.githubusercontent.com/vabene1111/recipes/develop/docs/install/docker/plain/docker-compose.yml
|
wget https://raw.githubusercontent.com/vabene1111/recipes/develop/docs/install/docker/plain/docker-compose.yml
|
||||||
```
|
```
|
||||||
|
|
||||||
~~~yaml
|
```yaml
|
||||||
{% include "./docker/plain/docker-compose.yml" %}
|
{ % include "./docker/plain/docker-compose.yml" % }
|
||||||
~~~
|
```
|
||||||
|
|
||||||
### Reverse Proxy
|
### Reverse Proxy
|
||||||
|
|
||||||
Most deployments will likely use a reverse proxy.
|
Most deployments will likely use a reverse proxy.
|
||||||
|
|
||||||
#### Traefik
|
#### Traefik
|
||||||
|
|
||||||
If you use traefik, this configuration is the one for you.
|
If you use traefik, this configuration is the one for you.
|
||||||
|
|
||||||
!!! info
|
!!! info
|
||||||
Traefik can be a little confusing to setup.
|
Traefik can be a little confusing to setup.
|
||||||
Please refer to [their excellent documentation](https://doc.traefik.io/traefik/). If that does not help,
|
Please refer to [their excellent documentation](https://doc.traefik.io/traefik/). If that does not help,
|
||||||
[this little example](traefik.md) might be for you.
|
[this little example](traefik.md) might be for you.
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
wget https://raw.githubusercontent.com/vabene1111/recipes/develop/docs/install/docker/traefik-nginx/docker-compose.yml
|
wget https://raw.githubusercontent.com/vabene1111/recipes/develop/docs/install/docker/traefik-nginx/docker-compose.yml
|
||||||
```
|
```
|
||||||
|
|
||||||
~~~yaml
|
```yaml
|
||||||
{% include "./docker/traefik-nginx/docker-compose.yml" %}
|
{ % include "./docker/traefik-nginx/docker-compose.yml" % }
|
||||||
~~~
|
```
|
||||||
|
|
||||||
#### nginx-proxy
|
#### nginx-proxy
|
||||||
|
|
||||||
@@ -97,6 +98,7 @@ in combination with [jrcs's letsencrypt companion](https://hub.docker.com/r/jrcs
|
|||||||
Please refer to the appropriate documentation on how to setup the reverse proxy and networks.
|
Please refer to the appropriate documentation on how to setup the reverse proxy and networks.
|
||||||
|
|
||||||
Remember to add the appropriate environment variables to `.env` file:
|
Remember to add the appropriate environment variables to `.env` file:
|
||||||
|
|
||||||
```
|
```
|
||||||
VIRTUAL_HOST=
|
VIRTUAL_HOST=
|
||||||
LETSENCRYPT_HOST=
|
LETSENCRYPT_HOST=
|
||||||
@@ -107,9 +109,31 @@ LETSENCRYPT_EMAIL=
|
|||||||
wget https://raw.githubusercontent.com/vabene1111/recipes/develop/docs/install/docker/nginx-proxy/docker-compose.yml
|
wget https://raw.githubusercontent.com/vabene1111/recipes/develop/docs/install/docker/nginx-proxy/docker-compose.yml
|
||||||
```
|
```
|
||||||
|
|
||||||
~~~yaml
|
```yaml
|
||||||
{% include "./docker/nginx-proxy/docker-compose.yml" %}
|
{ % 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.
|
||||||
|
|
||||||
#### Nginx Swag by LinuxServer
|
#### Nginx Swag by LinuxServer
|
||||||
[This container](https://github.com/linuxserver/docker-swag) is an all in one solution created by LinuxServer.io
|
[This container](https://github.com/linuxserver/docker-swag) is an all in one solution created by LinuxServer.io
|
||||||
@@ -136,6 +160,7 @@ Please refer to the [appropriate documentation](https://github.com/linuxserver/d
|
|||||||
## Additional Information
|
## Additional Information
|
||||||
|
|
||||||
### Nginx vs Gunicorn
|
### Nginx vs Gunicorn
|
||||||
|
|
||||||
All examples use an additional `nginx` container to serve mediafiles and act as the forward facing webserver.
|
All examples use an additional `nginx` container to serve mediafiles and act as the forward facing webserver.
|
||||||
This is **technically not required** but **very much recommended**.
|
This is **technically not required** but **very much recommended**.
|
||||||
|
|
||||||
@@ -144,14 +169,14 @@ the WSGi server that handles the Python execution, explicitly state that it is n
|
|||||||
You will also likely not see any decrease in performance or a lot of space used as nginx is a very light container.
|
You will also likely not see any decrease in performance or a lot of space used as nginx is a very light container.
|
||||||
|
|
||||||
!!! info
|
!!! info
|
||||||
Even if you run behind a reverse proxy as described above, using an additional nginx container is the recommended option.
|
Even if you run behind a reverse proxy as described above, using an additional nginx container is the recommended option.
|
||||||
|
|
||||||
If you run a small private deployment and don't care about performance, security and whatever else feel free to run
|
If you run a small private deployment and don't care about performance, security and whatever else feel free to run
|
||||||
without a ngix container.
|
without a ngix container.
|
||||||
|
|
||||||
!!! warning
|
!!! warning
|
||||||
When running without nginx make sure to enable `GUNICORN_MEDIA` in the `.env`. Without it, media files will be uploaded
|
When running without nginx make sure to enable `GUNICORN_MEDIA` in the `.env`. Without it, media files will be uploaded
|
||||||
but not shown on the page.
|
but not shown on the page.
|
||||||
|
|
||||||
For additional information please refer to the [0.9.0 Release](https://github.com/vabene1111/recipes/releases?after=0.9.0)
|
For additional information please refer to the [0.9.0 Release](https://github.com/vabene1111/recipes/releases?after=0.9.0)
|
||||||
and [Issue 201](https://github.com/vabene1111/recipes/issues/201) where these topics have been discussed.
|
and [Issue 201](https://github.com/vabene1111/recipes/issues/201) where these topics have been discussed.
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ CORS_ORIGIN_ALLOW_ALL = True
|
|||||||
|
|
||||||
LOGIN_REDIRECT_URL = "index"
|
LOGIN_REDIRECT_URL = "index"
|
||||||
LOGOUT_REDIRECT_URL = "index"
|
LOGOUT_REDIRECT_URL = "index"
|
||||||
|
ACCOUNT_LOGOUT_REDIRECT_URL = "index"
|
||||||
|
|
||||||
SESSION_EXPIRE_AT_BROWSER_CLOSE = False
|
SESSION_EXPIRE_AT_BROWSER_CLOSE = False
|
||||||
SESSION_COOKIE_AGE = 365 * 60 * 24 * 60
|
SESSION_COOKIE_AGE = 365 * 60 * 24 * 60
|
||||||
|
|||||||
@@ -304,7 +304,7 @@ export default {
|
|||||||
this.settings?.search_keywords?.length === 0 &&
|
this.settings?.search_keywords?.length === 0 &&
|
||||||
this.settings?.search_foods?.length === 0 &&
|
this.settings?.search_foods?.length === 0 &&
|
||||||
this.settings?.search_books?.length === 0 &&
|
this.settings?.search_books?.length === 0 &&
|
||||||
this.settings?.pagination_page === 1 &&
|
// this.settings?.pagination_page === 1 &&
|
||||||
!this.random_search &&
|
!this.random_search &&
|
||||||
this.settings?.search_ratings === undefined
|
this.settings?.search_ratings === undefined
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -30,22 +30,24 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col col-md-12">
|
<div class="col col-md-12">
|
||||||
<div role="tablist">
|
<div role="tablist">
|
||||||
<div class="row justify-content-md-center w-75" v-if="entrymode">
|
<!-- add to shopping form -->
|
||||||
<div class="col col-md-2">
|
<b-row class="row justify-content-md-center" v-if="entrymode">
|
||||||
|
<b-col cols="12" sm="4" md="2">
|
||||||
<b-form-input min="1" type="number" :description="$t('Amount')" v-model="new_item.amount"></b-form-input>
|
<b-form-input min="1" type="number" :description="$t('Amount')" v-model="new_item.amount"></b-form-input>
|
||||||
</div>
|
</b-col>
|
||||||
<div class="col col-md-3">
|
<b-col cols="12" sm="8" md="3">
|
||||||
<lookup-input :form="formUnit" :model="Models.UNIT" @change="new_item.unit = $event" :show_label="false" />
|
<lookup-input :form="formUnit" :model="Models.UNIT" @change="new_item.unit = $event" :show_label="false" />
|
||||||
</div>
|
</b-col>
|
||||||
<div class="col col-md-4">
|
<b-col cols="12" sm="8" md="4">
|
||||||
<lookup-input :form="formFood" :model="Models.FOOD" @change="new_item.food = $event" :show_label="false" />
|
<lookup-input :form="formFood" :model="Models.FOOD" @change="new_item.food = $event" :show_label="false" />
|
||||||
</div>
|
</b-col>
|
||||||
<div class="col col-md-1">
|
<b-col cols="12" sm="4" md="1">
|
||||||
<b-button variant="link" class="px-0">
|
<b-button variant="link" class="px-0">
|
||||||
<i class="btn fas fa-cart-plus fa-lg px-0 text-success" @click="addItem" />
|
<i class="btn fas fa-cart-plus fa-lg px-0 text-success" @click="addItem" />
|
||||||
</b-button>
|
</b-button>
|
||||||
</div>
|
</b-col>
|
||||||
</div>
|
</b-row>
|
||||||
|
<!-- shopping list table -->
|
||||||
<div v-if="items && items.length > 0">
|
<div v-if="items && items.length > 0">
|
||||||
<div v-for="(done, x) in Sections" :key="x">
|
<div v-for="(done, x) in Sections" :key="x">
|
||||||
<div v-if="x == 'true'">
|
<div v-if="x == 'true'">
|
||||||
|
|||||||
Reference in New Issue
Block a user