Merge branch 'develop' into feature/shopping-ui
# Conflicts: # cookbook/serializer.py
This commit is contained in:
commit
3e51bdc7f0
@ -450,7 +450,7 @@ class SpacePreferenceForm(forms.ModelForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Space
|
model = Space
|
||||||
|
|
||||||
fields = ('food_inherit', 'reset_food_inherit', 'use_plural')
|
fields = ('food_inherit', 'reset_food_inherit',)
|
||||||
|
|
||||||
help_texts = {
|
help_texts = {
|
||||||
'food_inherit': _('Fields on food that should be inherited by default.'),
|
'food_inherit': _('Fields on food that should be inherited by default.'),
|
||||||
|
@ -14,12 +14,14 @@ class IngredientObject(object):
|
|||||||
unit = ""
|
unit = ""
|
||||||
food = ""
|
food = ""
|
||||||
note = ""
|
note = ""
|
||||||
|
numeric_amount = 0
|
||||||
|
|
||||||
def __init__(self, ingredient):
|
def __init__(self, ingredient):
|
||||||
if ingredient.no_amount:
|
if ingredient.no_amount:
|
||||||
self.amount = ""
|
self.amount = ""
|
||||||
else:
|
else:
|
||||||
self.amount = f"<scalable-number v-bind:number='{bleach.clean(str(ingredient.amount))}' v-bind:factor='ingredient_factor'></scalable-number>"
|
self.amount = f"<scalable-number v-bind:number='{bleach.clean(str(ingredient.amount))}' v-bind:factor='ingredient_factor'></scalable-number>"
|
||||||
|
self.numeric_amount = float(ingredient.amount)
|
||||||
if ingredient.unit:
|
if ingredient.unit:
|
||||||
if ingredient.unit.plural_name in (None, ""):
|
if ingredient.unit.plural_name in (None, ""):
|
||||||
self.unit = bleach.clean(str(ingredient.unit))
|
self.unit = bleach.clean(str(ingredient.unit))
|
||||||
@ -83,9 +85,12 @@ def render_instructions(step): # TODO deduplicate markdown cleanup code
|
|||||||
for i in step.ingredients.all():
|
for i in step.ingredients.all():
|
||||||
ingredients.append(IngredientObject(i))
|
ingredients.append(IngredientObject(i))
|
||||||
|
|
||||||
|
def scale(number):
|
||||||
|
return f"<scalable-number v-bind:number='{bleach.clean(str(number))}' v-bind:factor='ingredient_factor'></scalable-number>"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
template = Template(instructions)
|
template = Template(instructions)
|
||||||
instructions = template.render(ingredients=ingredients)
|
instructions = template.render(ingredients=ingredients, scale=scale)
|
||||||
except TemplateSyntaxError:
|
except TemplateSyntaxError:
|
||||||
return _('Could not parse template code.') + ' Error: Template Syntax broken'
|
return _('Could not parse template code.') + ' Error: Template Syntax broken'
|
||||||
except UndefinedError:
|
except UndefinedError:
|
||||||
|
17
cookbook/migrations/0209_remove_space_use_plural.py
Normal file
17
cookbook/migrations/0209_remove_space_use_plural.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# Generated by Django 4.2.7 on 2024-01-28 07:42
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('cookbook', '0208_space_app_name_userpreference_max_owned_spaces'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='space',
|
||||||
|
name='use_plural',
|
||||||
|
),
|
||||||
|
]
|
@ -303,7 +303,6 @@ class Space(ExportModelOperationsMixin('space'), models.Model):
|
|||||||
max_recipes = models.IntegerField(default=0)
|
max_recipes = models.IntegerField(default=0)
|
||||||
max_file_storage_mb = models.IntegerField(default=0, help_text=_('Maximum file storage for space in MB. 0 for unlimited, -1 to disable file upload.'))
|
max_file_storage_mb = models.IntegerField(default=0, help_text=_('Maximum file storage for space in MB. 0 for unlimited, -1 to disable file upload.'))
|
||||||
max_users = models.IntegerField(default=0)
|
max_users = models.IntegerField(default=0)
|
||||||
use_plural = models.BooleanField(default=True)
|
|
||||||
allow_sharing = models.BooleanField(default=True)
|
allow_sharing = models.BooleanField(default=True)
|
||||||
no_sharing_limit = models.BooleanField(default=False)
|
no_sharing_limit = models.BooleanField(default=False)
|
||||||
demo = models.BooleanField(default=False)
|
demo = models.BooleanField(default=False)
|
||||||
|
@ -315,9 +315,8 @@ class SpaceSerializer(WritableNestedModelSerializer):
|
|||||||
fields = (
|
fields = (
|
||||||
'id', 'name', 'created_by', 'created_at', 'message', 'max_recipes', 'max_file_storage_mb', 'max_users',
|
'id', 'name', 'created_by', 'created_at', 'message', 'max_recipes', 'max_file_storage_mb', 'max_users',
|
||||||
'allow_sharing', 'demo', 'food_inherit', 'user_count', 'recipe_count', 'file_size_mb',
|
'allow_sharing', 'demo', 'food_inherit', 'user_count', 'recipe_count', 'file_size_mb',
|
||||||
'image', 'nav_logo', 'space_theme', 'custom_space_theme', 'nav_bg_color', 'nav_text_color', 'use_plural',
|
'image', 'nav_logo', 'space_theme', 'custom_space_theme', 'nav_bg_color', 'nav_text_color',
|
||||||
'logo_color_32', 'logo_color_128', 'logo_color_144', 'logo_color_180', 'logo_color_192', 'logo_color_512',
|
'logo_color_32', 'logo_color_128', 'logo_color_144', 'logo_color_180', 'logo_color_192', 'logo_color_512', 'logo_color_svg',)
|
||||||
'logo_color_svg',)
|
|
||||||
read_only_fields = (
|
read_only_fields = (
|
||||||
'id', 'created_by', 'created_at', 'max_recipes', 'max_file_storage_mb', 'max_users', 'allow_sharing',
|
'id', 'created_by', 'created_at', 'max_recipes', 'max_file_storage_mb', 'max_users', 'allow_sharing',
|
||||||
'demo',)
|
'demo',)
|
||||||
|
@ -3,7 +3,7 @@ from django.templatetags.static import static
|
|||||||
from django_scopes import scopes_disabled
|
from django_scopes import scopes_disabled
|
||||||
|
|
||||||
from cookbook.models import UserPreference, UserFile, Space
|
from cookbook.models import UserPreference, UserFile, Space
|
||||||
from recipes.settings import STICKY_NAV_PREF_DEFAULT, UNAUTHENTICATED_THEME_FROM_SPACE
|
from recipes.settings import STICKY_NAV_PREF_DEFAULT, UNAUTHENTICATED_THEME_FROM_SPACE, FORCE_THEME_FROM_SPACE
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
|
||||||
@ -15,11 +15,14 @@ def theme_values(request):
|
|||||||
|
|
||||||
def get_theming_values(request):
|
def get_theming_values(request):
|
||||||
space = None
|
space = None
|
||||||
if getattr(request,'space',None):
|
if getattr(request, 'space', None):
|
||||||
space = request.space
|
space = request.space
|
||||||
if not request.user.is_authenticated and UNAUTHENTICATED_THEME_FROM_SPACE > 0:
|
if not request.user.is_authenticated and UNAUTHENTICATED_THEME_FROM_SPACE > 0 and FORCE_THEME_FROM_SPACE == 0:
|
||||||
with scopes_disabled():
|
with scopes_disabled():
|
||||||
space = Space.objects.filter(id=UNAUTHENTICATED_THEME_FROM_SPACE).first()
|
space = Space.objects.filter(id=UNAUTHENTICATED_THEME_FROM_SPACE).first()
|
||||||
|
if FORCE_THEME_FROM_SPACE:
|
||||||
|
with scopes_disabled():
|
||||||
|
space = Space.objects.filter(id=FORCE_THEME_FROM_SPACE).first()
|
||||||
|
|
||||||
themes = {
|
themes = {
|
||||||
UserPreference.BOOTSTRAP: 'themes/bootstrap.min.css',
|
UserPreference.BOOTSTRAP: 'themes/bootstrap.min.css',
|
||||||
|
@ -204,6 +204,22 @@ server {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Tandoor does not support directly serving of images, as explained in the [Nginx vs Gunicorn"](#nginx-vs-gunicorn) section. If you are already using nginx to serve as a reverse proxy, you can configure it to serve images as well.
|
||||||
|
|
||||||
|
Add the following directly after the `location /` context:
|
||||||
|
|
||||||
|
```
|
||||||
|
location /media/ {
|
||||||
|
root /media/;
|
||||||
|
index index.html index.htm;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Make sure you also update your `docker-compose.yml` file to mount the `mediafiles` directory. If you are using the [Plain](#plain) deployment, you do not need to make any changes. If you are using nginx to act as a reverse proxy for other apps, it may not be optimal to have `mediafiles` mounted to `/media`. In that case, adjust the directory declarations as needed, utilizing nginx's [`alias`](https://nginx.org/en/docs/http/ngx_http_core_module.html#alias) if needed.
|
||||||
|
|
||||||
|
!!!note
|
||||||
|
Use `alias` if your mount point directory is not the same as the URL request path. Tandoor media files are requested from `$http_host/media/recipes/xxx.jpg`. This means if you are mounting to a directory that does **NOT** end in `./media`, you will need to use `alias`.
|
||||||
|
|
||||||
!!!note
|
!!!note
|
||||||
Don't forget to [download and configure](#docker-compose) your ```.env``` file!
|
Don't forget to [download and configure](#docker-compose) your ```.env``` file!
|
||||||
|
|
||||||
|
@ -560,6 +560,15 @@ With this setting you can specify the ID of a space of which the appearance sett
|
|||||||
UNAUTHENTICATED_THEME_FROM_SPACE=
|
UNAUTHENTICATED_THEME_FROM_SPACE=
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Force Theme
|
||||||
|
> default `0` - options `1-X` (space ID)
|
||||||
|
|
||||||
|
Similar to the Default theme but forces the theme upon all users (authenticated/unauthenticated) and all spaces
|
||||||
|
|
||||||
|
```
|
||||||
|
FORCE_THEME_FROM_SPACE=
|
||||||
|
```
|
||||||
|
|
||||||
### Rate Limiting / Performance
|
### Rate Limiting / Performance
|
||||||
|
|
||||||
#### Shopping auto sync
|
#### Shopping auto sync
|
||||||
|
@ -59,6 +59,7 @@ KJ_PREF_DEFAULT = bool(int(os.getenv('KJ_PREF_DEFAULT', False)))
|
|||||||
STICKY_NAV_PREF_DEFAULT = bool(int(os.getenv('STICKY_NAV_PREF_DEFAULT', True)))
|
STICKY_NAV_PREF_DEFAULT = bool(int(os.getenv('STICKY_NAV_PREF_DEFAULT', True)))
|
||||||
MAX_OWNED_SPACES_PREF_DEFAULT = int(os.getenv('MAX_OWNED_SPACES_PREF_DEFAULT', 100))
|
MAX_OWNED_SPACES_PREF_DEFAULT = int(os.getenv('MAX_OWNED_SPACES_PREF_DEFAULT', 100))
|
||||||
UNAUTHENTICATED_THEME_FROM_SPACE = int(os.getenv('UNAUTHENTICATED_THEME_FROM_SPACE', 0))
|
UNAUTHENTICATED_THEME_FROM_SPACE = int(os.getenv('UNAUTHENTICATED_THEME_FROM_SPACE', 0))
|
||||||
|
FORCE_THEME_FROM_SPACE = int(os.getenv('FORCE_THEME_FROM_SPACE', 0))
|
||||||
|
|
||||||
# minimum interval that users can set for automatic sync of shopping lists
|
# minimum interval that users can set for automatic sync of shopping lists
|
||||||
SHOPPING_MIN_AUTOSYNC_INTERVAL = int(
|
SHOPPING_MIN_AUTOSYNC_INTERVAL = int(
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
<cookbook-toc :recipes="recipes" v-if="current_page === 1" v-on:switchRecipe="switchRecipe($event)"></cookbook-toc>
|
<cookbook-toc :recipes="recipes" v-if="current_page === 1" v-on:switchRecipe="switchRecipe($event)"></cookbook-toc>
|
||||||
</transition>
|
</transition>
|
||||||
<transition name="flip" mode="out-in">
|
<transition name="flip" mode="out-in">
|
||||||
<recipe-card :recipe="display_recipes[1].recipe_content" v-if="current_page > 1 && display_recipes.length === 2" :key="display_recipes[1].recipe" :use_plural="use_plural"></recipe-card>
|
<recipe-card :recipe="display_recipes[1].recipe_content" v-if="current_page > 1 && display_recipes.length === 2" :key="display_recipes[1].recipe" ></recipe-card>
|
||||||
</transition>
|
</transition>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-1" @click="swipeLeft" style="cursor: pointer"></div>
|
<div class="col-md-1" @click="swipeLeft" style="cursor: pointer"></div>
|
||||||
@ -57,10 +57,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted(){
|
mounted(){
|
||||||
let apiClient = new ApiApiFactory()
|
|
||||||
apiClient.retrieveSpace(window.ACTIVE_SPACE_ID).then(r => {
|
|
||||||
this.use_plural = r.data.use_plural
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@ -69,7 +66,6 @@ export default {
|
|||||||
bounce_left: false,
|
bounce_left: false,
|
||||||
bounce_right: false,
|
bounce_right: false,
|
||||||
cookbook_editing: false,
|
cookbook_editing: false,
|
||||||
use_plural: false,
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -76,8 +76,7 @@
|
|||||||
<div class="col-md-10 offset-md-2">
|
<div class="col-md-10 offset-md-2">
|
||||||
<generic-horizontal-card v-for="child in item[children]"
|
<generic-horizontal-card v-for="child in item[children]"
|
||||||
v-bind:key="child.id"
|
v-bind:key="child.id"
|
||||||
:item="child" :model="model"
|
:item="child" :model="model"
|
||||||
:use_plural="use_plural"
|
|
||||||
@item-action="$emit('item-action', $event)"></generic-horizontal-card>
|
@item-action="$emit('item-action', $event)"></generic-horizontal-card>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -160,7 +159,6 @@ export default {
|
|||||||
recipe_count: { type: String, default: "numrecipe" },
|
recipe_count: { type: String, default: "numrecipe" },
|
||||||
recipes: { type: String, default: "recipes" },
|
recipes: { type: String, default: "recipes" },
|
||||||
show_context_menu: { type: Boolean, default: true },
|
show_context_menu: { type: Boolean, default: true },
|
||||||
use_plural: { type: Boolean, default: false},
|
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -24,7 +24,6 @@
|
|||||||
<ingredient-component
|
<ingredient-component
|
||||||
:ingredient="i"
|
:ingredient="i"
|
||||||
:ingredient_factor="ingredient_factor"
|
:ingredient_factor="ingredient_factor"
|
||||||
:use_plural="use_plural"
|
|
||||||
:key="i.id"
|
:key="i.id"
|
||||||
:detailed="detailed"
|
:detailed="detailed"
|
||||||
@checked-state-changed="$emit('checked-state-changed', $event)"
|
@checked-state-changed="$emit('checked-state-changed', $event)"
|
||||||
@ -64,7 +63,6 @@ export default {
|
|||||||
recipe: {type: Number},
|
recipe: {type: Number},
|
||||||
ingredient_factor: {type: Number, default: 1},
|
ingredient_factor: {type: Number, default: 1},
|
||||||
servings: {type: Number, default: 1},
|
servings: {type: Number, default: 1},
|
||||||
use_plural: {type: Boolean, default: false},
|
|
||||||
detailed: {type: Boolean, default: true},
|
detailed: {type: Boolean, default: true},
|
||||||
header: {type: Boolean, default: false},
|
header: {type: Boolean, default: false},
|
||||||
recipe_list: {type: Number, default: undefined},
|
recipe_list: {type: Number, default: undefined},
|
||||||
|
@ -34,7 +34,6 @@
|
|||||||
<div v-for="i in r.steps.flatMap((s) => s.ingredients)" v-bind:key="i.id">
|
<div v-for="i in r.steps.flatMap((s) => s.ingredients)" v-bind:key="i.id">
|
||||||
<table class="table table-sm mb-0">
|
<table class="table table-sm mb-0">
|
||||||
<ingredient-component
|
<ingredient-component
|
||||||
:use_plural="true"
|
|
||||||
:key="i.id"
|
:key="i.id"
|
||||||
:detailed="true"
|
:detailed="true"
|
||||||
:ingredient="i"
|
:ingredient="i"
|
||||||
|
@ -145,7 +145,6 @@ export default {
|
|||||||
props: {
|
props: {
|
||||||
recipe: Object,
|
recipe: Object,
|
||||||
meal_plan: Object,
|
meal_plan: Object,
|
||||||
use_plural: { type: Boolean, default: false },
|
|
||||||
footer_text: String,
|
footer_text: String,
|
||||||
footer_icon: String,
|
footer_icon: String,
|
||||||
detailed: { type: Boolean, default: true },
|
detailed: { type: Boolean, default: true },
|
||||||
|
@ -90,7 +90,6 @@
|
|||||||
:index="index"
|
:index="index"
|
||||||
:start_time="start_time"
|
:start_time="start_time"
|
||||||
:force_ingredients="true"
|
:force_ingredients="true"
|
||||||
:use_plural="use_plural"
|
|
||||||
></step-component>
|
></step-component>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -147,10 +146,6 @@ export default {
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
use_plural: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
step_time: function() {
|
step_time: function() {
|
||||||
|
Loading…
Reference in New Issue
Block a user