mirror of
https://github.com/TandoorRecipes/recipes.git
synced 2025-12-27 04:00:48 -05:00
Compare commits
124 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6c22fb0ef4 | ||
|
|
f869fc85ae | ||
|
|
f435110810 | ||
|
|
36a0693b49 | ||
|
|
32a565c1e0 | ||
|
|
f3fd087b81 | ||
|
|
8e8d25071c | ||
|
|
129cc76624 | ||
|
|
0e48ee60cb | ||
|
|
a784421b5c | ||
|
|
314d8d41d1 | ||
|
|
02fd73f43e | ||
|
|
f12c47603d | ||
|
|
e341b96249 | ||
|
|
7ed5606e9d | ||
|
|
f3fffc1a3b | ||
|
|
c5a3e22542 | ||
|
|
c6990ef2d8 | ||
|
|
12a438752b | ||
|
|
8642298eda | ||
|
|
6260aba668 | ||
|
|
c56489cfa0 | ||
|
|
33b84c3d6a | ||
|
|
45454eb27b | ||
|
|
f628823ab2 | ||
|
|
781cbc16f7 | ||
|
|
28e6c9d922 | ||
|
|
d96bac47c4 | ||
|
|
1dafeb4db9 | ||
|
|
c02cdf6e68 | ||
|
|
3628835fd4 | ||
|
|
747b5937eb | ||
|
|
2fde8f9b52 | ||
|
|
7c06da89f0 | ||
|
|
2b17b12252 | ||
|
|
07afa7957a | ||
|
|
f0488c93d2 | ||
|
|
86a3d87276 | ||
|
|
fd14d79c12 | ||
|
|
4e852374cc | ||
|
|
898b1699b7 | ||
|
|
e92dbcd63e | ||
|
|
0c28e7e1b4 | ||
|
|
f8c1411e4d | ||
|
|
f1a194e166 | ||
|
|
f374e0cb27 | ||
|
|
6f7a41f3b8 | ||
|
|
a562b571b5 | ||
|
|
d71420484c | ||
|
|
bc7758e233 | ||
|
|
0f5185c677 | ||
|
|
1e6096d5c8 | ||
|
|
bc2a092e79 | ||
|
|
9809f58c28 | ||
|
|
589302a87d | ||
|
|
173eaf44a0 | ||
|
|
576c62b8a1 | ||
|
|
da57e656eb | ||
|
|
b42e6ac0f6 | ||
|
|
cebcf266fc | ||
|
|
d5ebb0047d | ||
|
|
6d553035db | ||
|
|
0f57dc9c8a | ||
|
|
31a4bc7747 | ||
|
|
0eed67b5aa | ||
|
|
3dbf40ff44 | ||
|
|
16459c1ec1 | ||
|
|
ba575ff79b | ||
|
|
2f9e407f49 | ||
|
|
1779c1ac14 | ||
|
|
ca1c353575 | ||
|
|
cac643266b | ||
|
|
5518199f64 | ||
|
|
a508fa81c0 | ||
|
|
c5bec8b69e | ||
|
|
c4905d39c1 | ||
|
|
5275e5eba7 | ||
|
|
272341f1dc | ||
|
|
78885987f0 | ||
|
|
cdbcc971b1 | ||
|
|
5378b4c577 | ||
|
|
269593cd98 | ||
|
|
82c83f4b8d | ||
|
|
42885cefc9 | ||
|
|
54a5009f98 | ||
|
|
7c49164387 | ||
|
|
1b8d4e4494 | ||
|
|
932211e7f6 | ||
|
|
9658993163 | ||
|
|
13e3c98fac | ||
|
|
9a9644dc6c | ||
|
|
cc43b2a9b0 | ||
|
|
91c0dbd8d2 | ||
|
|
3271ec6867 | ||
|
|
d39b779db9 | ||
|
|
536b0bad20 | ||
|
|
5c2020b8dd | ||
|
|
5851d061a2 | ||
|
|
a8bcc1457d | ||
|
|
6ba9cb8b55 | ||
|
|
8e53cce3b2 | ||
|
|
7f3a4ada75 | ||
|
|
0163988593 | ||
|
|
bcf78aed0a | ||
|
|
a7c89cc32e | ||
|
|
e66502ee8f | ||
|
|
40a12f35d7 | ||
|
|
fd1a399d03 | ||
|
|
538e45d20c | ||
|
|
0304e2a1ed | ||
|
|
65245234d8 | ||
|
|
a88d7625dc | ||
|
|
1af06b6480 | ||
|
|
48270197fa | ||
|
|
86cea901b4 | ||
|
|
f5312496e3 | ||
|
|
7f57e7ab56 | ||
|
|
c8d8dd581e | ||
|
|
256c1a7d41 | ||
|
|
7aa71dc744 | ||
|
|
07c34ea7b5 | ||
|
|
2d2582b449 | ||
|
|
4f81cb10de | ||
|
|
0256864904 |
@@ -81,7 +81,8 @@ REVERSE_PROXY_AUTH=0
|
||||
# Default settings for spaces, apply per space and can be changed in the admin view
|
||||
# SPACE_DEFAULT_MAX_RECIPES=0 # 0=unlimited recipes
|
||||
# SPACE_DEFAULT_MAX_USERS=0 # 0=unlimited users per space
|
||||
# SPACE_DEFAULT_FILES=1 # 1=can upload files (images, etc.) NOT IMPLEMENTED YET
|
||||
# SPACE_DEFAULT_MAX_FILES=0 # Maximum file storage for space in MB. 0 for unlimited, -1 to disable file upload.
|
||||
# SPACE_DEFAULT_ALLOW_SHARING=1 # Allow users to share recipes with public links
|
||||
|
||||
# allow people to create accounts on your application instance (without an invite link)
|
||||
# when unset: 0 (false)
|
||||
@@ -111,4 +112,9 @@ REVERSE_PROXY_AUTH=0
|
||||
# SOCIAL_DEFAULT_ACCESS = 1
|
||||
|
||||
# if SOCIAL_DEFAULT_ACCESS is used, which group should be added
|
||||
# SOCIAL_DEFAULT_GROUP=guest
|
||||
# SOCIAL_DEFAULT_GROUP=guest
|
||||
|
||||
# Django session cookie settings. Can be changed to allow a single django application to authenticate several applications
|
||||
# when running under the same database
|
||||
# SESSION_COOKIE_DOMAIN=.example.com
|
||||
# SESSION_COOKIE_NAME=sessionid # use this only to not interfere with non unified django applications under the same top level domain
|
||||
15
README.md
15
README.md
@@ -9,18 +9,17 @@
|
||||
<h4 align="center">The recipe manager that allows you to manage your ever growing collection of digital recipes.</h4>
|
||||
|
||||
<p align="center">
|
||||
|
||||
<img src="https://github.com/vabene1111/recipes/workflows/Continous%20Integration/badge.svg?branch=develop" >
|
||||
<img src="https://img.shields.io/github/stars/vabene1111/recipes" >
|
||||
<img src="https://img.shields.io/github/forks/vabene1111/recipes" >
|
||||
<img src="https://img.shields.io/docker/pulls/vabene1111/recipes" >
|
||||
|
||||
<a href="https://github.com/vabene1111/recipes/actions" target="_blank" rel="noopener noreferrer"><img src="https://github.com/vabene1111/recipes/workflows/Continous%20Integration/badge.svg?branch=master" ></a>
|
||||
<a href="https://github.com/vabene1111/recipes/stargazers" target="_blank" rel="noopener noreferrer"><img src="https://img.shields.io/github/stars/vabene1111/recipes" ></a>
|
||||
<a href="https://github.com/vabene1111/recipes/network/members" target="_blank" rel="noopener noreferrer"><img src="https://img.shields.io/github/forks/vabene1111/recipes" ></a>
|
||||
<a href="https://hub.docker.com/r/vabene1111/recipes" target="_blank" rel="noopener noreferrer"><img src="https://img.shields.io/docker/pulls/vabene1111/recipes" ></a>
|
||||
<a href="https://github.com/vabene1111/recipes/releases/latest" rel="noopener noreferrer"><img src="https://img.shields.io/github/v/release/vabene1111/recipes" ></a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://docs.tandoor.dev/install/docker/" target="_blank" rel="noopener noreferrer">Installation</a> •
|
||||
<a href="https://docs.tandoor.dev/install/docker.html" target="_blank" rel="noopener noreferrer">Installation</a> •
|
||||
<a href="https://docs.tandoor.dev/" target="_blank" rel="noopener noreferrer">Documentation</a> •
|
||||
<a href="https://app.tandoor.dev/" target="_blank" rel="noopener noreferrer">Demo</a>
|
||||
<a href="https://app.tandoor.dev/accounts/login/?demo" target="_blank" rel="noopener noreferrer">Demo</a>
|
||||
</p>
|
||||
|
||||

|
||||
|
||||
@@ -23,14 +23,20 @@ admin.site.unregister(Group)
|
||||
|
||||
|
||||
class SpaceAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'created_by', 'message')
|
||||
list_display = ('name', 'created_by', 'max_recipes', 'max_users', 'max_file_storage_mb', 'allow_sharing')
|
||||
search_fields = ('name', 'created_by__username')
|
||||
list_filter = ('max_recipes', 'max_users', 'max_file_storage_mb', 'allow_sharing')
|
||||
date_hierarchy = 'created_at'
|
||||
|
||||
|
||||
admin.site.register(Space, SpaceAdmin)
|
||||
|
||||
|
||||
class UserPreferenceAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'space', 'theme', 'nav_color', 'default_page', 'search_style',)
|
||||
list_display = ('name', 'space', 'theme', 'nav_color', 'default_page', 'search_style',) # TODO add new fields
|
||||
search_fields = ('user__username', 'space__name')
|
||||
list_filter = ('theme', 'nav_color', 'default_page', 'search_style')
|
||||
date_hierarchy = 'created_at'
|
||||
|
||||
@staticmethod
|
||||
def name(obj):
|
||||
@@ -42,6 +48,7 @@ admin.site.register(UserPreference, UserPreferenceAdmin)
|
||||
|
||||
class StorageAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'method')
|
||||
search_fields = ('name',)
|
||||
|
||||
|
||||
admin.site.register(Storage, StorageAdmin)
|
||||
@@ -49,6 +56,7 @@ admin.site.register(Storage, StorageAdmin)
|
||||
|
||||
class SyncAdmin(admin.ModelAdmin):
|
||||
list_display = ('storage', 'path', 'active', 'last_checked')
|
||||
search_fields = ('storage__name', 'path')
|
||||
|
||||
|
||||
admin.site.register(Sync, SyncAdmin)
|
||||
@@ -77,6 +85,7 @@ admin.site.register(Keyword)
|
||||
|
||||
class StepAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'type', 'order')
|
||||
search_fields = ('name', 'type')
|
||||
|
||||
|
||||
admin.site.register(Step, StepAdmin)
|
||||
@@ -84,6 +93,9 @@ admin.site.register(Step, StepAdmin)
|
||||
|
||||
class RecipeAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'internal', 'created_by', 'storage')
|
||||
search_fields = ('name', 'created_by__username')
|
||||
list_filter = ('internal',)
|
||||
date_hierarchy = 'created_at'
|
||||
|
||||
@staticmethod
|
||||
def created_by(obj):
|
||||
@@ -98,6 +110,7 @@ admin.site.register(Food)
|
||||
|
||||
class IngredientAdmin(admin.ModelAdmin):
|
||||
list_display = ('food', 'amount', 'unit')
|
||||
search_fields = ('food__name', 'unit__name')
|
||||
|
||||
|
||||
admin.site.register(Ingredient, IngredientAdmin)
|
||||
@@ -105,6 +118,8 @@ admin.site.register(Ingredient, IngredientAdmin)
|
||||
|
||||
class CommentAdmin(admin.ModelAdmin):
|
||||
list_display = ('recipe', 'name', 'created_at')
|
||||
search_fields = ('text', 'user__username')
|
||||
date_hierarchy = 'created_at'
|
||||
|
||||
@staticmethod
|
||||
def name(obj):
|
||||
@@ -123,6 +138,7 @@ admin.site.register(RecipeImport, RecipeImportAdmin)
|
||||
|
||||
class RecipeBookAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'user_name')
|
||||
search_fields = ('name', 'created_by__username')
|
||||
|
||||
@staticmethod
|
||||
def user_name(obj):
|
||||
@@ -152,6 +168,7 @@ admin.site.register(MealPlan, MealPlanAdmin)
|
||||
|
||||
class MealTypeAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'created_by', 'order')
|
||||
search_fields = ('name', 'created_by__username')
|
||||
|
||||
|
||||
admin.site.register(MealType, MealTypeAdmin)
|
||||
|
||||
@@ -20,7 +20,7 @@ class AllAuthCustomAdapter(DefaultAccountAdapter):
|
||||
if 'signup_token' in request.session and InviteLink.objects.filter(valid_until__gte=datetime.datetime.today(), used_by=None, uuid=request.session['signup_token']).exists():
|
||||
signup_token = True
|
||||
|
||||
if request.resolver_match.view_name == 'account_signup' and not settings.ENABLE_SIGNUP and not signup_token:
|
||||
if (request.resolver_match.view_name == 'account_signup' or request.resolver_match.view_name == 'socialaccount_signup') and not settings.ENABLE_SIGNUP and not signup_token:
|
||||
return False
|
||||
else:
|
||||
return super(AllAuthCustomAdapter, self).is_open_for_signup(request)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import re
|
||||
import string
|
||||
import unicodedata
|
||||
|
||||
@@ -22,20 +23,16 @@ def parse_fraction(x):
|
||||
def parse_amount(x):
|
||||
amount = 0
|
||||
unit = ''
|
||||
note = ''
|
||||
|
||||
did_check_frac = False
|
||||
end = 0
|
||||
while (
|
||||
end < len(x)
|
||||
and (
|
||||
x[end] in string.digits
|
||||
or (
|
||||
(x[end] == '.' or x[end] == ',' or x[end] == '/')
|
||||
and end + 1 < len(x)
|
||||
and x[end + 1] in string.digits
|
||||
)
|
||||
)
|
||||
):
|
||||
while (end < len(x) and (x[end] in string.digits
|
||||
or (
|
||||
(x[end] == '.' or x[end] == ',' or x[end] == '/')
|
||||
and end + 1 < len(x)
|
||||
and x[end + 1] in string.digits
|
||||
))):
|
||||
end += 1
|
||||
if end > 0:
|
||||
if "/" in x[:end]:
|
||||
@@ -55,7 +52,11 @@ def parse_amount(x):
|
||||
unit = x[end + 1:]
|
||||
except ValueError:
|
||||
unit = x[end:]
|
||||
return amount, unit
|
||||
|
||||
if unit.startswith('(') or unit.startswith('-'): # i dont know any unit that starts with ( or - so its likely an alternative like 1L (500ml) Water or 2-3
|
||||
unit = ''
|
||||
note = x
|
||||
return amount, unit, note
|
||||
|
||||
|
||||
def parse_ingredient_with_comma(tokens):
|
||||
@@ -106,6 +107,13 @@ def parse(x):
|
||||
unit = ''
|
||||
ingredient = ''
|
||||
note = ''
|
||||
unit_note = ''
|
||||
|
||||
# if the string contains parenthesis early on remove it and place it at the end
|
||||
# because its likely some kind of note
|
||||
if re.match('(.){1,6}\s\((.[^\(\)])+\)\s', x):
|
||||
match = re.search('\((.[^\(])+\)', x)
|
||||
x = x[:match.start()] + x[match.end():] + ' ' + x[match.start():match.end()]
|
||||
|
||||
tokens = x.split()
|
||||
if len(tokens) == 1:
|
||||
@@ -114,17 +122,17 @@ def parse(x):
|
||||
else:
|
||||
try:
|
||||
# try to parse first argument as amount
|
||||
amount, unit = parse_amount(tokens[0])
|
||||
amount, unit, unit_note = parse_amount(tokens[0])
|
||||
# only try to parse second argument as amount if there are at least
|
||||
# three arguments if it already has a unit there can't be
|
||||
# a fraction for the amount
|
||||
if len(tokens) > 2:
|
||||
try:
|
||||
if not unit == '':
|
||||
# a unit is already found, no need to try the second argument for a fraction # noqa: E501
|
||||
# a unit is already found, no need to try the second argument for a fraction
|
||||
# probably not the best method to do it, but I didn't want to make an if check and paste the exact same thing in the else as already is in the except # noqa: E501
|
||||
raise ValueError
|
||||
# try to parse second argument as amount and add that, in case of '2 1/2' or '2 ½' # noqa: E501
|
||||
# try to parse second argument as amount and add that, in case of '2 1/2' or '2 ½'
|
||||
amount += parse_fraction(tokens[1])
|
||||
# assume that units can't end with a comma
|
||||
if len(tokens) > 3 and not tokens[2].endswith(','):
|
||||
@@ -142,7 +150,10 @@ def parse(x):
|
||||
# try to use second argument as unit and everything else as ingredient, use everything as ingredient if it fails # noqa: E501
|
||||
try:
|
||||
ingredient, note = parse_ingredient(tokens[2:])
|
||||
unit = tokens[1]
|
||||
if unit == '':
|
||||
unit = tokens[1]
|
||||
else:
|
||||
note = tokens[1]
|
||||
except ValueError:
|
||||
ingredient, note = parse_ingredient(tokens[1:])
|
||||
else:
|
||||
@@ -158,6 +169,9 @@ def parse(x):
|
||||
ingredient, note = parse_ingredient(tokens)
|
||||
except ValueError:
|
||||
ingredient = ' '.join(tokens[1:])
|
||||
|
||||
if unit_note not in note:
|
||||
note += ' ' + unit_note
|
||||
return amount, unit.strip(), ingredient.strip(), note.strip()
|
||||
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ def search_recipes(request, queryset, params):
|
||||
|
||||
search_internal = params.get('internal', None)
|
||||
search_random = params.get('random', False)
|
||||
search_new = params.get('new', False)
|
||||
search_last_viewed = int(params.get('last_viewed', 0))
|
||||
|
||||
if search_last_viewed > 0:
|
||||
@@ -29,9 +30,12 @@ def search_recipes(request, queryset, params):
|
||||
|
||||
return queryset.filter(pk__in=last_viewed_recipes[len(last_viewed_recipes) - min(len(last_viewed_recipes), search_last_viewed):])
|
||||
|
||||
queryset = queryset.annotate(
|
||||
new_recipe=Case(When(created_at__gte=(datetime.now() - timedelta(days=7)), then=Value(100)),
|
||||
default=Value(0), )).order_by('-new_recipe', 'name')
|
||||
if search_new == 'true':
|
||||
queryset = queryset.annotate(
|
||||
new_recipe=Case(When(created_at__gte=(datetime.now() - timedelta(days=7)), then=Value(100)),
|
||||
default=Value(0), )).order_by('-new_recipe', 'name')
|
||||
else:
|
||||
queryset = queryset.order_by('name')
|
||||
|
||||
if settings.DATABASES['default']['ENGINE'] in ['django.db.backends.postgresql_psycopg2',
|
||||
'django.db.backends.postgresql']:
|
||||
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
BIN
cookbook/locale/nb_NO/LC_MESSAGES/django.mo
Normal file
BIN
cookbook/locale/nb_NO/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
BIN
cookbook/locale/sv/LC_MESSAGES/django.mo
Normal file
BIN
cookbook/locale/sv/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
BIN
cookbook/locale/zh_Hant/LC_MESSAGES/django.mo
Normal file
BIN
cookbook/locale/zh_Hant/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
2303
cookbook/locale/zh_Hant/LC_MESSAGES/django.po
Normal file
2303
cookbook/locale/zh_Hant/LC_MESSAGES/django.po
Normal file
File diff suppressed because it is too large
Load Diff
20
cookbook/migrations/0139_space_created_at.py
Normal file
20
cookbook/migrations/0139_space_created_at.py
Normal file
@@ -0,0 +1,20 @@
|
||||
# Generated by Django 3.2.4 on 2021-06-22 16:14
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.utils.timezone
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('cookbook', '0138_auto_20210617_1602'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='space',
|
||||
name='created_at',
|
||||
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
||||
20
cookbook/migrations/0140_userpreference_created_at.py
Normal file
20
cookbook/migrations/0140_userpreference_created_at.py
Normal file
@@ -0,0 +1,20 @@
|
||||
# Generated by Django 3.2.4 on 2021-06-22 16:19
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.utils.timezone
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('cookbook', '0139_space_created_at'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='userpreference',
|
||||
name='created_at',
|
||||
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
||||
24
cookbook/migrations/0141_auto_20210713_1042.py
Normal file
24
cookbook/migrations/0141_auto_20210713_1042.py
Normal file
@@ -0,0 +1,24 @@
|
||||
# Generated by Django 3.2.5 on 2021-07-13 08:42
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('cookbook', '0140_userpreference_created_at'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='step',
|
||||
name='step_recipe',
|
||||
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.PROTECT, to='cookbook.recipe'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='step',
|
||||
name='type',
|
||||
field=models.CharField(choices=[('TEXT', 'Text'), ('TIME', 'Time'), ('FILE', 'File'), ('RECIPE', 'Recipe')], default='TEXT', max_length=16),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 3.2.5 on 2021-07-29 14:50
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('cookbook', '0141_auto_20210713_1042'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='userpreference',
|
||||
name='search_style',
|
||||
field=models.CharField(choices=[('SMALL', 'Small'), ('LARGE', 'Large'), ('NEW', 'New')], default='NEW', max_length=64),
|
||||
),
|
||||
]
|
||||
@@ -66,6 +66,7 @@ class PermissionModelMixin:
|
||||
class Space(ExportModelOperationsMixin('space'), models.Model):
|
||||
name = models.CharField(max_length=128, default='Default')
|
||||
created_by = models.ForeignKey(User, on_delete=models.PROTECT, null=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
message = models.CharField(max_length=512, default='', blank=True)
|
||||
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.'))
|
||||
@@ -142,7 +143,7 @@ class UserPreference(models.Model, PermissionModelMixin):
|
||||
choices=PAGES, max_length=64, default=SEARCH
|
||||
)
|
||||
search_style = models.CharField(
|
||||
choices=SEARCH_STYLE, max_length=64, default=LARGE
|
||||
choices=SEARCH_STYLE, max_length=64, default=NEW
|
||||
)
|
||||
show_recent = models.BooleanField(default=True)
|
||||
plan_share = models.ManyToManyField(
|
||||
@@ -153,6 +154,7 @@ class UserPreference(models.Model, PermissionModelMixin):
|
||||
shopping_auto_sync = models.IntegerField(default=5)
|
||||
sticky_navbar = models.BooleanField(default=STICKY_NAV_PREF_DEFAULT)
|
||||
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
space = models.ForeignKey(Space, on_delete=models.CASCADE, null=True)
|
||||
objects = ScopedManager(space='space')
|
||||
|
||||
@@ -329,10 +331,11 @@ class Step(ExportModelOperationsMixin('step'), models.Model, PermissionModelMixi
|
||||
TEXT = 'TEXT'
|
||||
TIME = 'TIME'
|
||||
FILE = 'FILE'
|
||||
RECIPE = 'RECIPE'
|
||||
|
||||
name = models.CharField(max_length=128, default='', blank=True)
|
||||
type = models.CharField(
|
||||
choices=((TEXT, _('Text')), (TIME, _('Time')), (FILE, _('File')),),
|
||||
choices=((TEXT, _('Text')), (TIME, _('Time')), (FILE, _('File')), (RECIPE, _('Recipe')),),
|
||||
default=TEXT,
|
||||
max_length=16
|
||||
)
|
||||
@@ -342,6 +345,7 @@ class Step(ExportModelOperationsMixin('step'), models.Model, PermissionModelMixi
|
||||
order = models.IntegerField(default=0)
|
||||
file = models.ForeignKey('UserFile', on_delete=models.PROTECT, null=True, blank=True)
|
||||
show_as_header = models.BooleanField(default=True)
|
||||
step_recipe = models.ForeignKey('Recipe', default=None, blank=True, null=True, on_delete=models.PROTECT)
|
||||
|
||||
space = models.ForeignKey(Space, on_delete=models.CASCADE)
|
||||
objects = ScopedManager(space='space')
|
||||
|
||||
@@ -2,7 +2,7 @@ from decimal import Decimal
|
||||
from gettext import gettext as _
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.db.models import QuerySet, Sum
|
||||
from django.db.models import QuerySet, Sum, Avg
|
||||
from drf_writable_nested import (UniqueFieldsMixin,
|
||||
WritableNestedModelSerializer)
|
||||
from rest_framework import serializers
|
||||
@@ -246,7 +246,7 @@ class SupermarketCategorySerializer(UniqueFieldsMixin, WritableNestedModelSerial
|
||||
fields = ('id', 'name')
|
||||
|
||||
|
||||
class SupermarketCategoryRelationSerializer(SpacedModelSerializer):
|
||||
class SupermarketCategoryRelationSerializer(WritableNestedModelSerializer):
|
||||
category = SupermarketCategorySerializer()
|
||||
|
||||
class Meta:
|
||||
@@ -300,6 +300,7 @@ class StepSerializer(WritableNestedModelSerializer):
|
||||
ingredients_markdown = serializers.SerializerMethodField('get_ingredients_markdown')
|
||||
ingredients_vue = serializers.SerializerMethodField('get_ingredients_vue')
|
||||
file = UserFileViewSerializer(allow_null=True, required=False)
|
||||
step_recipe_data = serializers.SerializerMethodField('get_step_recipe_data')
|
||||
|
||||
def create(self, validated_data):
|
||||
validated_data['space'] = self.context['request'].space
|
||||
@@ -311,11 +312,27 @@ class StepSerializer(WritableNestedModelSerializer):
|
||||
def get_ingredients_markdown(self, obj):
|
||||
return obj.get_instruction_render()
|
||||
|
||||
def get_step_recipe_data(self, obj):
|
||||
# check if root type is recipe to prevent infinite recursion
|
||||
# can be improved later to allow multi level embedding
|
||||
if obj.step_recipe and type(self.parent.root) == RecipeSerializer:
|
||||
return StepRecipeSerializer(obj.step_recipe).data
|
||||
|
||||
class Meta:
|
||||
model = Step
|
||||
fields = (
|
||||
'id', 'name', 'type', 'instruction', 'ingredients', 'ingredients_markdown',
|
||||
'ingredients_vue', 'time', 'order', 'show_as_header', 'file',
|
||||
'ingredients_vue', 'time', 'order', 'show_as_header', 'file', 'step_recipe', 'step_recipe_data'
|
||||
)
|
||||
|
||||
|
||||
class StepRecipeSerializer(WritableNestedModelSerializer):
|
||||
steps = StepSerializer(many=True)
|
||||
|
||||
class Meta:
|
||||
model = Recipe
|
||||
fields = (
|
||||
'id', 'name', 'steps',
|
||||
)
|
||||
|
||||
|
||||
@@ -330,8 +347,30 @@ class NutritionInformationSerializer(serializers.ModelSerializer):
|
||||
fields = ('id', 'carbohydrates', 'fats', 'proteins', 'calories', 'source')
|
||||
|
||||
|
||||
class RecipeOverviewSerializer(WritableNestedModelSerializer):
|
||||
class RecipeBaseSerializer(WritableNestedModelSerializer):
|
||||
def get_recipe_rating(self, obj):
|
||||
try:
|
||||
rating = obj.cooklog_set.filter(created_by=self.context['request'].user, rating__gt=0).aggregate(Avg('rating'))
|
||||
if rating['rating__avg']:
|
||||
return rating['rating__avg']
|
||||
except TypeError:
|
||||
pass
|
||||
return 0
|
||||
|
||||
def get_recipe_last_cooked(self, obj):
|
||||
try:
|
||||
last = obj.cooklog_set.filter(created_by=self.context['request'].user).last()
|
||||
if last:
|
||||
return last.created_at
|
||||
except TypeError:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
class RecipeOverviewSerializer(RecipeBaseSerializer):
|
||||
keywords = KeywordLabelSerializer(many=True)
|
||||
rating = serializers.SerializerMethodField('get_recipe_rating')
|
||||
last_cooked = serializers.SerializerMethodField('get_recipe_last_cooked')
|
||||
|
||||
def create(self, validated_data):
|
||||
pass
|
||||
@@ -344,22 +383,24 @@ class RecipeOverviewSerializer(WritableNestedModelSerializer):
|
||||
fields = (
|
||||
'id', 'name', 'description', 'image', 'keywords', 'working_time',
|
||||
'waiting_time', 'created_by', 'created_at', 'updated_at',
|
||||
'internal', 'servings', 'file_path'
|
||||
'internal', 'servings', 'servings_text', 'rating', 'last_cooked',
|
||||
)
|
||||
read_only_fields = ['image', 'created_by', 'created_at']
|
||||
|
||||
|
||||
class RecipeSerializer(WritableNestedModelSerializer):
|
||||
class RecipeSerializer(RecipeBaseSerializer):
|
||||
nutrition = NutritionInformationSerializer(allow_null=True, required=False)
|
||||
steps = StepSerializer(many=True)
|
||||
keywords = KeywordSerializer(many=True)
|
||||
rating = serializers.SerializerMethodField('get_recipe_rating')
|
||||
last_cooked = serializers.SerializerMethodField('get_recipe_last_cooked')
|
||||
|
||||
class Meta:
|
||||
model = Recipe
|
||||
fields = (
|
||||
'id', 'name', 'description', 'image', 'keywords', 'steps', 'working_time',
|
||||
'waiting_time', 'created_by', 'created_at', 'updated_at',
|
||||
'internal', 'nutrition', 'servings', 'file_path', 'servings_text',
|
||||
'internal', 'nutrition', 'servings', 'file_path', 'servings_text', 'rating', 'last_cooked',
|
||||
)
|
||||
read_only_fields = ['image', 'created_by', 'created_at']
|
||||
|
||||
@@ -400,6 +441,14 @@ class RecipeBookSerializer(SpacedModelSerializer):
|
||||
|
||||
|
||||
class RecipeBookEntrySerializer(serializers.ModelSerializer):
|
||||
book_content = serializers.SerializerMethodField(method_name='get_book_content', read_only=True)
|
||||
recipe_content = serializers.SerializerMethodField(method_name='get_recipe_content', read_only=True)
|
||||
|
||||
def get_book_content(self, obj):
|
||||
return RecipeBookSerializer(context={'request': self.context['request']}).to_representation(obj.book)
|
||||
|
||||
def get_recipe_content(self, obj):
|
||||
return RecipeOverviewSerializer(context={'request': self.context['request']}).to_representation(obj.recipe)
|
||||
|
||||
def create(self, validated_data):
|
||||
book = validated_data['book']
|
||||
@@ -409,7 +458,7 @@ class RecipeBookEntrySerializer(serializers.ModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = RecipeBookEntry
|
||||
fields = ('id', 'book', 'recipe',)
|
||||
fields = ('id', 'book', 'book_content', 'recipe', 'recipe_content',)
|
||||
|
||||
|
||||
class MealPlanSerializer(SpacedModelSerializer, WritableNestedModelSerializer):
|
||||
|
||||
1112
cookbook/static/css/app.min.css
vendored
1112
cookbook/static/css/app.min.css
vendored
File diff suppressed because it is too large
Load Diff
14
cookbook/static/themes/tandoor.min.css
vendored
14
cookbook/static/themes/tandoor.min.css
vendored
@@ -10402,16 +10402,6 @@ footer a:hover {
|
||||
box-shadow: none
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
width: 500px
|
||||
}
|
||||
|
||||
@media (max-width: 575px) {
|
||||
.modal-content {
|
||||
width: 300px
|
||||
}
|
||||
}
|
||||
|
||||
.modal-content .modal-header {
|
||||
justify-content: center;
|
||||
border: none
|
||||
@@ -10468,3 +10458,7 @@ textarea, input:not([type="submit"]):not([class="multiselect__input"]):not([clas
|
||||
.multiselect__tag-icon:after {
|
||||
color: #212529 !important
|
||||
}
|
||||
|
||||
.form-control-search {
|
||||
font-size: 20px;
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -6,6 +6,14 @@
|
||||
{% block title %}{% trans "E-mail Addresses" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="{% url 'view_settings' %}">{% trans 'Settings' %}</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page">{% trans 'Email' %}</li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<h3>{% trans "E-mail Addresses" %}</h3>
|
||||
{% if user.emailaddress_set.all %}
|
||||
<p>{% trans 'The following e-mail addresses are associated with your account:' %}</p>
|
||||
|
||||
@@ -44,13 +44,13 @@
|
||||
|
||||
{% if socialaccount_providers %}
|
||||
<div class="row" style="margin-top: 2vh">
|
||||
<div class="col-6 offset-3">
|
||||
<div class="col-sm-12 col-lg-6 col-md-6 offset-lg-3 offset-md-3">
|
||||
<h5>{% trans "Social Login" %}</h5>
|
||||
<span>{% trans 'You can use any of the following providers to sign in.' %}</span>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
<ul class="socialaccount_providers">
|
||||
<ul class="socialaccount_providers list-unstyled">
|
||||
{% include "socialaccount/snippets/provider_list.html" with process="login" %}
|
||||
</ul>
|
||||
|
||||
|
||||
24
cookbook/templates/account/password_change.html
Normal file
24
cookbook/templates/account/password_change.html
Normal file
@@ -0,0 +1,24 @@
|
||||
{% extends "base.html" %}
|
||||
{% load crispy_forms_filters %}
|
||||
|
||||
{% load i18n %}
|
||||
|
||||
{% block head_title %}{% trans "Change Password" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="{% url 'view_settings' %}">{% trans 'Settings' %}</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page">{% trans 'Password' %}</li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<h1>{% trans "Change Password" %}</h1>
|
||||
|
||||
<form method="POST" action="{% url 'account_change_password' %}" class="password_change">
|
||||
{% csrf_token %}
|
||||
{{ form | crispy }}
|
||||
<button type="submit" name="action" class="btn btn-success">{% trans "Change Password" %}</button>
|
||||
<a href="{% url 'account_reset_password' %}">{% trans "Forgot Password?" %}</a>
|
||||
</form>
|
||||
{% endblock %}
|
||||
23
cookbook/templates/account/password_set.html
Normal file
23
cookbook/templates/account/password_set.html
Normal file
@@ -0,0 +1,23 @@
|
||||
{% extends "base.html" %}
|
||||
{% load crispy_forms_filters %}
|
||||
|
||||
{% load i18n %}
|
||||
|
||||
{% block head_title %}{% trans "Set Password" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="{% url 'view_settings' %}">{% trans 'Settings' %}</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page">{% trans 'Password' %}</li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<h1>{% trans "Set Password" %}</h1>
|
||||
|
||||
<form method="POST" action="{% url 'account_set_password' %}" class="password_set">
|
||||
{% csrf_token %}
|
||||
{{ form | crispy }}
|
||||
<input type="submit" class="btn btn-primary" name="action" value="{% trans 'Set Password' %}"/>
|
||||
</form>
|
||||
{% endblock %}
|
||||
@@ -67,9 +67,9 @@
|
||||
</button>
|
||||
|
||||
{% 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">
|
||||
<img class="brand-icon" src="{% static 'assets/brand_logo.png' %}" alt="" style="height: 5vh;">
|
||||
</a>
|
||||
<a class="navbar-brand p-0 me-2 justify-content-center" href="/" aria-label="Tandoor">
|
||||
<img class="brand-icon" src="{% static 'assets/brand_logo.png' %}" alt="" style="height: 5vh;">
|
||||
</a>
|
||||
{% endif %}
|
||||
<div class="collapse navbar-collapse" id="navbarText">
|
||||
<ul class="navbar-nav mr-auto">
|
||||
@@ -97,6 +97,9 @@
|
||||
<a class="dropdown-item" href="{% url 'list_food' %}"><i
|
||||
class="fas fa-leaf fa-fw"></i> {% trans 'Ingredients' %}
|
||||
</a>
|
||||
<a class="dropdown-item" href="{% url 'view_supermarket' %}"><i
|
||||
class="fas fa-store-alt fa-fw"></i> {% trans 'Supermarket' %}
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
<li class="nav-item dropdown {% if request.resolver_match.url_name in 'list_keyword,data_batch_edit' %}active{% endif %}">
|
||||
@@ -152,12 +155,18 @@
|
||||
<a class="dropdown-item" href="{% url 'view_history' %}"><i
|
||||
class="fas fa-history"></i> {% trans 'History' %}</a>
|
||||
{% if request.user == request.space.created_by %}
|
||||
<a class="dropdown-item" href="{% url 'view_space' %}"><i class="fas fa-server fa-fw"></i> {% trans 'Space Settings' %}</a>
|
||||
<a class="dropdown-item" href="{% url 'view_space' %}"><i
|
||||
class="fas fa-server fa-fw"></i> {% trans 'Space Settings' %}</a>
|
||||
{% endif %}
|
||||
<div class="dropdown-divider"></div>
|
||||
<a class="dropdown-item" href="{% url 'view_system' %}"><i
|
||||
class="fas fa-server fa-fw"></i> {% trans 'System' %}</a>
|
||||
<a class="dropdown-item" href="{% url 'admin:index' %}"><i
|
||||
class="fas fa-user-shield fa-fw"></i> {% trans 'Admin' %}</a>
|
||||
{% if user.is_superuser %}
|
||||
<div class="dropdown-divider"></div>
|
||||
<a class="dropdown-item" href="{% url 'view_system' %}"><i
|
||||
class="fas fa-server fa-fw"></i> {% trans 'System' %}</a>
|
||||
<a class="dropdown-item" href="{% url 'list_keyword' %}"><i
|
||||
class="fas fa-tags"></i> {% trans 'Keywords' %}</a>
|
||||
<a class="dropdown-item" href="{% url 'admin:index' %}"><i
|
||||
class="fas fa-user-shield fa-fw"></i> {% trans 'Admin' %}</a>
|
||||
{% endif %}
|
||||
@@ -192,10 +201,7 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<div class="container-fluid" id="id_base_container">
|
||||
<div class="container-fluid mt-2 mt-md-5 mt-xl-5 mt-lg-5" id="id_base_container">
|
||||
<div class="row">
|
||||
<div class="col-xl-2 d-none d-xl-block">
|
||||
{% block content_xl_left %}
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
<img src="{% if recipe.image %}{{ recipe.image.url }}{% endif %}" id="id_image"
|
||||
class="img img-fluid img-responsive"
|
||||
style="max-height: 20vh">
|
||||
<input type="file" @change="imageChanged">
|
||||
<input class="mt-2" type="file" @change="imageChanged">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="id_name"> {% trans 'Preparation Time' %}</label>
|
||||
@@ -202,6 +202,7 @@
|
||||
<option value="TEXT">{% trans 'Text' %}</option>
|
||||
<option value="TIME">{% trans 'Time' %}</option>
|
||||
<option value="FILE">{% trans 'File' %}</option>
|
||||
<option value="RECIPE">{% trans 'Recipe' %}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -214,7 +215,7 @@
|
||||
:id="'id_step_' + step.id + '_time'">
|
||||
</div>
|
||||
|
||||
<div class="col-md-9">
|
||||
<div class="col-md-9" v-if="step.type === 'FILE'">
|
||||
<label :for="'id_step_' + step.id + '_file'">{% trans 'File' %}</label>
|
||||
<multiselect
|
||||
v-tabindex
|
||||
@@ -235,6 +236,28 @@
|
||||
@search-change="searchFiles">
|
||||
</multiselect>
|
||||
</div>
|
||||
|
||||
<div class="col-md-9" v-if="step.type === 'RECIPE'">
|
||||
<label :for="'id_step_' + step.id + '_recipe'">{% trans 'Recipe' %}</label>
|
||||
<multiselect
|
||||
v-tabindex
|
||||
ref="step_recipe"
|
||||
v-model="step.step_recipe"
|
||||
:options="recipes.map(recipe => recipe.id)"
|
||||
:close-on-select="true"
|
||||
:clear-on-select="true"
|
||||
:allow-empty="true"
|
||||
:preserve-search="true"
|
||||
placeholder="{% trans 'Select Recipe' %}"
|
||||
select-label="{% trans 'Select' %}"
|
||||
:id="'id_step_' + step.id + '_recipe'"
|
||||
:custom-label="opt => recipes.find(x => x.id == opt).name"
|
||||
|
||||
:multiple="false"
|
||||
:loading="recipes_loading"
|
||||
@search-change="searchRecipes">
|
||||
</multiselect>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-if="step.type == 'TEXT'">
|
||||
@@ -523,6 +546,8 @@
|
||||
units_loading: false,
|
||||
files: [],
|
||||
files_loading: false,
|
||||
recipes: [],
|
||||
recipes_loading: false,
|
||||
message: '',
|
||||
},
|
||||
directives: {
|
||||
@@ -549,6 +574,7 @@
|
||||
this.searchFoods('')
|
||||
this.searchKeywords('')
|
||||
this.searchFiles('')
|
||||
this.searchRecipes('')
|
||||
|
||||
this._keyListener = function (e) {
|
||||
if (e.code === "Space" && e.ctrlKey) {
|
||||
@@ -720,6 +746,16 @@
|
||||
this.makeToast(gettext('Error'), gettext('There was an error loading a resource!') + err.bodyText, 'danger')
|
||||
})
|
||||
},
|
||||
searchRecipes: function (query) {
|
||||
this.recipes_loading = true
|
||||
this.$http.get("{% url 'api:recipe-list' %}" + '?query=' + query + '&limit=10').then((response) => {
|
||||
this.recipes = response.data.results
|
||||
this.recipes_loading = false
|
||||
}).catch((err) => {
|
||||
console.log(err)
|
||||
this.makeToast(gettext('Error'), gettext('There was an error loading a resource!') + err.bodyText, 'danger')
|
||||
})
|
||||
},
|
||||
searchUnits: function (query) {
|
||||
this.units_loading = true
|
||||
this.$http.get("{% url 'api:unit-list' %}" + '?query=' + query + '&limit=10').then((response) => {
|
||||
|
||||
@@ -73,6 +73,7 @@
|
||||
}
|
||||
|
||||
$('#id_log_rating').on("input", () => {
|
||||
let rating = $('#id_log_rating')
|
||||
$('#id_rating_show').html(rating.val() + '/5')
|
||||
});
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">{% trans 'Join an existing space.' %}</h5>
|
||||
<p class="card-text">{% trans 'To join an existing space either enter your invite token or click on the invite link the space owner send you.' %}</p>
|
||||
<p class="card-text" style="height: 64px">{% trans 'To join an existing space either enter your invite token or click on the invite link the space owner send you.' %}</p>
|
||||
|
||||
<form method="POST" action="{% url 'view_no_space' %}">
|
||||
{% csrf_token %}
|
||||
@@ -49,7 +49,7 @@
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">{% trans 'Create your own recipe space.' %}</h5>
|
||||
<p class="card-text">{% trans 'Start your own recipe space and invite other users to it.' %}</p>
|
||||
<p class="card-text" style="height: 64px">{% trans 'Start your own recipe space and invite other users to it.' %}</p>
|
||||
<form method="POST" action="{% url 'view_no_space' %}">
|
||||
{% csrf_token %}
|
||||
{{ create_form | crispy }}
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<div class="card-body">
|
||||
<div class="card-body" style="padding: 16px">
|
||||
<div class="d-flex">
|
||||
<div class="flex-fill">
|
||||
<h5 class="card-title p-0 m-0">{{ row.cells.name }}
|
||||
|
||||
@@ -12,9 +12,11 @@
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h3>
|
||||
{% trans 'Settings' %}
|
||||
</h3>
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="{% url 'view_settings' %}">{% trans 'Settings' %}</a></li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<!-- Nav tabs -->
|
||||
<ul class="nav nav-tabs" id="myTab" role="tablist" style="margin-bottom: 2vh">
|
||||
@@ -44,23 +46,12 @@
|
||||
class="fas fa-save"></i> {% trans 'Save' %}</button>
|
||||
</form>
|
||||
|
||||
<h4>{% trans 'Password Settings' %}</h4>
|
||||
<form action="." method="post">
|
||||
{% csrf_token %}
|
||||
{{ password_form|crispy }}
|
||||
<button class="btn btn-success" type="submit" name="password_form"><i
|
||||
class="fas fa-save"></i> {% trans 'Save' %}</button>
|
||||
</form>
|
||||
<h4>{% trans 'Account Settings' %}</h4>
|
||||
|
||||
<h4>{% trans 'Email Settings' %}</h4>
|
||||
<a href="{% url 'account_email'%}" class="btn btn-primary">{% trans 'Emails' %}</a>
|
||||
<a href="{% url 'account_change_password'%}" class="btn btn-primary">{% trans 'Password' %}</a>
|
||||
|
||||
<a href="{% url 'account_email'%}" class="btn btn-primary">{% trans 'Manage Email Settings' %}</a>
|
||||
|
||||
<br/><br/>
|
||||
|
||||
<h4>{% trans 'Social' %}</h4>
|
||||
|
||||
<a href="{% url 'socialaccount_connections' %}" class="btn btn-primary">{% trans 'Manage Social Accounts' %}</a>
|
||||
<a href="{% url 'socialaccount_connections' %}" class="btn btn-primary">{% trans 'Social' %}</a>
|
||||
<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
@@ -4,6 +4,14 @@
|
||||
{% block head_title %}{% trans "Account Connections" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="{% url 'view_settings' %}">{% trans 'Settings' %}</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page">{% trans 'Social' %}</li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<h3>{% trans "Account Connections" %}</h3>
|
||||
|
||||
{% if form.accounts %}
|
||||
@@ -46,7 +54,7 @@
|
||||
|
||||
<h4>{% trans 'Add a 3rd Party Account' %}</h4>
|
||||
|
||||
<ul class="socialaccount_providers">
|
||||
<ul class="socialaccount_providers list-unstyled">
|
||||
{% include "socialaccount/snippets/provider_list.html" with process="connect" %}
|
||||
</ul>
|
||||
|
||||
|
||||
60
cookbook/templates/socialaccount/signup.html
Normal file
60
cookbook/templates/socialaccount/signup.html
Normal file
@@ -0,0 +1,60 @@
|
||||
{% extends "base.html" %}
|
||||
{% load crispy_forms_filters %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block head_title %}{% trans "Signup" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{% trans "Sign Up" %}</h1>
|
||||
|
||||
<p>{% blocktrans with provider_name=account.get_provider.name site_name=site.name %}You are about to use your
|
||||
{{ provider_name }} account to login to
|
||||
{{ site_name }}. As a final step, please complete the following form:{% endblocktrans %}</p>
|
||||
|
||||
<form class="signup" id="signup_form" method="post" action="{% url 'socialaccount_signup' %}">
|
||||
{% csrf_token %}
|
||||
{% if redirect_field_value %}
|
||||
<input type="hidden" name="{{ redirect_field_name }}" value="{{ redirect_field_value }}"/>
|
||||
{% endif %}
|
||||
|
||||
<div class="form-group">
|
||||
{{ form.username |as_crispy_field }}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
{{ form.email |as_crispy_field }}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
{{ form.email2 |as_crispy_field }}
|
||||
</div>
|
||||
{% if TERMS_URL != '' or PRIVACY_URL != '' %}
|
||||
<div class="form-group">
|
||||
{{ form.terms |as_crispy_field }}
|
||||
<small>
|
||||
{% trans 'I accept the follwoing' %}
|
||||
{% if TERMS_URL != '' %}
|
||||
<a href="{{ TERMS_URL }}" target="_blank"
|
||||
rel="noreferrer nofollow">{% trans 'Terms and Conditions' %}</a>
|
||||
{% endif %}
|
||||
{% if TERMS_URL != '' or PRIVACY_URL != '' %}
|
||||
{% trans 'and' %}
|
||||
{% endif %}
|
||||
{% if PRIVACY_URL != '' %}
|
||||
<a href="{{ PRIVACY_URL }}" target="_blank"
|
||||
rel="noreferrer nofollow">{% trans 'Privacy Policy' %}</a>
|
||||
{% endif %}
|
||||
</small>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if CAPTCHA_ENABLED %}
|
||||
<div class="form-group">
|
||||
{{ form.captcha.errors }}
|
||||
{{ form.captcha }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
<button type="submit" class="btn btn-primary">{% trans "Sign Up" %} »</button>
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
||||
133
cookbook/templates/socialaccount/snippets/provider_list.html
Normal file
133
cookbook/templates/socialaccount/snippets/provider_list.html
Normal file
@@ -0,0 +1,133 @@
|
||||
{% load i18n %}
|
||||
{% load socialaccount %}
|
||||
|
||||
{% get_providers as socialaccount_providers %}
|
||||
|
||||
{% for provider in socialaccount_providers %}
|
||||
{% if provider.id == "openid" %}
|
||||
{% for brand in provider.get_brands %}
|
||||
<li>
|
||||
<a title="{{ brand.name }}"
|
||||
class="socialaccount_provider {{ provider.id }} {{ brand.id }}"
|
||||
href="{% provider_login_url provider.id openid=brand.openid_url process=process %}"
|
||||
>{{ brand.name }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
<li CLASS="mb-1">
|
||||
{% if provider.id == 'discord' %}
|
||||
<a title="{{ provider.name }}"
|
||||
class="socialaccount_provider {{ provider.id }}"
|
||||
href="{% provider_login_url provider.id process=process scope=scope auth_params=auth_params %}">
|
||||
<button class="btn discord-login-button btn-social"><i
|
||||
class="fab fa-discord"></i> {% trans 'Sign in using' %} Discord
|
||||
</button>
|
||||
</a>
|
||||
{% elif provider.id == 'github' %}
|
||||
<a title="{{ provider.name }}"
|
||||
class="socialaccount_provider {{ provider.id }}"
|
||||
href="{% provider_login_url provider.id process=process scope=scope auth_params=auth_params %}">
|
||||
<button class="btn btn-social btn-github"><i
|
||||
class="fab fa-github"></i> {% trans 'Sign in using' %} Github
|
||||
</button>
|
||||
</a>
|
||||
{% elif provider.id == 'reddit' %}
|
||||
<a title="{{ provider.name }}"
|
||||
class="socialaccount_provider {{ provider.id }}"
|
||||
href="{% provider_login_url provider.id process=process scope=scope auth_params=auth_params %}">
|
||||
<button class="btn btn-social btn-reddit"><i
|
||||
class="fab fa-reddit"></i> {% trans 'Sign in using' %} Reddit
|
||||
</button>
|
||||
</a>
|
||||
{% elif provider.id == 'twitter' %}
|
||||
<a title="{{ provider.name }}"
|
||||
class="socialaccount_provider {{ provider.id }}"
|
||||
href="{% provider_login_url provider.id process=process scope=scope auth_params=auth_params %}">
|
||||
<button class="btn btn-social btn-twitter"><i
|
||||
class="fab fa-twitter"></i> {% trans 'Sign in using' %} Twitter
|
||||
</button>
|
||||
</a>
|
||||
{% elif provider.id == 'dropbox' %}
|
||||
<a title="{{ provider.name }}"
|
||||
class="socialaccount_provider {{ provider.id }}"
|
||||
href="{% provider_login_url provider.id process=process scope=scope auth_params=auth_params %}">
|
||||
<button class="btn btn-social btn-dropbox"><i
|
||||
class="fab fa-dropbox"></i> {% trans 'Sign in using' %} Dropbox
|
||||
</button>
|
||||
</a>
|
||||
{% elif provider.id == 'google' %}
|
||||
<a title="{{ provider.name }}"
|
||||
class="socialaccount_provider {{ provider.id }}"
|
||||
href="{% provider_login_url provider.id process=process scope=scope auth_params=auth_params %}">
|
||||
<button class="btn btn-social btn-google"><i
|
||||
class="fab fa-google"></i> {% trans 'Sign in using' %} Google
|
||||
</button>
|
||||
</a>
|
||||
{% elif provider.id == 'facebook' %}
|
||||
<a title="{{ provider.name }}"
|
||||
class="socialaccount_provider {{ provider.id }}"
|
||||
href="{% provider_login_url provider.id process=process scope=scope auth_params=auth_params %}">
|
||||
<button class="btn btn-social btn-facebook"><i
|
||||
class="fab fa-facebook"></i> {% trans 'Sign in using' %} Facebook
|
||||
</button>
|
||||
</a>
|
||||
{% elif provider.id == 'instagram' %}
|
||||
<a title="{{ provider.name }}"
|
||||
class="socialaccount_provider {{ provider.id }}"
|
||||
href="{% provider_login_url provider.id process=process scope=scope auth_params=auth_params %}">
|
||||
<button class="btn btn-social btn-instagram"><i
|
||||
class="fab fa-instagram"></i> {% trans 'Sign in using' %} Instagram
|
||||
</button>
|
||||
</a>
|
||||
{% elif provider.id == 'flickr' %}
|
||||
<a title="{{ provider.name }}"
|
||||
class="socialaccount_provider {{ provider.id }}"
|
||||
href="{% provider_login_url provider.id process=process scope=scope auth_params=auth_params %}">
|
||||
<button class="btn btn-social btn-flickr"><i
|
||||
class="fab fa-flickr"></i> {% trans 'Sign in using' %} Flickr
|
||||
</button>
|
||||
</a>
|
||||
{% elif provider.id == 'apple' %}
|
||||
<a title="{{ provider.name }}"
|
||||
class="socialaccount_provider {{ provider.id }}"
|
||||
href="{% provider_login_url provider.id process=process scope=scope auth_params=auth_params %}">
|
||||
<button class="btn btn-social btn-apple"><i
|
||||
class="fab fa-apple"></i> {% trans 'Sign in using' %} Apple
|
||||
</button>
|
||||
</a>
|
||||
{% elif provider.id == 'pinterest' %}
|
||||
<a title="{{ provider.name }}"
|
||||
class="socialaccount_provider {{ provider.id }}"
|
||||
href="{% provider_login_url provider.id process=process scope=scope auth_params=auth_params %}">
|
||||
<button class="btn btn-social btn-pinterest"><i
|
||||
class="fab fa-pinterest"></i> {% trans 'Sign in using' %} Pinterest
|
||||
</button>
|
||||
</a>
|
||||
{% elif provider.id == 'windowslive' %}
|
||||
<a title="{{ provider.name }}"
|
||||
class="socialaccount_provider {{ provider.id }}"
|
||||
href="{% provider_login_url provider.id process=process scope=scope auth_params=auth_params %}">
|
||||
<button class="btn btn-social btn-microsoft"><i
|
||||
class="fab fa-microsoft"></i> {% trans 'Sign in using' %} Microsoft Live
|
||||
</button>
|
||||
</a>
|
||||
{% elif provider.id == 'yahoo' %}
|
||||
<a title="{{ provider.name }}"
|
||||
class="socialaccount_provider {{ provider.id }}"
|
||||
href="{% provider_login_url provider.id process=process scope=scope auth_params=auth_params %}">
|
||||
<button class="btn btn-social btn-yahoo"><i
|
||||
class="fab fa-yahoo"></i> {% trans 'Sign in using' %} Yahoo
|
||||
</button>
|
||||
</a>
|
||||
{% else %}
|
||||
<a title="{{ provider.name }}"
|
||||
class="socialaccount_provider {{ provider.id }}"
|
||||
href="{% provider_login_url provider.id process=process scope=scope auth_params=auth_params %}">
|
||||
<button class="btn btn-social btn-success"><i
|
||||
class="fas fa-sign-in-alt"></i> {% trans 'Sign in using' %} {{ provider.name }}
|
||||
</button>
|
||||
</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
|
||||
{% endfor %}
|
||||
@@ -14,7 +14,13 @@
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h3>{{ request.space.name }} <small>{% if HOSTED %}
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="{% url 'view_space' %}">{% trans 'Space Settings' %}</a></li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<h3><span class="text-muted">{% trans 'Space:' %}</span> {{ request.space.name }} <small>{% if HOSTED %}
|
||||
<a href="https://tandoor.dev/manage">{% trans 'Manage Subscription' %}</a>{% endif %}</small></h3>
|
||||
|
||||
<br/>
|
||||
|
||||
@@ -55,7 +55,7 @@ def recipe_rating(recipe, user):
|
||||
if not user.is_authenticated:
|
||||
return ''
|
||||
rating = recipe.cooklog_set \
|
||||
.filter(created_by=user, rating__gte=0) \
|
||||
.filter(created_by=user, rating__gt=0) \
|
||||
.aggregate(Avg('rating'))
|
||||
if rating['rating__avg']:
|
||||
|
||||
@@ -64,7 +64,7 @@ def recipe_rating(recipe, user):
|
||||
rating_stars = rating_stars + '<i class="fas fa-star fa-xs"></i>'
|
||||
|
||||
if rating['rating__avg'] % 1 >= 0.5:
|
||||
rating_stars = rating_stars + '<i class="fas fa-star-half-alt fa-xs"></i>' # noqa: E501
|
||||
rating_stars = rating_stars + '<i class="fas fa-star-half-alt fa-xs"></i>'
|
||||
|
||||
rating_stars += '</span>'
|
||||
|
||||
|
||||
@@ -773,7 +773,7 @@ COOKPAD = {
|
||||
"text": "Water",
|
||||
"id": 49092
|
||||
},
|
||||
"note": "",
|
||||
"note": "2-3",
|
||||
"original": "2-3 c Water"
|
||||
},
|
||||
{
|
||||
@@ -1498,10 +1498,10 @@ GIALLOZAFFERANO = {
|
||||
"id": 64900
|
||||
},
|
||||
"ingredient": {
|
||||
"text": "Pane (raffermo o secco) 80 g",
|
||||
"text": "Pane 80 g",
|
||||
"id": 24720
|
||||
},
|
||||
"note": "",
|
||||
"note": "raffermo o secco",
|
||||
"original": "Pane (raffermo o secco) 80 g"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -53,7 +53,12 @@ def test_ingredient_parser():
|
||||
"50 g smör eller margarin": (50, "g", "smör eller margarin", ""),
|
||||
"3,5 l Wasser": (3.5, "l", "Wasser", ""),
|
||||
"3.5 l Wasser": (3.5, "l", "Wasser", ""),
|
||||
"400 g Karotte(n)": (400, "g", "Karotte(n)", "")
|
||||
"400 g Karotte(n)": (400, "g", "Karotte(n)", ""),
|
||||
"400g unsalted butter": (400, "g", "butter", "unsalted"),
|
||||
"2L Wasser": (2, "L", "Wasser", ""),
|
||||
"1 (16 ounce) package dry lentils, rinsed": (1, "package", "dry lentils, rinsed", "16 ounce"),
|
||||
"2-3 c Water": (2, "c", "Water", "2-3"),
|
||||
"Pane (raffermo o secco) 80 g": (0, "", "Pane 80 g", "raffermo o secco"), #TODO this is actually not a good result but currently expected
|
||||
}
|
||||
# for German you could say that if an ingredient does not have
|
||||
# an amount # and it starts with a lowercase letter, then that
|
||||
|
||||
@@ -36,6 +36,7 @@ router.register(r'recipe-book', api.RecipeBookViewSet)
|
||||
router.register(r'recipe-book-entry', api.RecipeBookEntryViewSet)
|
||||
router.register(r'supermarket', api.SupermarketViewSet)
|
||||
router.register(r'supermarket-category', api.SupermarketCategoryViewSet)
|
||||
router.register(r'supermarket-category-relation', api.SupermarketCategoryRelationViewSet)
|
||||
router.register(r'import-log', api.ImportLogViewSet)
|
||||
router.register(r'bookmarklet-import', api.BookmarkletImportViewSet)
|
||||
router.register(r'user-file', api.UserFileViewSet)
|
||||
|
||||
@@ -41,7 +41,7 @@ from cookbook.models import (CookLog, Food, Ingredient, Keyword, MealPlan,
|
||||
MealType, Recipe, RecipeBook, ShoppingList,
|
||||
ShoppingListEntry, ShoppingListRecipe, Step,
|
||||
Storage, Sync, SyncLog, Unit, UserPreference,
|
||||
ViewLog, RecipeBookEntry, Supermarket, ImportLog, BookmarkletImport, SupermarketCategory, UserFile, ShareLink)
|
||||
ViewLog, RecipeBookEntry, Supermarket, ImportLog, BookmarkletImport, SupermarketCategory, UserFile, ShareLink, SupermarketCategoryRelation)
|
||||
from cookbook.provider.dropbox import Dropbox
|
||||
from cookbook.provider.local import Local
|
||||
from cookbook.provider.nextcloud import Nextcloud
|
||||
@@ -58,7 +58,7 @@ from cookbook.serializer import (FoodSerializer, IngredientSerializer,
|
||||
UserNameSerializer, UserPreferenceSerializer,
|
||||
ViewLogSerializer, CookLogSerializer, RecipeBookEntrySerializer,
|
||||
RecipeOverviewSerializer, SupermarketSerializer, ImportLogSerializer,
|
||||
BookmarkletImportSerializer, SupermarketCategorySerializer, UserFileSerializer)
|
||||
BookmarkletImportSerializer, SupermarketCategorySerializer, UserFileSerializer, SupermarketCategoryRelationSerializer)
|
||||
|
||||
|
||||
class StandardFilterMixin(ViewSetMixin):
|
||||
@@ -169,6 +169,16 @@ class SupermarketCategoryViewSet(viewsets.ModelViewSet, StandardFilterMixin):
|
||||
return super().get_queryset()
|
||||
|
||||
|
||||
class SupermarketCategoryRelationViewSet(viewsets.ModelViewSet, StandardFilterMixin):
|
||||
queryset = SupermarketCategoryRelation.objects
|
||||
serializer_class = SupermarketCategoryRelationSerializer
|
||||
permission_classes = [CustomIsUser]
|
||||
|
||||
def get_queryset(self):
|
||||
self.queryset = self.queryset.filter(supermarket__space=self.request.space)
|
||||
return super().get_queryset()
|
||||
|
||||
|
||||
class KeywordViewSet(viewsets.ModelViewSet, StandardFilterMixin):
|
||||
"""
|
||||
list:
|
||||
@@ -218,12 +228,30 @@ class RecipeBookViewSet(viewsets.ModelViewSet, StandardFilterMixin):
|
||||
|
||||
|
||||
class RecipeBookEntryViewSet(viewsets.ModelViewSet, viewsets.GenericViewSet):
|
||||
"""
|
||||
list:
|
||||
optional parameters
|
||||
|
||||
- **recipe**: id of recipe - only return books for that recipe
|
||||
- **book**: id of book - only return recipes in that book
|
||||
|
||||
"""
|
||||
queryset = RecipeBookEntry.objects
|
||||
serializer_class = RecipeBookEntrySerializer
|
||||
permission_classes = [CustomIsOwner]
|
||||
|
||||
def get_queryset(self):
|
||||
return self.queryset.filter(book__created_by=self.request.user).filter(book__space=self.request.space)
|
||||
queryset = self.queryset.filter(Q(book__created_by=self.request.user) | Q(book__shared=self.request.user)).filter(book__space=self.request.space)
|
||||
|
||||
recipe_id = self.request.query_params.get('recipe', None)
|
||||
if recipe_id is not None:
|
||||
queryset = queryset.filter(recipe__pk=recipe_id)
|
||||
|
||||
book_id = self.request.query_params.get('book', None)
|
||||
if book_id is not None:
|
||||
queryset = queryset.filter(book__pk=book_id)
|
||||
|
||||
return queryset
|
||||
|
||||
|
||||
class MealPlanViewSet(viewsets.ModelViewSet):
|
||||
@@ -347,6 +375,11 @@ class RecipeSchema(AutoSchema):
|
||||
"description": 'true or false. returns the results in randomized order.',
|
||||
'schema': {'type': 'string', },
|
||||
})
|
||||
parameters.append({
|
||||
"name": 'new', "in": "query", "required": False,
|
||||
"description": 'true or false. returns new results first in search results',
|
||||
'schema': {'type': 'string', },
|
||||
})
|
||||
return parameters
|
||||
|
||||
|
||||
@@ -592,7 +625,7 @@ def share_link(request, pk):
|
||||
def log_cooking(request, recipe_id):
|
||||
recipe = get_object_or_None(Recipe, id=recipe_id)
|
||||
if recipe:
|
||||
log = CookLog.objects.create(created_by=request.user, recipe=recipe)
|
||||
log = CookLog.objects.create(created_by=request.user, recipe=recipe, space=request.space)
|
||||
servings = request.GET['s'] if 's' in request.GET else None
|
||||
if servings and re.match(r'^([1-9])+$', servings):
|
||||
log.servings = int(servings)
|
||||
|
||||
@@ -188,16 +188,19 @@ def import_url(request):
|
||||
try:
|
||||
response = requests.get(data['image'])
|
||||
|
||||
img, filetype = handle_image(request, response.content)
|
||||
img, filetype = handle_image(request, BytesIO(response.content))
|
||||
recipe.image = File(
|
||||
img, name=f'{uuid.uuid4()}_{recipe.pk}{filetype}'
|
||||
)
|
||||
recipe.save()
|
||||
except UnidentifiedImageError:
|
||||
except UnidentifiedImageError as e:
|
||||
print(e)
|
||||
pass
|
||||
except MissingSchema:
|
||||
except MissingSchema as e:
|
||||
print(e)
|
||||
pass
|
||||
except:
|
||||
except Exception as e:
|
||||
print(e)
|
||||
pass
|
||||
|
||||
return HttpResponse(reverse('view_recipe', args=[recipe.pk]))
|
||||
|
||||
@@ -41,7 +41,7 @@ class RecipeCreate(GroupRequiredMixin, CreateView):
|
||||
obj.space = self.request.space
|
||||
obj.internal = True
|
||||
obj.save()
|
||||
obj.steps.add(Step.objects.create())
|
||||
obj.steps.add(Step.objects.create(space=self.request.space))
|
||||
return HttpResponseRedirect(reverse('edit_recipe', kwargs={'pk': obj.pk}))
|
||||
|
||||
def get_success_url(self):
|
||||
|
||||
@@ -14,7 +14,7 @@ from django.contrib.auth.password_validation import validate_password
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import IntegrityError
|
||||
from django.db.models import Avg, Q, Sum
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.http import HttpResponseRedirect, JsonResponse
|
||||
from django.shortcuts import get_object_or_404, render, redirect
|
||||
from django.urls import reverse, reverse_lazy
|
||||
from django.utils import timezone
|
||||
@@ -27,6 +27,7 @@ from cookbook.filters import RecipeFilter
|
||||
from cookbook.forms import (CommentForm, Recipe, RecipeBookEntryForm, User,
|
||||
UserCreateForm, UserNameForm, UserPreference,
|
||||
UserPreferenceForm, SpaceJoinForm, SpaceCreateForm, AllAuthSignupForm)
|
||||
from cookbook.helper.ingredient_parser import parse
|
||||
from cookbook.helper.permission_helper import group_required, share_link_valid, has_group_permission
|
||||
from cookbook.models import (Comment, CookLog, InviteLink, MealPlan,
|
||||
RecipeBook, RecipeBookEntry, ViewLog, ShoppingList, Space, Keyword, RecipeImport, Unit,
|
||||
@@ -114,10 +115,12 @@ def no_space(request):
|
||||
created_space = Space.objects.create(
|
||||
name=create_form.cleaned_data['name'],
|
||||
created_by=request.user,
|
||||
allow_files=settings.SPACE_DEFAULT_FILES,
|
||||
max_file_storage_mb=settings.SPACE_DEFAULT_MAX_FILES,
|
||||
max_recipes=settings.SPACE_DEFAULT_MAX_RECIPES,
|
||||
max_users=settings.SPACE_DEFAULT_MAX_USERS,
|
||||
allow_sharing=settings.SPACE_DEFAULT_ALLOW_SHARING,
|
||||
)
|
||||
|
||||
request.user.userpreference.space = created_space
|
||||
request.user.userpreference.save()
|
||||
request.user.groups.add(Group.objects.filter(name='admin').get())
|
||||
@@ -137,7 +140,7 @@ def no_space(request):
|
||||
if 'signup_token' in request.session:
|
||||
return HttpResponseRedirect(reverse('view_invite', args=[request.session.pop('signup_token', '')]))
|
||||
|
||||
create_form = SpaceCreateForm()
|
||||
create_form = SpaceCreateForm(initial={'name': f'{request.user.username}\'s Space'})
|
||||
join_form = SpaceJoinForm()
|
||||
|
||||
return render(request, 'no_space_info.html', {'create_form': create_form, 'join_form': join_form})
|
||||
@@ -303,8 +306,6 @@ def user_settings(request):
|
||||
up = request.user.userpreference
|
||||
|
||||
user_name_form = UserNameForm(instance=request.user)
|
||||
password_form = PasswordChangeForm(request.user)
|
||||
password_form.fields['old_password'].widget.attrs.pop("autofocus", None)
|
||||
|
||||
if request.method == "POST":
|
||||
if 'preference_form' in request.POST:
|
||||
@@ -338,12 +339,6 @@ def user_settings(request):
|
||||
request.user.last_name = user_name_form.cleaned_data['last_name']
|
||||
request.user.save()
|
||||
|
||||
if 'password_form' in request.POST:
|
||||
password_form = PasswordChangeForm(request.user, request.POST)
|
||||
if password_form.is_valid():
|
||||
user = password_form.save()
|
||||
update_session_auth_hash(request, user)
|
||||
|
||||
if up:
|
||||
preference_form = UserPreferenceForm(instance=up)
|
||||
else:
|
||||
@@ -355,7 +350,6 @@ def user_settings(request):
|
||||
return render(request, 'settings.html', {
|
||||
'preference_form': preference_form,
|
||||
'user_name_form': user_name_form,
|
||||
'password_form': password_form,
|
||||
'api_token': api_token,
|
||||
})
|
||||
|
||||
@@ -551,6 +545,7 @@ def offline(request):
|
||||
def test(request):
|
||||
if not settings.DEBUG:
|
||||
return HttpResponseRedirect(reverse('index'))
|
||||
return JsonResponse(parse('Pane (raffermo o secco) 80 g'), safe=False)
|
||||
|
||||
|
||||
def test2(request):
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://docs.tandoor.dev/install/docker/" target="_blank" rel="noopener noreferrer">Installation</a> •
|
||||
<a href="https://docs.tandoor.dev/install/docker.html" rel="noopener noreferrer">Installation</a> •
|
||||
<a href="https://docs.tandoor.dev/" target="_blank" rel="noopener noreferrer">Documentation</a> •
|
||||
<a href="https://app.tandoor.dev/" target="_blank" rel="noopener noreferrer">Demo</a>
|
||||
</p>
|
||||
@@ -70,4 +70,4 @@ Over the time tons of features have been added making this the most comprehensiv
|
||||
I am just a single developer with many other interests and obligations so development and support might be slow at times,
|
||||
but I try my best to constantly improve this application.
|
||||
|
||||
If you have any wishes, feature requests, problems or ideas feel free to open an issue on GitHub.
|
||||
If you have any wishes, feature requests, problems or ideas feel free to open an issue on GitHub.
|
||||
|
||||
@@ -7,16 +7,32 @@ These intructions are inspired from a standard django/gunicorn/postgresql instru
|
||||
|
||||
## Prerequisites
|
||||
|
||||
*Optional*: create a virtual env and activate it
|
||||
Setup user: `sudo useradd recipes`
|
||||
|
||||
Get the last version from the repository: `git clone https://github.com/vabene1111/recipes.git -b master`
|
||||
|
||||
Install postgresql requirements: `sudo apt install libpq-dev postgresql`
|
||||
Install project requirements: `pip3.9 install -r requirements.txt`
|
||||
Move it to the `/var/www` directory: `mv recipes /var/www`
|
||||
|
||||
Change to the directory: `cd /var/www/recipes`
|
||||
|
||||
Give the user permissions: `chown -R recipes:www-data /var/www/recipes`
|
||||
|
||||
Create virtual env: `python3.9 -m venv /var/www/recipes`
|
||||
|
||||
### Install postgresql requirements
|
||||
|
||||
`sudo apt install libpq-dev postgresql`
|
||||
|
||||
###Install project requirements
|
||||
|
||||
Using binaries from the virtual env:
|
||||
|
||||
`/var/www/recipes/bin/pip3.9 install -r requirements.txt`
|
||||
|
||||
|
||||
## Setup postgresql
|
||||
|
||||
Run `sudo -u postgres psql`
|
||||
`sudo -u postgres psql`
|
||||
|
||||
In the psql console:
|
||||
|
||||
@@ -37,12 +53,18 @@ ALTER USER djangouser WITH SUPERUSER;
|
||||
|
||||
Download the `.env` configuration file and **edit it accordingly**.
|
||||
```shell
|
||||
wget https://raw.githubusercontent.com/vabene1111/recipes/develop/.env.template -O .env
|
||||
wget https://raw.githubusercontent.com/vabene1111/recipes/develop/.env.template -O /var/www/recipes/.env
|
||||
```
|
||||
|
||||
Things to edit:
|
||||
- `SECRET_KEY`: use something secure.
|
||||
- `POSTGRES_HOST`: probably 127.0.0.1.
|
||||
- `POSTGRES_PASSWORD`: the password we set earlier when setting up djangodb.
|
||||
- `STATIC_URL`, `MEDIA_URL`: these will be in `/var/www/recipes`, under `/staticfiles/` and `/mediafiles/` respectively.
|
||||
|
||||
## Initialize the application
|
||||
|
||||
Execute `export $(cat .env |grep "^[^#]" | xargs)` to load variables from `.env`
|
||||
Execute `export $(cat /var/www/recipes/.env |grep "^[^#]" | xargs)` to load variables from `/var/www/recipes/.env`
|
||||
|
||||
Execute `/python3.9 manage.py migrate`
|
||||
|
||||
@@ -67,10 +89,11 @@ After=network.target
|
||||
Type=simple
|
||||
Restart=always
|
||||
RestartSec=3
|
||||
User=recipes
|
||||
Group=www-data
|
||||
WorkingDirectory=/media/data/recipes
|
||||
EnvironmentFile=/media/data/recipes/.env
|
||||
ExecStart=/opt/.pyenv/versions/3.9/bin/gunicorn --error-logfile /tmp/gunicorn_err.log --log-level debug --capture-output --bind unix:/media/data/recipes/recipes.sock recipes.wsgi:application
|
||||
WorkingDirectory=/var/www/recipes
|
||||
EnvironmentFile=/var/www/recipes/.env
|
||||
ExecStart=/var/www/recipes/bin/gunicorn --error-logfile /tmp/gunicorn_err.log --log-level debug --capture-output --bind unix:/var/www/recipes/recipes.sock recipes.wsgi:application
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -80,11 +103,11 @@ WantedBy=multi-user.target
|
||||
|
||||
*Note2*: Fix the path in the `ExecStart` line to where you gunicorn and recipes are
|
||||
|
||||
Finally, run `sudo systemctl enable gunicorn_recipes.service` and `sudo systemctl start gunicorn_recipes.service`. You can check that the service is correctly started with `systemctl status gunicorn_recipes.service`
|
||||
Finally, run `sudo systemctl enable gunicorn_recipes` and `sudo systemctl start gunicorn_recipes`. You can check that the service is correctly started with `systemctl status gunicorn_recipes`
|
||||
|
||||
### nginx
|
||||
|
||||
Now we tell nginx to listen to a new port and forward that to gunicorn. `sudo nano /etc/nginx/sites-available/recipes.conf`
|
||||
Now we tell nginx to listen to a new port and forward that to gunicorn. `sudo nano /etc/nginx/conf.d/recipes.conf`
|
||||
|
||||
And enter these lines:
|
||||
|
||||
@@ -95,20 +118,21 @@ server {
|
||||
#error_log /var/log/nginx/error.log;
|
||||
|
||||
# serve media files
|
||||
location /static {
|
||||
alias /media/data/recipes/staticfiles;
|
||||
location /staticfiles {
|
||||
alias /var/www/recipes/staticfiles;
|
||||
}
|
||||
|
||||
location /media {
|
||||
alias /media/data/recipes/mediafiles;
|
||||
location /mediafiles {
|
||||
alias /var/www/recipes/mediafiles;
|
||||
}
|
||||
|
||||
location / {
|
||||
proxy_pass http://unix:/media/data/recipes/recipes.sock;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_pass http://unix:/var/www/recipes/recipes.sock;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
*Note*: Enter the correct path in static and proxy_pass lines.
|
||||
|
||||
Enable the website `sudo ln -s /etc/nginx/sites-available/recipes.conf /etc/nginx/sites-enabled` and restart nginx : `sudo systemctl restart nginx.service`
|
||||
Reload nginx : `sudo systemctl reload nginx`
|
||||
|
||||
@@ -4,8 +4,13 @@
|
||||
Many people appear to host this application on their Synology NAS. The following documentation was provided by
|
||||
@therealschimmi in [this issue discussion](https://github.com/vabene1111/recipes/issues/98#issuecomment-643062907).
|
||||
|
||||
There is also this
|
||||
([word](https://github.com/vabene1111/recipes/files/6708738/Tandoor.on.a.Synology.Disk.Station.docx),
|
||||
[pdf](https://github.com/vabene1111/recipes/files/6901601/Tandoor.on.a.Synology.Disk.Station.pdf)) awesome and
|
||||
very detailed guide provided by @DiversityBug.
|
||||
|
||||
There are, as always, most likely other ways to do this but this can be used as a starting point for your
|
||||
setup. Since i cannot test it myself feedback and improvements are always very welcome.
|
||||
setup. Since I cannot test it myself feedback and improvements are always very welcome.
|
||||
|
||||
## Instructions
|
||||
|
||||
@@ -15,7 +20,7 @@ Basic guide to setup `vabenee1111/recipes docker` container on Synology NAS
|
||||
|
||||
- Install Docker through package center
|
||||
- Optional: Create a shared folder for your docker projects, they have to store data somewhere outside the containers
|
||||
- Create a folder somewhere, i suggest naming it 'recipes' and storing it in the dedicated docker folder
|
||||
- Create a folder somewhere, I suggest naming it 'recipes' and storing it in the dedicated docker folder
|
||||
- Within, create the necessary folder structure. You will need these folders:
|
||||
|
||||

|
||||
|
||||
@@ -2,7 +2,7 @@ server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
|
||||
client_max_body_size 16M;
|
||||
client_max_body_size 128M;
|
||||
|
||||
# serve media files
|
||||
location /media/ {
|
||||
|
||||
Binary file not shown.
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-06-12 20:30+0200\n"
|
||||
"POT-Creation-Date: 2021-08-12 15:09+0200\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@@ -18,42 +18,42 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: .\recipes\settings.py:303
|
||||
#: .\recipes\settings.py:317
|
||||
msgid "Armenian "
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:304
|
||||
#: .\recipes\settings.py:318
|
||||
msgid "Catalan"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:305
|
||||
#: .\recipes\settings.py:319
|
||||
msgid "Czech"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:306
|
||||
#: .\recipes\settings.py:320
|
||||
msgid "Dutch"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:307
|
||||
#: .\recipes\settings.py:321
|
||||
msgid "English"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:308
|
||||
#: .\recipes\settings.py:322
|
||||
msgid "French"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:309
|
||||
#: .\recipes\settings.py:323
|
||||
msgid "German"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:310
|
||||
#: .\recipes\settings.py:324
|
||||
msgid "Italian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:311
|
||||
#: .\recipes\settings.py:325
|
||||
msgid "Latvian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:312
|
||||
#: .\recipes\settings.py:326
|
||||
msgid "Spanish"
|
||||
msgstr ""
|
||||
|
||||
Binary file not shown.
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-06-12 20:30+0200\n"
|
||||
"POT-Creation-Date: 2021-08-12 15:09+0200\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@@ -18,42 +18,42 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: .\recipes\settings.py:303
|
||||
#: .\recipes\settings.py:317
|
||||
msgid "Armenian "
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:304
|
||||
#: .\recipes\settings.py:318
|
||||
msgid "Catalan"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:305
|
||||
#: .\recipes\settings.py:319
|
||||
msgid "Czech"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:306
|
||||
#: .\recipes\settings.py:320
|
||||
msgid "Dutch"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:307
|
||||
#: .\recipes\settings.py:321
|
||||
msgid "English"
|
||||
msgstr "Englisch"
|
||||
|
||||
#: .\recipes\settings.py:308
|
||||
#: .\recipes\settings.py:322
|
||||
msgid "French"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:309
|
||||
#: .\recipes\settings.py:323
|
||||
msgid "German"
|
||||
msgstr "Deutsch"
|
||||
|
||||
#: .\recipes\settings.py:310
|
||||
#: .\recipes\settings.py:324
|
||||
msgid "Italian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:311
|
||||
#: .\recipes\settings.py:325
|
||||
msgid "Latvian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:312
|
||||
#: .\recipes\settings.py:326
|
||||
msgid "Spanish"
|
||||
msgstr ""
|
||||
|
||||
Binary file not shown.
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-06-12 20:30+0200\n"
|
||||
"POT-Creation-Date: 2021-08-12 15:09+0200\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@@ -18,42 +18,42 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: .\recipes\settings.py:303
|
||||
#: .\recipes\settings.py:317
|
||||
msgid "Armenian "
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:304
|
||||
#: .\recipes\settings.py:318
|
||||
msgid "Catalan"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:305
|
||||
#: .\recipes\settings.py:319
|
||||
msgid "Czech"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:306
|
||||
#: .\recipes\settings.py:320
|
||||
msgid "Dutch"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:307
|
||||
#: .\recipes\settings.py:321
|
||||
msgid "English"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:308
|
||||
#: .\recipes\settings.py:322
|
||||
msgid "French"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:309
|
||||
#: .\recipes\settings.py:323
|
||||
msgid "German"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:310
|
||||
#: .\recipes\settings.py:324
|
||||
msgid "Italian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:311
|
||||
#: .\recipes\settings.py:325
|
||||
msgid "Latvian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:312
|
||||
#: .\recipes\settings.py:326
|
||||
msgid "Spanish"
|
||||
msgstr ""
|
||||
|
||||
Binary file not shown.
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-06-12 20:30+0200\n"
|
||||
"POT-Creation-Date: 2021-08-12 15:09+0200\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@@ -18,42 +18,42 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: .\recipes\settings.py:303
|
||||
#: .\recipes\settings.py:317
|
||||
msgid "Armenian "
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:304
|
||||
#: .\recipes\settings.py:318
|
||||
msgid "Catalan"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:305
|
||||
#: .\recipes\settings.py:319
|
||||
msgid "Czech"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:306
|
||||
#: .\recipes\settings.py:320
|
||||
msgid "Dutch"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:307
|
||||
#: .\recipes\settings.py:321
|
||||
msgid "English"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:308
|
||||
#: .\recipes\settings.py:322
|
||||
msgid "French"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:309
|
||||
#: .\recipes\settings.py:323
|
||||
msgid "German"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:310
|
||||
#: .\recipes\settings.py:324
|
||||
msgid "Italian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:311
|
||||
#: .\recipes\settings.py:325
|
||||
msgid "Latvian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:312
|
||||
#: .\recipes\settings.py:326
|
||||
msgid "Spanish"
|
||||
msgstr ""
|
||||
|
||||
Binary file not shown.
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-06-12 20:30+0200\n"
|
||||
"POT-Creation-Date: 2021-08-12 15:09+0200\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@@ -18,42 +18,42 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
|
||||
#: .\recipes\settings.py:303
|
||||
#: .\recipes\settings.py:317
|
||||
msgid "Armenian "
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:304
|
||||
#: .\recipes\settings.py:318
|
||||
msgid "Catalan"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:305
|
||||
#: .\recipes\settings.py:319
|
||||
msgid "Czech"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:306
|
||||
#: .\recipes\settings.py:320
|
||||
msgid "Dutch"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:307
|
||||
#: .\recipes\settings.py:321
|
||||
msgid "English"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:308
|
||||
#: .\recipes\settings.py:322
|
||||
msgid "French"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:309
|
||||
#: .\recipes\settings.py:323
|
||||
msgid "German"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:310
|
||||
#: .\recipes\settings.py:324
|
||||
msgid "Italian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:311
|
||||
#: .\recipes\settings.py:325
|
||||
msgid "Latvian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:312
|
||||
#: .\recipes\settings.py:326
|
||||
msgid "Spanish"
|
||||
msgstr ""
|
||||
|
||||
Binary file not shown.
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-06-12 20:30+0200\n"
|
||||
"POT-Creation-Date: 2021-08-12 15:09+0200\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@@ -17,42 +17,42 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: .\recipes\settings.py:303
|
||||
#: .\recipes\settings.py:317
|
||||
msgid "Armenian "
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:304
|
||||
#: .\recipes\settings.py:318
|
||||
msgid "Catalan"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:305
|
||||
#: .\recipes\settings.py:319
|
||||
msgid "Czech"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:306
|
||||
#: .\recipes\settings.py:320
|
||||
msgid "Dutch"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:307
|
||||
#: .\recipes\settings.py:321
|
||||
msgid "English"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:308
|
||||
#: .\recipes\settings.py:322
|
||||
msgid "French"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:309
|
||||
#: .\recipes\settings.py:323
|
||||
msgid "German"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:310
|
||||
#: .\recipes\settings.py:324
|
||||
msgid "Italian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:311
|
||||
#: .\recipes\settings.py:325
|
||||
msgid "Latvian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:312
|
||||
#: .\recipes\settings.py:326
|
||||
msgid "Spanish"
|
||||
msgstr ""
|
||||
|
||||
Binary file not shown.
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-06-12 20:30+0200\n"
|
||||
"POT-Creation-Date: 2021-08-12 15:09+0200\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@@ -18,42 +18,42 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: .\recipes\settings.py:303
|
||||
#: .\recipes\settings.py:317
|
||||
msgid "Armenian "
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:304
|
||||
#: .\recipes\settings.py:318
|
||||
msgid "Catalan"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:305
|
||||
#: .\recipes\settings.py:319
|
||||
msgid "Czech"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:306
|
||||
#: .\recipes\settings.py:320
|
||||
msgid "Dutch"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:307
|
||||
#: .\recipes\settings.py:321
|
||||
msgid "English"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:308
|
||||
#: .\recipes\settings.py:322
|
||||
msgid "French"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:309
|
||||
#: .\recipes\settings.py:323
|
||||
msgid "German"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:310
|
||||
#: .\recipes\settings.py:324
|
||||
msgid "Italian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:311
|
||||
#: .\recipes\settings.py:325
|
||||
msgid "Latvian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:312
|
||||
#: .\recipes\settings.py:326
|
||||
msgid "Spanish"
|
||||
msgstr ""
|
||||
|
||||
Binary file not shown.
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-06-12 20:30+0200\n"
|
||||
"POT-Creation-Date: 2021-08-12 15:09+0200\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@@ -19,42 +19,42 @@ msgstr ""
|
||||
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : "
|
||||
"2);\n"
|
||||
|
||||
#: .\recipes\settings.py:303
|
||||
#: .\recipes\settings.py:317
|
||||
msgid "Armenian "
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:304
|
||||
#: .\recipes\settings.py:318
|
||||
msgid "Catalan"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:305
|
||||
#: .\recipes\settings.py:319
|
||||
msgid "Czech"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:306
|
||||
#: .\recipes\settings.py:320
|
||||
msgid "Dutch"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:307
|
||||
#: .\recipes\settings.py:321
|
||||
msgid "English"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:308
|
||||
#: .\recipes\settings.py:322
|
||||
msgid "French"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:309
|
||||
#: .\recipes\settings.py:323
|
||||
msgid "German"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:310
|
||||
#: .\recipes\settings.py:324
|
||||
msgid "Italian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:311
|
||||
#: .\recipes\settings.py:325
|
||||
msgid "Latvian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:312
|
||||
#: .\recipes\settings.py:326
|
||||
msgid "Spanish"
|
||||
msgstr ""
|
||||
|
||||
Binary file not shown.
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-06-12 20:30+0200\n"
|
||||
"POT-Creation-Date: 2021-08-12 15:09+0200\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@@ -18,42 +18,42 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: .\recipes\settings.py:303
|
||||
#: .\recipes\settings.py:317
|
||||
msgid "Armenian "
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:304
|
||||
#: .\recipes\settings.py:318
|
||||
msgid "Catalan"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:305
|
||||
#: .\recipes\settings.py:319
|
||||
msgid "Czech"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:306
|
||||
#: .\recipes\settings.py:320
|
||||
msgid "Dutch"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:307
|
||||
#: .\recipes\settings.py:321
|
||||
msgid "English"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:308
|
||||
#: .\recipes\settings.py:322
|
||||
msgid "French"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:309
|
||||
#: .\recipes\settings.py:323
|
||||
msgid "German"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:310
|
||||
#: .\recipes\settings.py:324
|
||||
msgid "Italian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:311
|
||||
#: .\recipes\settings.py:325
|
||||
msgid "Latvian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:312
|
||||
#: .\recipes\settings.py:326
|
||||
msgid "Spanish"
|
||||
msgstr ""
|
||||
|
||||
Binary file not shown.
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-06-12 20:30+0200\n"
|
||||
"POT-Creation-Date: 2021-08-12 15:09+0200\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@@ -18,42 +18,42 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: .\recipes\settings.py:303
|
||||
#: .\recipes\settings.py:317
|
||||
msgid "Armenian "
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:304
|
||||
#: .\recipes\settings.py:318
|
||||
msgid "Catalan"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:305
|
||||
#: .\recipes\settings.py:319
|
||||
msgid "Czech"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:306
|
||||
#: .\recipes\settings.py:320
|
||||
msgid "Dutch"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:307
|
||||
#: .\recipes\settings.py:321
|
||||
msgid "English"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:308
|
||||
#: .\recipes\settings.py:322
|
||||
msgid "French"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:309
|
||||
#: .\recipes\settings.py:323
|
||||
msgid "German"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:310
|
||||
#: .\recipes\settings.py:324
|
||||
msgid "Italian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:311
|
||||
#: .\recipes\settings.py:325
|
||||
msgid "Latvian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:312
|
||||
#: .\recipes\settings.py:326
|
||||
msgid "Spanish"
|
||||
msgstr ""
|
||||
|
||||
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user