mirror of
https://github.com/TandoorRecipes/recipes.git
synced 2025-12-26 03:43:34 -05:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ed313cbf9a | ||
|
|
b1db591e9f | ||
|
|
94c51f90cd | ||
|
|
3074d916dc | ||
|
|
bf467b1ec0 | ||
|
|
348c1c78f1 | ||
|
|
913e896906 | ||
|
|
a0a673a0c9 | ||
|
|
3d60379ed0 | ||
|
|
fd7e20a46b | ||
|
|
a970f0c00e | ||
|
|
297dd6244a | ||
|
|
c83eb1a42b | ||
|
|
8181a6d416 | ||
|
|
4a8b50aeba | ||
|
|
388ef32475 | ||
|
|
bfe72210df | ||
|
|
02c5aed0a3 |
@@ -57,7 +57,9 @@ GUNICORN_MEDIA=0
|
||||
# S3_ACCESS_KEY=
|
||||
# S3_SECRET_ACCESS_KEY=
|
||||
# S3_BUCKET_NAME=
|
||||
# S3_REGION_NAME= # default none, set your region might be required
|
||||
# S3_QUERYSTRING_AUTH=1 # default true, set to 0 to serve media from a public bucket without signed urls
|
||||
# S3_QUERYSTRING_EXPIRE=3600 # number of seconds querystring are valid for
|
||||
# S3_ENDPOINT_URL= # when using a custom endpoint like minio
|
||||
|
||||
# Email Settings, see https://docs.djangoproject.com/en/3.2/ref/settings/#email-host
|
||||
@@ -85,6 +87,20 @@ REVERSE_PROXY_AUTH=0
|
||||
# when unset: 0 (false)
|
||||
# ENABLE_SIGNUP=0
|
||||
|
||||
# If signup is enabled you might want to add a captcha to it to prevent spam
|
||||
# HCAPTCHA_SITEKEY=
|
||||
# HCAPTCHA_SECRET=
|
||||
|
||||
# if signup is enabled you might want to provide urls to data protection policies or terms and conditions
|
||||
# TERMS_URL=
|
||||
# PRIVACY_URL=
|
||||
# IMPRINT_URL=
|
||||
|
||||
# enable serving of prometheus metrics under the /metrics path
|
||||
# ATTENTION: view is not secured (as per the prometheus default way) so make sure to secure it
|
||||
# trough your web server (or leave it open of you dont care if the stats are exposed)
|
||||
# ENABLE_METRICS=0
|
||||
|
||||
# allows you to setup OAuth providers
|
||||
# see docs for more information https://vabene1111.github.io/recipes/features/authentication/
|
||||
# SOCIAL_PROVIDERS = allauth.socialaccount.providers.github, allauth.socialaccount.providers.nextcloud,
|
||||
|
||||
@@ -166,7 +166,7 @@ admin.site.register(ViewLog, ViewLogAdmin)
|
||||
|
||||
class InviteLinkAdmin(admin.ModelAdmin):
|
||||
list_display = (
|
||||
'username', 'group', 'valid_until',
|
||||
'group', 'valid_until',
|
||||
'created_by', 'created_at', 'used_by'
|
||||
)
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.forms import widgets
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django_scopes import scopes_disabled
|
||||
from django_scopes.forms import SafeModelChoiceField, SafeModelMultipleChoiceField
|
||||
from emoji_picker.widgets import EmojiPickerTextInput
|
||||
from hcaptcha.fields import hCaptchaField
|
||||
|
||||
from .models import (Comment, Food, InviteLink, Keyword, MealPlan, Recipe,
|
||||
RecipeBook, RecipeBookEntry, Storage, Sync, Unit, User,
|
||||
@@ -411,19 +413,11 @@ class InviteLinkForm(forms.ModelForm):
|
||||
|
||||
return email
|
||||
|
||||
def clean_username(self):
|
||||
username = self.cleaned_data['username']
|
||||
with scopes_disabled():
|
||||
if username != '' and (User.objects.filter(username=username).exists() or InviteLink.objects.filter(username=username).exists()):
|
||||
raise ValidationError(_('Username already taken!'))
|
||||
return username
|
||||
|
||||
class Meta:
|
||||
model = InviteLink
|
||||
fields = ('username', 'email', 'group', 'valid_until', 'space')
|
||||
fields = ('email', 'group', 'valid_until', 'space')
|
||||
help_texts = {
|
||||
'username': _('A username is not required, if left blank the new user can choose one.'),
|
||||
'email': _('An email address is not required but if present the invite link will be send to the user.')
|
||||
'email': _('An email address is not required but if present the invite link will be send to the user.'),
|
||||
}
|
||||
field_classes = {
|
||||
'space': SafeModelChoiceField,
|
||||
@@ -447,6 +441,21 @@ class SpaceJoinForm(forms.Form):
|
||||
token = forms.CharField()
|
||||
|
||||
|
||||
class AllAuthSignupForm(forms.Form):
|
||||
captcha = hCaptchaField()
|
||||
terms = forms.BooleanField(label=_('Accept Terms and Privacy'))
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(AllAuthSignupForm, self).__init__(**kwargs)
|
||||
if settings.PRIVACY_URL == '' and settings.TERMS_URL == '':
|
||||
self.fields.pop('terms')
|
||||
if settings.HCAPTCHA_SECRET == '':
|
||||
self.fields.pop('captcha')
|
||||
|
||||
def signup(self, request, user):
|
||||
pass
|
||||
|
||||
|
||||
class UserCreateForm(forms.Form):
|
||||
name = forms.CharField(label='Username')
|
||||
password = forms.CharField(
|
||||
|
||||
19
cookbook/helper/CustomStorageClass.py
Normal file
19
cookbook/helper/CustomStorageClass.py
Normal file
@@ -0,0 +1,19 @@
|
||||
import hashlib
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.cache import cache
|
||||
from storages.backends.s3boto3 import S3Boto3Storage
|
||||
|
||||
|
||||
class CachedS3Boto3Storage(S3Boto3Storage):
|
||||
def url(self, name, **kwargs):
|
||||
key = hashlib.md5(f'recipes_media_urls_{name}'.encode('utf-8')).hexdigest()
|
||||
if result := cache.get(key):
|
||||
return result
|
||||
|
||||
result = super(CachedS3Boto3Storage, self).url(name, **kwargs)
|
||||
|
||||
timeout = int(settings.AWS_QUERYSTRING_EXPIRE * .95)
|
||||
cache.set(key, result, timeout)
|
||||
|
||||
return result
|
||||
13
cookbook/helper/context_processors.py
Normal file
13
cookbook/helper/context_processors.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
def context_settings(request):
|
||||
return {
|
||||
'EMAIL_ENABLED': settings.EMAIL_HOST != '',
|
||||
'SIGNUP_ENABLED': settings.ENABLE_SIGNUP,
|
||||
'CAPTCHA_ENABLED': settings.HCAPTCHA_SITEKEY != '',
|
||||
'HOSTED': settings.HOSTED,
|
||||
'TERMS_URL': settings.TERMS_URL,
|
||||
'PRIVACY_URL': settings.PRIVACY_URL,
|
||||
'IMPRINT_URL': settings.IMPRINT_URL,
|
||||
}
|
||||
@@ -27,7 +27,7 @@ def search_recipes(request, queryset, params):
|
||||
last_viewed_recipes = ViewLog.objects.filter(created_by=request.user, space=request.space,
|
||||
created_at__gte=datetime.now() - timedelta(days=14)).order_by('pk').values_list('recipe__pk', flat=True).distinct()
|
||||
|
||||
return queryset.filter(pk__in=last_viewed_recipes[len(last_viewed_recipes)-search_last_viewed:])
|
||||
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)),
|
||||
|
||||
@@ -16,7 +16,10 @@ class ScopeMiddleware:
|
||||
with scopes_disabled():
|
||||
return self.get_response(request)
|
||||
|
||||
if request.path.startswith('/signup/'):
|
||||
if request.path.startswith('/signup/') or request.path.startswith('/invite/'):
|
||||
return self.get_response(request)
|
||||
|
||||
if request.path.startswith('/accounts/'):
|
||||
return self.get_response(request)
|
||||
|
||||
with scopes_disabled():
|
||||
|
||||
@@ -16,8 +16,10 @@ class Mealie(Integration):
|
||||
def get_recipe_from_file(self, file):
|
||||
recipe_json = json.loads(file.getvalue().decode("utf-8"))
|
||||
|
||||
description = '' if len(recipe_json['description'].strip()) > 500 else recipe_json['description'].strip()
|
||||
|
||||
recipe = Recipe.objects.create(
|
||||
name=recipe_json['name'].strip(), description=recipe_json['description'].strip(),
|
||||
name=recipe_json['name'].strip(), description=description,
|
||||
created_by=self.request.user, internal=True, space=self.request.space)
|
||||
|
||||
# TODO parse times (given in PT2H3M )
|
||||
@@ -30,6 +32,9 @@ class Mealie(Integration):
|
||||
if not ingredients_added:
|
||||
ingredients_added = True
|
||||
|
||||
if len(recipe_json['description'].strip()) > 500:
|
||||
step.instruction = recipe_json['description'].strip() + '\n\n' + step.instruction
|
||||
|
||||
for ingredient in recipe_json['recipeIngredient']:
|
||||
amount, unit, ingredient, note = parse(ingredient)
|
||||
f = get_food(ingredient, self.request.space)
|
||||
|
||||
@@ -16,8 +16,10 @@ class NextcloudCookbook(Integration):
|
||||
def get_recipe_from_file(self, file):
|
||||
recipe_json = json.loads(file.getvalue().decode("utf-8"))
|
||||
|
||||
description = '' if len(recipe_json['description'].strip()) > 500 else recipe_json['description'].strip()
|
||||
|
||||
recipe = Recipe.objects.create(
|
||||
name=recipe_json['name'].strip(), description=recipe_json['description'].strip(),
|
||||
name=recipe_json['name'].strip(), description=description,
|
||||
created_by=self.request.user, internal=True,
|
||||
servings=recipe_json['recipeYield'], space=self.request.space)
|
||||
|
||||
@@ -30,6 +32,9 @@ class NextcloudCookbook(Integration):
|
||||
instruction=s
|
||||
)
|
||||
if not ingredients_added:
|
||||
if len(recipe_json['description'].strip()) > 500:
|
||||
step.instruction = recipe_json['description'].strip() + '\n\n' + step.instruction
|
||||
|
||||
ingredients_added = True
|
||||
|
||||
for ingredient in recipe_json['recipeIngredient']:
|
||||
|
||||
@@ -23,10 +23,10 @@ class Paprika(Integration):
|
||||
name=recipe_json['name'].strip(), created_by=self.request.user, internal=True, space=self.request.space)
|
||||
|
||||
if 'description' in recipe_json:
|
||||
recipe.description = recipe_json['description'].strip()
|
||||
recipe.description = '' if len(recipe_json['description'].strip()) > 500 else recipe_json['description'].strip()
|
||||
|
||||
try:
|
||||
if re.match(r'([0-9])+\s(.)*', recipe_json['servings'] ):
|
||||
if re.match(r'([0-9])+\s(.)*', recipe_json['servings']):
|
||||
s = recipe_json['servings'].split(' ')
|
||||
recipe.servings = s[0]
|
||||
recipe.servings_text = s[1]
|
||||
@@ -58,6 +58,9 @@ class Paprika(Integration):
|
||||
instruction=instructions
|
||||
)
|
||||
|
||||
if len(recipe_json['description'].strip()) > 500:
|
||||
step.instruction = recipe_json['description'].strip() + '\n\n' + step.instruction
|
||||
|
||||
if 'categories' in recipe_json:
|
||||
for c in recipe_json['categories']:
|
||||
keyword, created = Keyword.objects.get_or_create(name=c.strip(), space=self.request.space)
|
||||
@@ -79,5 +82,5 @@ class Paprika(Integration):
|
||||
|
||||
if recipe_json.get("photo_data", None):
|
||||
self.import_recipe_image(recipe, BytesIO(base64.b64decode(recipe_json['photo_data'])))
|
||||
|
||||
|
||||
return recipe
|
||||
|
||||
18
cookbook/migrations/0125_space_demo.py
Normal file
18
cookbook/migrations/0125_space_demo.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 3.2.3 on 2021-06-04 14:52
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('cookbook', '0124_alter_userpreference_theme'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='space',
|
||||
name='demo',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
]
|
||||
18
cookbook/migrations/0126_alter_userpreference_theme.py
Normal file
18
cookbook/migrations/0126_alter_userpreference_theme.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 3.2.3 on 2021-06-05 15:11
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('cookbook', '0125_space_demo'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='userpreference',
|
||||
name='theme',
|
||||
field=models.CharField(choices=[('TANDOOR', 'Tandoor'), ('BOOTSTRAP', 'Bootstrap'), ('DARKLY', 'Darkly'), ('FLATLY', 'Flatly'), ('SUPERHERO', 'Superhero')], default='TANDOOR', max_length=128),
|
||||
),
|
||||
]
|
||||
17
cookbook/migrations/0127_remove_invitelink_username.py
Normal file
17
cookbook/migrations/0127_remove_invitelink_username.py
Normal file
@@ -0,0 +1,17 @@
|
||||
# Generated by Django 3.2.3 on 2021-06-07 14:21
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('cookbook', '0126_alter_userpreference_theme'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='invitelink',
|
||||
name='username',
|
||||
),
|
||||
]
|
||||
@@ -1,3 +1,4 @@
|
||||
import operator
|
||||
import re
|
||||
import uuid
|
||||
from datetime import date, timedelta
|
||||
@@ -9,6 +10,7 @@ from django.core.validators import MinLengthValidator
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext as _
|
||||
from django_prometheus.models import ExportModelOperationsMixin
|
||||
from django_scopes import ScopedManager
|
||||
|
||||
from recipes.settings import (COMMENT_PREF_DEFAULT, FRACTION_PREF_DEFAULT,
|
||||
@@ -52,18 +54,21 @@ class PermissionModelMixin:
|
||||
|
||||
def get_space(self):
|
||||
p = '.'.join(self.get_space_key())
|
||||
if getattr(self, p, None):
|
||||
return getattr(self, p)
|
||||
raise NotImplementedError('get space for method not implemented and standard fields not available')
|
||||
try:
|
||||
if space := operator.attrgetter(p)(self):
|
||||
return space
|
||||
except AttributeError:
|
||||
raise NotImplementedError('get space for method not implemented and standard fields not available')
|
||||
|
||||
|
||||
class Space(models.Model):
|
||||
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)
|
||||
message = models.CharField(max_length=512, default='', blank=True)
|
||||
max_recipes = models.IntegerField(default=0)
|
||||
allow_files = models.BooleanField(default=True)
|
||||
max_users = models.IntegerField(default=0)
|
||||
demo = models.BooleanField(default=False)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
@@ -78,11 +83,11 @@ class UserPreference(models.Model, PermissionModelMixin):
|
||||
TANDOOR = 'TANDOOR'
|
||||
|
||||
THEMES = (
|
||||
(TANDOOR, 'Tandoor'),
|
||||
(BOOTSTRAP, 'Bootstrap'),
|
||||
(DARKLY, 'Darkly'),
|
||||
(FLATLY, 'Flatly'),
|
||||
(SUPERHERO, 'Superhero'),
|
||||
(TANDOOR, 'Tandoor')
|
||||
)
|
||||
|
||||
# Nav colors
|
||||
@@ -124,7 +129,7 @@ class UserPreference(models.Model, PermissionModelMixin):
|
||||
SEARCH_STYLE = ((SMALL, _('Small')), (LARGE, _('Large')), (NEW, _('New')))
|
||||
|
||||
user = AutoOneToOneField(User, on_delete=models.CASCADE, primary_key=True)
|
||||
theme = models.CharField(choices=THEMES, max_length=128, default=FLATLY)
|
||||
theme = models.CharField(choices=THEMES, max_length=128, default=TANDOOR)
|
||||
nav_color = models.CharField(
|
||||
choices=COLORS, max_length=128, default=PRIMARY
|
||||
)
|
||||
@@ -247,7 +252,7 @@ class SyncLog(models.Model, PermissionModelMixin):
|
||||
return f"{self.created_at}:{self.sync} - {self.status}"
|
||||
|
||||
|
||||
class Keyword(models.Model, PermissionModelMixin):
|
||||
class Keyword(ExportModelOperationsMixin('keyword'), models.Model, PermissionModelMixin):
|
||||
name = models.CharField(max_length=64)
|
||||
icon = models.CharField(max_length=16, blank=True, null=True)
|
||||
description = models.TextField(default="", blank=True)
|
||||
@@ -267,7 +272,7 @@ class Keyword(models.Model, PermissionModelMixin):
|
||||
unique_together = (('space', 'name'),)
|
||||
|
||||
|
||||
class Unit(models.Model, PermissionModelMixin):
|
||||
class Unit(ExportModelOperationsMixin('unit'), models.Model, PermissionModelMixin):
|
||||
name = models.CharField(max_length=128, validators=[MinLengthValidator(1)])
|
||||
description = models.TextField(blank=True, null=True)
|
||||
|
||||
@@ -281,7 +286,7 @@ class Unit(models.Model, PermissionModelMixin):
|
||||
unique_together = (('space', 'name'),)
|
||||
|
||||
|
||||
class Food(models.Model, PermissionModelMixin):
|
||||
class Food(ExportModelOperationsMixin('food'), models.Model, PermissionModelMixin):
|
||||
name = models.CharField(max_length=128, validators=[MinLengthValidator(1)])
|
||||
recipe = models.ForeignKey('Recipe', null=True, blank=True, on_delete=models.SET_NULL)
|
||||
supermarket_category = models.ForeignKey(SupermarketCategory, null=True, blank=True, on_delete=models.SET_NULL)
|
||||
@@ -298,7 +303,7 @@ class Food(models.Model, PermissionModelMixin):
|
||||
unique_together = (('space', 'name'),)
|
||||
|
||||
|
||||
class Ingredient(models.Model, PermissionModelMixin):
|
||||
class Ingredient(ExportModelOperationsMixin('ingredient'), models.Model, PermissionModelMixin):
|
||||
food = models.ForeignKey(Food, on_delete=models.PROTECT, null=True, blank=True)
|
||||
unit = models.ForeignKey(Unit, on_delete=models.PROTECT, null=True, blank=True)
|
||||
amount = models.DecimalField(default=0, decimal_places=16, max_digits=32)
|
||||
@@ -323,7 +328,7 @@ class Ingredient(models.Model, PermissionModelMixin):
|
||||
ordering = ['order', 'pk']
|
||||
|
||||
|
||||
class Step(models.Model, PermissionModelMixin):
|
||||
class Step(ExportModelOperationsMixin('step'), models.Model, PermissionModelMixin):
|
||||
TEXT = 'TEXT'
|
||||
TIME = 'TIME'
|
||||
|
||||
@@ -380,7 +385,7 @@ class NutritionInformation(models.Model, PermissionModelMixin):
|
||||
return 'Nutrition'
|
||||
|
||||
|
||||
class Recipe(models.Model, PermissionModelMixin):
|
||||
class Recipe(ExportModelOperationsMixin('recipe'), models.Model, PermissionModelMixin):
|
||||
name = models.CharField(max_length=128)
|
||||
description = models.CharField(max_length=512, blank=True, null=True)
|
||||
servings = models.IntegerField(default=1)
|
||||
@@ -412,7 +417,7 @@ class Recipe(models.Model, PermissionModelMixin):
|
||||
return self.name
|
||||
|
||||
|
||||
class Comment(models.Model, PermissionModelMixin):
|
||||
class Comment(ExportModelOperationsMixin('comment'), models.Model, PermissionModelMixin):
|
||||
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE)
|
||||
text = models.TextField()
|
||||
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
@@ -446,7 +451,7 @@ class RecipeImport(models.Model, PermissionModelMixin):
|
||||
return self.name
|
||||
|
||||
|
||||
class RecipeBook(models.Model, PermissionModelMixin):
|
||||
class RecipeBook(ExportModelOperationsMixin('book'), models.Model, PermissionModelMixin):
|
||||
name = models.CharField(max_length=128)
|
||||
description = models.TextField(blank=True)
|
||||
icon = models.CharField(max_length=16, blank=True, null=True)
|
||||
@@ -460,7 +465,7 @@ class RecipeBook(models.Model, PermissionModelMixin):
|
||||
return self.name
|
||||
|
||||
|
||||
class RecipeBookEntry(models.Model, PermissionModelMixin):
|
||||
class RecipeBookEntry(ExportModelOperationsMixin('book_entry'), models.Model, PermissionModelMixin):
|
||||
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE)
|
||||
book = models.ForeignKey(RecipeBook, on_delete=models.CASCADE)
|
||||
|
||||
@@ -495,7 +500,7 @@ class MealType(models.Model, PermissionModelMixin):
|
||||
return self.name
|
||||
|
||||
|
||||
class MealPlan(models.Model, PermissionModelMixin):
|
||||
class MealPlan(ExportModelOperationsMixin('meal_plan'), models.Model, PermissionModelMixin):
|
||||
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE, blank=True, null=True)
|
||||
servings = models.DecimalField(default=1, max_digits=8, decimal_places=4)
|
||||
title = models.CharField(max_length=64, blank=True, default='')
|
||||
@@ -520,7 +525,7 @@ class MealPlan(models.Model, PermissionModelMixin):
|
||||
return f'{self.get_label()} - {self.date} - {self.meal_type.name}'
|
||||
|
||||
|
||||
class ShoppingListRecipe(models.Model, PermissionModelMixin):
|
||||
class ShoppingListRecipe(ExportModelOperationsMixin('shopping_list_recipe'), models.Model, PermissionModelMixin):
|
||||
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE, null=True, blank=True)
|
||||
servings = models.DecimalField(default=1, max_digits=8, decimal_places=4)
|
||||
|
||||
@@ -543,7 +548,7 @@ class ShoppingListRecipe(models.Model, PermissionModelMixin):
|
||||
return None
|
||||
|
||||
|
||||
class ShoppingListEntry(models.Model, PermissionModelMixin):
|
||||
class ShoppingListEntry(ExportModelOperationsMixin('shopping_list_entry'), models.Model, PermissionModelMixin):
|
||||
list_recipe = models.ForeignKey(ShoppingListRecipe, on_delete=models.CASCADE, null=True, blank=True)
|
||||
food = models.ForeignKey(Food, on_delete=models.CASCADE)
|
||||
unit = models.ForeignKey(Unit, on_delete=models.CASCADE, null=True, blank=True)
|
||||
@@ -573,7 +578,7 @@ class ShoppingListEntry(models.Model, PermissionModelMixin):
|
||||
return None
|
||||
|
||||
|
||||
class ShoppingList(models.Model, PermissionModelMixin):
|
||||
class ShoppingList(ExportModelOperationsMixin('shopping_list'), models.Model, PermissionModelMixin):
|
||||
uuid = models.UUIDField(default=uuid.uuid4)
|
||||
note = models.TextField(blank=True, null=True)
|
||||
recipes = models.ManyToManyField(ShoppingListRecipe, blank=True)
|
||||
@@ -591,7 +596,7 @@ class ShoppingList(models.Model, PermissionModelMixin):
|
||||
return f'Shopping list {self.id}'
|
||||
|
||||
|
||||
class ShareLink(models.Model, PermissionModelMixin):
|
||||
class ShareLink(ExportModelOperationsMixin('share_link'), models.Model, PermissionModelMixin):
|
||||
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE)
|
||||
uuid = models.UUIDField(default=uuid.uuid4)
|
||||
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
@@ -608,9 +613,8 @@ def default_valid_until():
|
||||
return date.today() + timedelta(days=14)
|
||||
|
||||
|
||||
class InviteLink(models.Model, PermissionModelMixin):
|
||||
class InviteLink(ExportModelOperationsMixin('invite_link'), models.Model, PermissionModelMixin):
|
||||
uuid = models.UUIDField(default=uuid.uuid4)
|
||||
username = models.CharField(blank=True, max_length=64)
|
||||
email = models.EmailField(blank=True)
|
||||
group = models.ForeignKey(Group, on_delete=models.CASCADE)
|
||||
valid_until = models.DateField(default=default_valid_until)
|
||||
@@ -641,7 +645,7 @@ class TelegramBot(models.Model, PermissionModelMixin):
|
||||
return f"{self.name}"
|
||||
|
||||
|
||||
class CookLog(models.Model, PermissionModelMixin):
|
||||
class CookLog(ExportModelOperationsMixin('cook_log'), models.Model, PermissionModelMixin):
|
||||
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE)
|
||||
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
created_at = models.DateTimeField(default=timezone.now)
|
||||
@@ -655,7 +659,7 @@ class CookLog(models.Model, PermissionModelMixin):
|
||||
return self.recipe.name
|
||||
|
||||
|
||||
class ViewLog(models.Model, PermissionModelMixin):
|
||||
class ViewLog(ExportModelOperationsMixin('view_log'), models.Model, PermissionModelMixin):
|
||||
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE)
|
||||
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
@@ -682,7 +686,7 @@ class ImportLog(models.Model, PermissionModelMixin):
|
||||
return f"{self.created_at}:{self.type}"
|
||||
|
||||
|
||||
class BookmarkletImport(models.Model, PermissionModelMixin):
|
||||
class BookmarkletImport(ExportModelOperationsMixin('bookmarklet_import'), models.Model, PermissionModelMixin):
|
||||
html = models.TextField()
|
||||
url = models.CharField(max_length=256, null=True, blank=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
BIN
cookbook/static/assets/brand_logo.png
Normal file
BIN
cookbook/static/assets/brand_logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
115
cookbook/static/assets/spinner.svg
Normal file
115
cookbook/static/assets/spinner.svg
Normal file
@@ -0,0 +1,115 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="2000px"
|
||||
height="2000px"
|
||||
viewBox="0 0 2000 2000"
|
||||
version="1.1"
|
||||
id="SVGRoot"
|
||||
sodipodi:docname="spinner.svg"
|
||||
inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)">
|
||||
<defs
|
||||
id="defs265" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.24748737"
|
||||
inkscape:cx="507.59315"
|
||||
inkscape:cy="671.7335"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
inkscape:document-rotation="0"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1017"
|
||||
inkscape:window-x="1912"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata268">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Ebene 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<path
|
||||
id="path5640-9"
|
||||
d="m 1363.6895,468.03149 c -6.2646,0 -11.3386,5.07398 -11.3386,11.33858 0,6.25981 5.074,11.33859 11.3386,11.33859 6.2645,0 11.3386,-5.07878 11.3386,-11.33859 0,-6.2646 -5.0741,-11.33858 -11.3386,-11.33858 z m 63.425,-26.44248 42.4018,-162.5339 h -219.2126 c -12.5054,0 -22.6772,10.17173 -22.6772,22.67732 v 75.5904 c 0,12.50559 10.1718,22.67716 22.6772,22.67716 h 48.3118 l 3.5576,40.89453 c -17.0411,6.52913 -29.1923,22.91807 -29.1923,42.25508 v 22.67717 c 0,8.34795 6.7703,15.11811 15.1181,15.11811 H 1439.28 c 8.3483,0 15.1181,-6.77016 15.1181,-15.11811 V 483.1496 c 0,-18.61414 -11.2391,-34.5733 -27.2836,-41.56059 z m -176.8108,-56.70713 c -4.1621,0 -7.5591,-3.3874 -7.5591,-7.55905 v -75.5904 c 0,-4.17165 3.397,-7.55905 7.5591,-7.55905 h 39.1086 l 7.8898,90.7085 z m 199.644,-90.7085 -7.8897,30.23606 h -74.589 c -2.0882,0 -3.7795,1.69138 -3.7795,3.77968 v 7.55891 c 0,2.0883 1.6913,3.77953 3.7795,3.77953 h 70.6488 l -7.8897,30.23622 h -62.7591 c -2.0882,0 -3.7795,1.69137 -3.7795,3.77952 v 7.55906 c 0,2.0883 1.6913,3.77952 3.7795,3.77952 h 58.8144 l -13.8048,52.91339 h -95.4001 L 1304.5871,294.17338 Z M 1439.28,505.82677 H 1288.0989 V 483.1496 c 0,-16.67244 13.564,-30.23622 30.2362,-30.23622 h 90.7087 c 16.6726,0 30.2362,13.56378 30.2362,30.23622 z"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#303030;fill-opacity:1;stroke-width:0.472441" />
|
||||
<path
|
||||
id="path5684-6-0"
|
||||
d="m 1740.0225,645.7638 h -53.5609 l 50.4238,-50.42358 c 11.3904,-11.39059 8.2723,-30.59063 -6.1322,-37.79536 a 23.601733,23.601733 0 0 0 -10.5495,-2.48976 c -5.0177,0 -9.9972,1.59693 -14.1641,4.71969 l -114.652,85.98901 h -85.6959 c -2.0881,0 -3.7795,1.6913 -3.7795,3.77957 v 7.55905 c 0,2.08815 1.6914,3.77945 3.7795,3.77945 h 11.3386 V 676 c 0,38.4661 23.9811,71.2583 57.7746,84.4488 -6.0283,7.9276 -10.2519,17.3056 -11.7732,27.6095 -0.6848,4.6441 2.8252,8.8866 7.5213,8.8866 h 104.6078 c 4.6961,0 8.2065,-4.2378 7.5213,-8.8866 -1.5163,-10.3039 -5.74,-19.6866 -11.7728,-27.6095 33.7935,-13.1905 57.7746,-45.9827 57.7746,-84.4488 v -15.11813 h 11.3386 c 2.0881,0 3.7795,-1.6913 3.7795,-3.77945 v -7.55905 c 0,-2.08827 -1.6914,-3.77957 -3.7795,-3.77957 z m -24.9166,-73.89453 c 8.3055,-6.22673 18.5246,5.34331 11.088,12.77961 l -61.11,61.11492 h -48.5008 z M 1713.5658,676 c 0,31.3276 -18.9026,58.9464 -48.1512,70.3654 l -18.6664,7.285 12.1323,15.9495 c 2.8729,3.7797 5.1357,7.9135 6.7042,12.2269 h -85.4506 c 1.5685,-4.3134 3.8267,-8.4472 6.7041,-12.2269 l 12.1323,-15.9495 -18.6663,-7.285 C 1551.0506,734.9464 1532.1484,707.3276 1532.1484,676 v -15.11813 h 181.4174 z"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#303030;fill-opacity:1;stroke-width:0.472441" />
|
||||
<path
|
||||
id="path5695-2"
|
||||
d="M 1075.5907,1636.198 H 924.40969 a 30.236221,30.236221 0 0 0 -30.2366,30.2361 v 196.5355 a 15.118111,15.118111 0 0 0 15.11811,15.1181 h 181.4177 a 15.118111,15.118111 0 0 0 15.118,-15.1181 v -196.5355 a 30.236221,30.236221 0 0 0 -30.2362,-30.2361 z m 15.1181,226.7716 H 909.2912 v -151.1811 h 181.4176 z m 0,-166.2992 H 909.2912 v -30.2363 a 15.118111,15.118111 0 0 1 15.11849,-15.118 h 151.18101 a 15.118111,15.118111 0 0 1 15.1181,15.118 z m -22.6775,-30.2363 a 7.5590556,7.5590556 0 1 0 7.5594,7.5591 7.5590556,7.5590556 0 0 0 -7.5594,-7.5591 z m -30.2359,0 a 7.5590556,7.5590556 0 1 0 7.5591,7.5591 7.5590556,7.5590556 0 0 0 -7.5591,-7.5591 z m -75.59041,0 a 7.5590556,7.5590556 0 1 0 7.5587,7.5591 7.5590556,7.5590556 0 0 0 -7.5587,-7.5591 z m -30.23625,0 a 7.5590556,7.5590556 0 1 0 7.55906,7.5591 7.5590556,7.5590556 0 0 0 -7.55906,-7.5591 z m 0,181.4175 h 136.06256 a 7.5590556,7.5590556 0 0 0 7.5594,-7.5591 v -105.8268 a 7.5590556,7.5590556 0 0 0 -7.5594,-7.559 H 931.96874 a 7.5590556,7.5590556 0 0 0 -7.55905,7.559 v 105.8268 a 7.5590556,7.5590556 0 0 0 7.55905,7.5591 z m 7.55906,-105.8268 h 120.9444 v 90.7086 H 939.5278 Z m 98.2676,15.118 h -75.59041 a 7.5591,7.5591 0 0 0 0,15.1182 h 75.59041 a 7.5591,7.5591 0 0 0 0,-15.1182 z"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#303030;fill-opacity:1;stroke-width:0.472441" />
|
||||
<path
|
||||
id="path5673-8-4-5"
|
||||
d="m 1467.3153,1614.6952 c -6.378,-5.3858 -14.4567,-8.315 -22.7717,-8.315 -10.3464,0 -20.0787,4.4881 -26.8819,12.3307 l -9.6378,-42.0473 c -7.4173,-32.5984 -35.7165,-55.3228 -68.7874,-55.3228 -5.3386,0 -10.7244,0.6139 -15.9685,1.8425 -37.9842,8.8347 -61.748,47.2442 -53.0078,85.5592 l 21.0708,92.126 c 0.189,0.8974 0.5197,1.7479 0.7559,2.6456 -4.4409,1.4646 -8.5039,5.5748 -8.5039,10.5827 v 37.7952 c 0,6.2835 5.0551,11.3386 11.2441,11.3386 h 143.1968 c 6.189,0 11.9055,-5.0551 11.9055,-11.3386 v -37.7952 c 0,-5.3858 -4.4409,-9.6378 -9.4015,-10.8189 l 31.37,-37.9843 c 12.5671,-15.2126 10.4884,-37.8897 -4.5826,-50.5984 z m -32.5512,133.4173 h -136.063 v -30.2362 h 136.063 z m 25.6063,-92.5039 -38.9764,47.1497 h -113.7638 c -0.5669,-1.7482 -1.2756,-3.4962 -1.7008,-5.3388 l -21.0708,-92.1259 c -6.8976,-30.1889 11.811,-60.4253 41.7637,-67.4174 4.1575,-0.9453 8.3622,-1.4645 12.567,-1.4645 26.0787,0 48.3779,17.9055 54.2362,43.6064 l 12.9921,56.8818 3.4016,14.8347 9.685,-11.7166 9.1654,-11.1023 c 3.9213,-4.7718 9.685,-7.4646 15.8268,-7.4646 4.8189,0 9.4488,1.7007 13.1811,4.8189 8.7874,7.37 9.9685,20.504 2.6929,29.3386 z"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#303030;fill-opacity:1;stroke-width:0.472441" />
|
||||
<path
|
||||
id="path5706-9"
|
||||
d="m 455.99659,983.47136 c -22.56427,-58.66282 -79.4948,-97.74958 -143.13917,-97.74958 -63.64457,0 -120.58155,39.08676 -143.12757,97.74958 -25.31405,0.97615 -28.28993,22.26634 -28.28993,27.32554 a 27.43272,27.43272 0 0 0 27.50999,27.2958 c 1.61917,0 2.83909,-0.3566 4.35685,-0.4524 l 19.32015,57.9249 a 27.379152,27.379152 0 0 0 25.96867,18.713 h 188.52348 a 27.379152,27.379152 0 0 0 25.96829,-18.719 l 19.30226,-57.9189 c 1.51209,0.1014 2.73806,0.4524 4.35693,0.4524 a 27.43272,27.43272 0 0 0 27.5278,-27.2958 c 0,-5.22 -3.06519,-26.272 -28.27775,-27.32554 z m -40.97343,106.05254 a 8.3327858,8.3327858 0 0 1 -7.9041,5.7079 H 218.5895 a 8.3327858,8.3327858 0 0 1 -7.90412,-5.702 l -18.60593,-55.8237 a 75.631939,75.631939 0 0 0 17.37958,-9.9338 c 5.41071,-4.0116 7.49951,-5.6128 15.12436,0.039 7.21381,5.3568 19.32588,14.2849 40.4377,14.2849 21.11197,0 33.23583,-8.9638 40.47356,-14.3325 5.39857,-3.9937 7.44592,-5.5948 15.00504,0.028 7.23173,5.3568 19.32022,14.2848 40.43164,14.2848 21.11189,0 33.21824,-8.9637 40.43777,-14.3205 6.47601,-4.8032 8.40441,-4.8807 14.95147,0 a 75.536702,75.536702 0 0 0 17.30819,9.9339 l -18.6056,55.8297 z m 41.73547,-70.4775 c -14.8447,0 -22.73066,-5.8568 -29.11133,-10.6005 -17.6834,-13.09438 -29.79589,-5.7675 -37.62855,0.039 -6.33277,4.708 -14.22515,10.5648 -29.08742,10.5648 -14.86181,0 -22.74814,-5.8568 -29.12872,-10.5768 -18.93914,-14.09428 -31.84332,-4.3152 -37.67614,0 -6.34458,4.72 -14.23157,10.5768 -29.11144,10.5768 -14.87973,0 -22.76637,-5.8568 -29.16451,-10.5768 -4.03546,-2.976 -17.68349,-14.87988 -37.77753,0 -6.35673,4.7021 -14.28477,10.5589 -29.16452,10.5589 a 8.2554502,8.2554502 0 1 1 0,-16.5109 c 3.20802,0 6.59458,0.2861 15.92147,-4.86268 31.74184,-91.61298 118.33737,-92.88672 128.02748,-92.88672 9.52324,0 96.25581,1.19038 127.96763,92.85092 9.37404,5.30318 12.24317,4.94608 15.93934,4.94608 a 8.25545,8.25545 0 1 1 0,16.5109 z m -196.779,-84.70277 a 9.5648466,9.5648466 0 0 0 -12.7788,4.26161 l -9.5233,19.04637 a 9.5231832,9.5231832 0 0 0 4.26186,12.77891 c 5.6782,2.7975 10.92787,-0.59555 12.77845,-4.26159 l 9.5233,-19.04632 a 9.5231832,9.5231832 0 0 0 -4.26151,-12.77898 z m 118.52804,4.25571 a 9.5252893,9.5252893 0 1 0 -17.04022,8.51727 l 9.52286,19.04637 c 1.84526,3.67236 7.10105,7.05909 12.77882,4.26159 a 9.5231832,9.5231832 0 0 0 4.2615,-12.77891 z m -65.65025,-5.26154 a 9.5231832,9.5231832 0 0 0 -9.52334,9.5232 v 19.04625 a 9.5233032,9.5233032 0 1 0 19.04658,0 V 942.861 a 9.5231832,9.5231832 0 0 0 -9.52324,-9.5232 z"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#303030;fill-opacity:1;stroke-width:0.595201" />
|
||||
<path
|
||||
id="path5662-5"
|
||||
d="m 1075.5909,154.17314 a 44.962217,44.962217 0 0 0 -27.2742,9.3261 C 1037.2899,148.7401 1019.8425,139.0551 1000,139.0551 c -19.84243,0 -37.28983,9.685 -48.31663,24.44414 a 45.146469,45.146469 0 0 0 -72.62811,36.02827 c 0,19.71972 30.23621,105.82684 30.23621,105.82684 v 60.47252 a 15.118115,15.118115 0 0 0 15.1181,15.11803 h 151.18133 a 15.118115,15.118115 0 0 0 15.1181,-15.11803 v -60.47252 c 0,0 30.2362,-86.10712 30.2362,-105.82684 a 45.354343,45.354343 0 0 0 -45.3543,-45.35437 z M 931.96867,358.26781 v -37.79527 h 136.06323 v 37.79527 z M 1069.3264,297.7953 h -13.9609 l 5.1073,-63.66146 a 3.7795287,3.7795287 0 0 0 -3.4628,-4.0676 l -7.5591,-0.60506 h -0.3061 a 3.7795287,3.7795287 0 0 0 -3.7795,3.48185 l -5.1969,64.86621 h -32.92 V 233.5431 a 3.7795287,3.7795287 0 0 0 -3.7796,-3.77952 h -7.55897 a 3.7795287,3.7795287 0 0 0 -3.77986,3.77952 v 64.25193 h -32.324 l -5.1968,-64.86606 a 3.7795287,3.7795287 0 0 0 -3.7799,-3.48201 h -0.3062 l -7.5587,0.60507 a 3.7795287,3.7795287 0 0 0 -3.4631,4.06764 l 5.1352,63.67563 h -13.9604 c -13.8237,-39.3165 -28.72444,-88.18587 -28.94176,-98.26779 a 22.474022,22.474022 0 0 1 36.24566,-17.95272 l 18.1984,13.80476 13.6724,-18.2976 c 7.285,-9.75598 18.274,-15.34968 30.15113,-15.34968 11.8771,0 22.8661,5.5937 30.1511,15.34019 l 13.6729,18.29764 18.1984,-13.79531 a 22.474022,22.474022 0 0 1 36.2456,17.915 c -0.2192,10.11964 -15.1181,58.98901 -28.9417,98.30551 z"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#303030;fill-opacity:1;stroke-width:0.472441" />
|
||||
<path
|
||||
id="path5739"
|
||||
d="m 689.18112,501.24409 6.7087,-77.85831 c -19.88984,-15.63783 -33.25984,-36.37803 -33.25984,-61.18099 0.0476,-44.55122 41.66925,-83.14968 71.85815,-83.14968 10.44099,0 18.89764,8.1733 18.89764,18.18894 v 205.51174 c 0,10.01578 -8.45665,18.18909 -18.89764,18.18909 h -26.45661 c -10.77162,0 -19.74803,-8.8348 -18.8504,-19.70079 z m -11.38586,-139.0393 c 0,24.09438 15.21263,40.39355 33.92126,54.09438 l -7.46453,86.26772 c -0.13644,1.74799 1.60619,3.25984 3.77953,3.25984 h 26.45661 c 2.03158,0 3.77953,-1.41778 3.77953,-3.07094 V 297.24405 c 0,-1.65347 -1.74795,-3.0708 -3.77953,-3.0708 -21.07082,0 -56.69287,31.08651 -56.69287,68.03154 z m -37.46457,-67.18118 c -1.55913,-9.21252 -10.01567,-15.9685 -21.77945,-15.9685 -5.62205,0 -11.38587,1.60637 -15.7324,5.29137 -3.96839,-3.35433 -9.44878,-5.29137 -15.73221,-5.29137 -6.28354,0 -11.76382,1.93704 -15.73236,5.29137 -4.34634,-3.685 -10.1102,-5.29137 -15.73221,-5.29137 -11.90555,0 -20.22047,6.85047 -21.7796,15.9685 -0.85096,4.44087 -7.22823,39.96854 -7.22823,54.85039 0,24.04714 12.61413,43.27568 33.54323,52.29918 l -5.38579,99.59059 c -0.56689,10.39359 7.70071,19.18111 18.1417,19.18111 h 28.34641 c 10.39386,0 18.70874,-8.74027 18.14181,-19.18111 l -5.3859,-99.59059 c 20.88197,-9.0235 33.54342,-28.25204 33.54342,-52.29918 0,-14.88185 -6.3781,-50.40952 -7.22842,-54.85039 z m -41.76382,97.41733 5.76386,110.12595 c 0.0915,1.74799 -1.27499,3.25984 -3.07095,3.25984 h -28.34641 c -1.748,0 -3.16532,-1.46491 -3.07083,-3.25984 l 5.76374,-110.12595 c -20.03146,-4.15748 -33.8739,-20.31504 -33.8739,-42.56694 0,-14.03145 6.99208,-52.25204 6.99208,-52.25204 0.75523,-4.67713 13.37001,-4.58264 13.93694,0.0911 V 355.587 c 0.42561,5.433 13.32294,5.52752 13.937,0.0911 l 3.49614,-58.06295 c 0.75523,-4.58264 13.18111,-4.58264 13.93694,0 l 3.49613,58.06295 c 0.61395,5.3859 13.51182,5.29138 13.93701,-0.0911 v -57.82681 c 0.56689,-4.67716 13.18107,-4.77154 13.93693,-0.0911 0,0 6.9921,38.22055 6.9921,52.25205 0.0476,22.11023 -13.70079,38.36217 -33.82678,42.51961 z"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#303030;fill-opacity:1;stroke-width:0.472441" />
|
||||
<path
|
||||
id="path5772-1"
|
||||
d="m 1751.068,1034.0124 c -6.2598,0 -11.3384,5.0739 -11.3384,11.3384 0,6.2597 5.0786,11.3383 11.3384,11.3383 6.2597,0 11.3383,-5.0786 11.3383,-11.3383 0,-6.2645 -5.0786,-11.3384 -11.3383,-11.3384 z m -79.3686,-64.25069 c 0,-6.26453 -5.0786,-11.3384 -11.3384,-11.3384 -6.2597,0 -11.3383,5.07387 -11.3383,11.3384 0,6.25969 5.0786,11.33828 11.3383,11.33828 6.2598,0 11.3384,-5.07859 11.3384,-11.33828 z m 3.7795,64.25069 c -6.2598,0 -11.3383,5.0739 -11.3383,11.3384 0,6.2597 5.0785,11.3383 11.3383,11.3383 6.2596,0 11.3384,-5.0786 11.3384,-11.3383 0,-6.2645 -5.0788,-11.3384 -11.3384,-11.3384 z m 45.3534,-45.3535 c -6.2597,0 -11.3384,5.07386 -11.3384,11.33839 0,6.25971 5.0787,11.33841 11.3384,11.33841 6.2597,0 11.3384,-5.0787 11.3384,-11.33841 0,-6.26453 -5.0787,-11.33839 -11.3384,-11.33839 z m 105.1256,11.25331 c -33.0562,-0.40123 -59.7485,-27.25459 -59.7485,-60.40509 -33.1505,0 -59.9989,-26.68762 -60.4051,-59.73902 -3.1275,-0.47698 -6.2786,-0.71299 -9.4156,-0.71299 -9.7699,0 -19.4547,2.29133 -28.2467,6.76995 l -32.6592,16.6391 a 62.465402,62.465402 0 0 0 -27.3019,27.3112 l -16.5824,32.5411 a 62.665709,62.665709 0 0 0 -6.0564,38.24344 l 5.7069,36.0371 a 62.623193,62.623193 0 0 0 17.5556,34.4685 l 25.875,25.8704 a 62.378004,62.378004 0 0 0 34.3506,17.5084 l 36.2402,5.7354 c 3.2409,0.5155 6.5007,0.7652 9.751,0.7652 9.7888,0 19.4879,-2.3056 28.2939,-6.7937 l 32.6592,-16.639 a 62.465402,62.465402 0 0 0 27.3018,-27.3112 l 16.5825,-32.5412 c 5.9337,-11.636 8.036,-24.8357 6.0991,-37.74735 z m -19.5681,30.87819 -16.5825,32.5458 c -4.573,8.9762 -11.7304,16.1335 -20.6971,20.702 l -32.6593,16.6391 c -6.6046,3.3636 -14.0171,5.1446 -21.4295,5.1446 -2.4614,0 -4.9464,-0.1935 -7.3841,-0.5813 l -36.2356,-5.7353 c -9.921,-1.5731 -18.9208,-6.1605 -26.0262,-13.2658 l -25.8751,-25.8704 c -7.1337,-7.129 -11.7352,-16.1713 -13.3131,-26.1397 l -5.707,-36.03228 c -1.5826,-9.97784 0.015,-20.01219 4.5921,-29.01204 l 16.5823,-32.54109 c 4.5731,-8.97622 11.7305,-16.13351 20.6973,-20.70194 l 32.6544,-16.64383 c 5.3527,-2.72591 11.2439,-4.40784 17.2485,-4.94162 6.2125,29.83412 30.1601,53.36111 60.1169,58.98788 5.6266,29.96157 29.1538,53.90912 58.9926,60.11682 -0.5193,6.0141 -2.1921,11.8769 -4.9746,17.3288 z"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#303030;fill-opacity:1;stroke-width:0.472433" />
|
||||
<path
|
||||
id="path5627-3"
|
||||
d="m 1556.6704,1294.1237 c -8.3858,0 -15.1181,-6.7323 -15.1181,-15.118 0,-14.6458 15.1181,-10.8662 15.1181,-30.2363 5.6693,0 15.1181,13.9371 15.1181,26.4567 0,12.5197 -6.7323,18.8976 -15.1181,18.8976 z m 60.4724,0 c -8.3858,0 -15.118,-6.7323 -15.118,-15.118 0,-14.6458 15.118,-10.8662 15.118,-30.2363 5.6693,0 15.1182,13.9371 15.1182,26.4567 0,12.5197 -6.7323,18.8976 -15.1182,18.8976 z m 60.4725,0 c -8.3858,0 -15.1181,-6.7323 -15.1181,-15.118 0,-14.6458 15.1181,-10.8662 15.1181,-30.2363 5.6693,0 15.1181,13.9371 15.1181,26.4567 0,12.5197 -6.7323,18.8976 -15.1181,18.8976 z m 22.6771,75.5907 h -15.118 v -68.0316 h -15.1182 v 68.0316 h -45.3543 v -68.0316 h -15.1181 v 68.0316 h -45.3543 v -68.0316 h -15.1181 v 68.0316 h -15.1181 c -12.5197,0 -22.6772,10.1573 -22.6772,22.6771 v 98.2677 h 211.6535 v -98.2677 c 0,-12.5198 -10.1575,-22.6771 -22.6772,-22.6771 z m 7.5591,105.8267 h -181.4173 v -34.036 c 7.6639,-4.4783 11.3045,-11.3183 20.1968,-11.3183 13.2052,0 14.7652,15.118 35.315,15.118 20.2408,0 22.3072,-15.118 35.1968,-15.118 13.2983,0 14.7407,15.118 35.315,15.118 20.4837,0 22.0947,-15.118 35.315,-15.118 8.7345,0 12.3992,6.8386 20.0787,11.3173 z m 0,-53.0901 c -4.5476,-3.72 -10.0238,-7.3823 -20.0787,-7.3823 -20.5181,0 -22.1221,15.1181 -35.315,15.1181 -13.0842,0 -14.8602,-15.1181 -35.315,-15.1181 -20.2403,0 -22.3076,15.1181 -35.1968,15.1181 -13.2983,0 -14.7411,-15.1181 -35.315,-15.1181 -10.1399,0 -15.6382,3.6728 -20.1968,7.3974 v -30.0746 c 0,-4.1678 3.3912,-7.5591 7.5591,-7.5591 h 166.2991 c 4.168,0 7.5591,3.3913 7.5591,7.5591 z"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#303030;fill-opacity:1;stroke-width:0.472441" />
|
||||
<path
|
||||
id="path5794"
|
||||
d="m 455.82468,561.09456 c -59.4562,0 -111.33852,39.33117 -140.22863,73.01275 l -57.67284,-51.27053 c -8.03297,-6.61302 -19.97164,0.40092 -17.95038,10.50915 L 255.94899,676 239.96708,758.66025 c -2.01551,10.10208 9.91131,17.11599 17.92597,10.52702 l 57.67356,-51.25873 c 28.91344,33.64577 80.82557,72.9769 140.25807,72.9769 90.81482,0 164.43402,-91.92438 164.43402,-114.90544 0,-22.98105 -73.6192,-114.90544 -164.43402,-114.90544 z m 0,210.66009 c -50.77599,0 -98.29963,-34.27421 -125.83995,-66.35192 l -12.09918,-14.09391 c -18.54503,15.24893 -7.27783,5.31435 -54.74779,48.8408 12.93728,-68.15931 11.15984,-58.87111 12.2299,-64.14962 -1.07006,-5.27254 0.71277,4.02766 -12.22415,-64.14959 47.56512,43.59825 36.26847,33.63976 54.74778,48.84688 l 12.09918,-14.09398 c 27.54104,-32.07776 75.03488,-66.35793 125.83421,-66.35793 76.21859,0 140.366,75.69999 145.28359,95.75462 -4.91759,20.05463 -69.065,95.75465 -145.28359,95.75465 z m -12.94948,-149.82603 -6.72448,-6.76864 a 4.7565564,4.7877296 0 0 0 -6.73058,0 l -50.34816,50.66611 a 4.7565564,4.7877296 0 0 0 0,6.76863 l 6.72484,6.76862 a 4.7565564,4.7877296 0 0 0 6.72448,0 l 50.3539,-50.64218 a 4.7565564,4.7877296 0 0 0 0,-6.79254 z m 66.59166,9.57547 -6.72448,-6.7686 a 4.7565564,4.7877296 0 0 0 -6.73023,0 l -88.40107,88.9679 a 4.7565564,4.7877296 0 0 0 0,6.76865 l 6.7252,6.76861 a 4.7565564,4.7877296 0 0 0 6.72449,0 l 88.40609,-88.94403 a 4.7565564,4.7877296 0 0 0 0,-6.79253 z m 21.81517,41.10869 a 4.7565564,4.7877296 0 0 0 -6.73096,0 l -50.34798,50.66612 a 4.7565564,4.7877296 0 0 0 0,6.76863 l 6.72413,6.76861 a 4.7565564,4.7877296 0 0 0 6.7252,0 l 50.35336,-50.64219 a 4.7565564,4.7877296 0 0 0 0,-6.77461 z"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#303030;fill-opacity:1;stroke-width:0.596513" />
|
||||
<path
|
||||
id="path5728"
|
||||
d="m 385.71444,1248.7695 c -66.79359,0 -120.94515,54.1512 -120.94515,120.9449 0,66.7937 54.15156,120.9448 120.94515,120.9448 66.79355,0 120.94484,-54.1511 120.94484,-120.9448 0,-66.7937 -54.15129,-120.9449 -120.94484,-120.9449 z m 0,226.7716 c -58.35119,0 -105.82666,-47.4756 -105.82666,-105.8267 0,-58.3512 47.47547,-105.8268 105.82666,-105.8268 58.35107,0 105.82669,47.4756 105.82669,105.8268 0,58.3511 -47.47562,105.8267 -105.82669,105.8267 z m 74.82984,-105.8267 13.3606,-13.3607 c 1.47405,-1.474 1.47405,-3.8693 0,-5.3433 l -5.34324,-5.3433 c -1.47401,-1.474 -3.8694,-1.474 -5.34342,0 l -13.36059,13.3606 -21.3826,-21.3779 10.6913,-10.6913 8.01736,8.0173 c 1.47402,1.4739 3.86922,1.4739 5.34323,0 l 5.34342,-5.3433 c 1.47402,-1.4741 1.47402,-3.8693 0,-5.3433 l -8.01736,-8.0174 2.67394,-2.674 c 1.47402,-1.4739 1.47402,-3.8693 0,-5.3433 l -5.34323,-5.3434 c -1.47401,-1.4739 -3.86921,-1.4739 -5.34323,0 l -2.67413,2.6741 -8.01736,-8.0172 c -1.47402,-1.474 -3.86922,-1.474 -5.34323,0 l -5.34327,5.3431 c -1.47402,1.4741 -1.47402,3.8694 0,5.3434 l 8.01721,8.0173 -10.6913,10.6913 -21.3826,-21.3779 13.36524,-13.3606 c 1.47401,-1.474 1.47401,-3.8692 0,-5.3433 l -5.34788,-5.3622 c -1.47402,-1.474 -3.86941,-1.474 -5.34342,0 l -13.36528,13.3654 -13.36539,-13.3654 c -1.47402,-1.474 -3.86922,-1.474 -5.34324,0 l -5.34346,5.3433 c -1.47397,1.474 -1.47397,3.8692 0,5.3434 l 13.36544,13.3606 -21.37796,21.3779 -10.6913,-10.6914 8.01717,-8.0173 c 1.47402,-1.4739 1.47402,-3.8693 0,-5.3433 l -5.34323,-5.3433 c -1.47402,-1.474 -3.86922,-1.474 -5.34323,0 l -8.01736,8.0174 -2.67413,-2.6741 c -1.47402,-1.474 -3.86922,-1.474 -5.34323,0 l -5.34312,5.3433 c -1.47402,1.474 -1.47402,3.8693 0,5.3434 l 2.67364,2.6739 -8.01714,8.0173 c -1.47401,1.4741 -1.47401,3.8694 0,5.3433 l 5.3435,5.3434 c 1.47401,1.474 3.8691,1.474 5.34312,0 l 8.01736,-8.0174 10.6913,10.6914 -21.37814,21.378 -13.36517,-13.3607 c -1.47402,-1.4739 -3.86948,-1.4739 -5.3435,0 l -5.35748,5.3575 c -1.47401,1.4741 -1.47401,3.8693 0,5.3433 l 13.36555,13.3654 -13.36555,13.3653 c -1.47401,1.4741 -1.47401,3.8693 0,5.3433 l 5.3435,5.3434 c 1.47402,1.474 3.8691,1.474 5.34312,0 l 13.36554,-13.3606 21.37803,21.3778 -10.69149,10.6914 -8.01706,-8.0173 c -1.47401,-1.474 -3.86948,-1.474 -5.34349,0 l -5.3435,5.3432 c -1.47364,1.474 -1.47364,3.8694 0,5.3434 l 8.01751,8.0172 -2.67401,2.6741 c -1.47402,1.474 -1.47402,3.8694 0,5.3434 l 5.34349,5.3432 c 1.47402,1.4741 3.8691,1.4741 5.34312,0 l 2.67394,-2.6739 8.01736,8.0172 c 1.47402,1.474 3.86941,1.474 5.34342,0 l 5.34323,-5.3433 c 1.47402,-1.474 1.47402,-3.8692 0,-5.3432 l -8.01736,-8.0175 10.6913,-10.6912 21.3828,21.3827 -13.36544,13.3652 c -1.47401,1.474 -1.47401,3.8695 0,5.3433 l 5.34343,5.3433 c 1.47401,1.4741 3.86921,1.4741 5.34323,0 l 13.37488,-13.3606 13.36528,13.3653 c 1.47401,1.474 3.8694,1.474 5.34342,0 l 5.34323,-5.3433 c 1.47401,-1.4739 1.47401,-3.8692 0,-5.3432 l -13.36543,-13.3653 21.38279,-21.3828 10.6913,10.6913 -8.01736,8.0173 c -1.47402,1.474 -1.47402,3.8694 0,5.3434 l 5.34323,5.3433 c 1.47402,1.474 3.86941,1.474 5.34342,0 l 8.01736,-8.0173 2.67394,2.674 c 1.47402,1.474 3.86922,1.474 5.34323,0 l 5.34342,-5.3434 c 1.47406,-1.474 1.47406,-3.8692 0,-5.3432 l -2.67409,-2.6741 8.01736,-8.0172 c 1.47402,-1.4741 1.47402,-3.8694 0,-5.3434 l -5.34327,-5.3433 c -1.47401,-1.474 -3.8694,-1.474 -5.34342,0 l -8.01717,8.0174 -10.69149,-10.6914 21.37799,-21.378 13.3654,13.3606 c 1.47405,1.474 3.86925,1.474 5.34326,0 l 5.34343,-5.3432 c 1.47397,-1.474 1.47397,-3.8694 0,-5.3433 z m -74.82984,-53.452 21.3778,21.378 -21.3778,21.3827 -21.37795,-21.3781 z m -53.45201,53.452 21.37795,-21.3827 21.38261,21.3827 -21.38261,21.3827 z m 53.45201,53.4519 -21.38275,-21.3827 21.38275,-21.3826 21.38264,21.3826 z m 32.06929,-32.074 -21.37795,-21.3779 21.3826,-21.378 21.37795,21.378 z"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#303030;fill-opacity:1;stroke-width:0.472441" />
|
||||
<path
|
||||
id="path5717-1"
|
||||
d="m 692.85695,1593.1058 a 3.8361918,3.8361918 0 0 0 3.77953,3.685 h 7.55867 a 3.7511533,3.7511533 0 0 0 3.77991,-3.8739 c -0.99288,-19.8897 -9.11811,-34.6297 -19.65317,-45.1178 -8.69329,-8.8345 -9.96888,-18.0471 -10.34683,-23.1022 a 3.7180827,3.7180827 0 0 0 -3.77953,-3.3543 l -7.55905,0.045 a 3.7794994,3.7794994 0 0 0 -3.73191,4.1102 49.823248,49.823248 0 0 0 14.74016,32.9289 c 8.17285,8.1259 14.36183,19.2282 15.21222,34.6768 z m -52.67679,0 a 3.8361918,3.8361918 0 0 0 3.77953,3.685 h 7.55905 a 3.7511533,3.7511533 0 0 0 3.77953,-3.8739 c -0.99212,-19.8897 -9.11811,-34.6297 -19.65354,-45.1178 -8.64567,-8.8345 -9.92088,-18.0471 -10.29884,-23.1022 a 3.7322557,3.7322557 0 0 0 -3.82677,-3.3543 l -7.55905,0.045 a 3.7794994,3.7794994 0 0 0 -3.73229,4.1102 c 0.47244,7.0393 2.45669,20.5982 14.74016,32.9289 8.16831,8.1259 14.35729,19.2282 15.21222,34.6768 z m 128.50318,18.9448 H 557.03131 a 15.117997,15.117997 0 0 0 -15.11811,15.1179 c 0,44.7399 24.35868,83.6971 60.47206,104.6214 v 16.3227 a 15.117997,15.117997 0 0 0 15.11773,15.118 h 90.70791 a 15.117997,15.117997 0 0 0 15.11849,-15.118 v -16.3227 c 36.11263,-20.9243 60.47169,-59.8815 60.47169,-104.6214 a 15.117997,15.117997 0 0 0 -15.11774,-15.1179 z m -60.47244,111.0228 v 25.0392 h -90.70791 v -25.0392 c -62.52207,-36.2218 -60.47168,-87.7695 -60.47168,-95.9049 h 211.65203 c 0,8.2488 1.59194,59.9476 -60.47244,95.9049 z"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#303030;fill-opacity:1;stroke-width:0.472437" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 24 KiB |
17
cookbook/static/css/app.min.css
vendored
Normal file
17
cookbook/static/css/app.min.css
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
.spinner-tandoor {
|
||||
animation: rotation 3s infinite linear;
|
||||
content: url("../assets/spinner.svg");
|
||||
width: auto;
|
||||
height: 20vh;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@keyframes rotation {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(359deg);
|
||||
}
|
||||
}
|
||||
204
cookbook/static/themes/tandoor.min.css
vendored
204
cookbook/static/themes/tandoor.min.css
vendored
@@ -1,83 +1,91 @@
|
||||
/* devanagari */
|
||||
@font-face {
|
||||
font-family: 'Poppins';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(/static/webfonts/poppins_devanagari_400.woff2) format('woff2');
|
||||
unicode-range: U+0900-097F, U+1CD0-1CF6, U+1CF8-1CF9, U+200C-200D, U+20A8, U+20B9, U+25CC, U+A830-A839, U+A8E0-A8FB;
|
||||
font-family: 'Poppins';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(/static/webfonts/poppins_devanagari_400.woff2) format('woff2');
|
||||
unicode-range: U+0900-097F, U+1CD0-1CF6, U+1CF8-1CF9, U+200C-200D, U+20A8, U+20B9, U+25CC, U+A830-A839, U+A8E0-A8FB;
|
||||
}
|
||||
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Poppins';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(/static/webfonts/poppins_latin_ext_400.woff2) format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
font-family: 'Poppins';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(/static/webfonts/poppins_latin_ext_400.woff2) format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Poppins';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(/static/webfonts/poppins_latin_400.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
font-family: 'Poppins';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(/static/webfonts/poppins_latin_400.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
|
||||
/* devanagari */
|
||||
@font-face {
|
||||
font-family: 'Poppins';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url(/static/webfonts/poppins_devanagari_500.woff2) format('woff2');
|
||||
unicode-range: U+0900-097F, U+1CD0-1CF6, U+1CF8-1CF9, U+200C-200D, U+20A8, U+20B9, U+25CC, U+A830-A839, U+A8E0-A8FB;
|
||||
font-family: 'Poppins';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url(/static/webfonts/poppins_devanagari_500.woff2) format('woff2');
|
||||
unicode-range: U+0900-097F, U+1CD0-1CF6, U+1CF8-1CF9, U+200C-200D, U+20A8, U+20B9, U+25CC, U+A830-A839, U+A8E0-A8FB;
|
||||
}
|
||||
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Poppins';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url(/static/webfonts/poppins_latin_ext_500.woff2) format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
font-family: 'Poppins';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url(/static/webfonts/poppins_latin_ext_500.woff2) format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Poppins';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url(/static/webfonts/poppins_latin_500.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
font-family: 'Poppins';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url(/static/webfonts/poppins_latin_500.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
|
||||
/* devanagari */
|
||||
@font-face {
|
||||
font-family: 'Poppins';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url(/static/webfonts/poppins_devanagari_700.woff2) format('woff2');
|
||||
unicode-range: U+0900-097F, U+1CD0-1CF6, U+1CF8-1CF9, U+200C-200D, U+20A8, U+20B9, U+25CC, U+A830-A839, U+A8E0-A8FB;
|
||||
font-family: 'Poppins';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url(/static/webfonts/poppins_devanagari_700.woff2) format('woff2');
|
||||
unicode-range: U+0900-097F, U+1CD0-1CF6, U+1CF8-1CF9, U+200C-200D, U+20A8, U+20B9, U+25CC, U+A830-A839, U+A8E0-A8FB;
|
||||
}
|
||||
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Poppins';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url(/static/webfonts/poppins_latin_ext_700.woff2) format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
font-family: 'Poppins';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url(/static/webfonts/poppins_latin_ext_700.woff2) format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Poppins';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url(/static/webfonts/poppins_latin_700.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
font-family: 'Poppins';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url(/static/webfonts/poppins_latin_700.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
|
||||
|
||||
@@ -92,7 +100,7 @@
|
||||
--indigo: #6610f2;
|
||||
--purple: #6f42c1;
|
||||
--pink: #e83e8c;
|
||||
--#a7240e: #dc3545;
|
||||
-- #a7240e: #dc3545;
|
||||
--orange: #fd7e14;
|
||||
--yellow: #ffc107;
|
||||
--green: #28a745;
|
||||
@@ -1812,7 +1820,9 @@ pre code {
|
||||
transition: border-color .15s ease-in-out, box-shadow .15s ease-in-out
|
||||
}
|
||||
|
||||
@media (prefers-#a7240euced-motion: #a7240euce) {
|
||||
@media (prefers-#a7240euced-motion: #a7240euce
|
||||
|
||||
) {
|
||||
.form-control {
|
||||
transition: none
|
||||
}
|
||||
@@ -2275,7 +2285,9 @@ select.form-control[multiple], select.form-control[size], textarea.form-control
|
||||
transition: color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out
|
||||
}
|
||||
|
||||
@media (prefers-#a7240euced-motion: #a7240euce) {
|
||||
@media (prefers-#a7240euced-motion: #a7240euce
|
||||
|
||||
) {
|
||||
.btn {
|
||||
transition: none
|
||||
}
|
||||
@@ -2807,7 +2819,9 @@ input[type=button].btn-block, input[type=reset].btn-block, input[type=submit].bt
|
||||
transition: opacity .15s linear
|
||||
}
|
||||
|
||||
@media (prefers-#a7240euced-motion: #a7240euce) {
|
||||
@media (prefers-#a7240euced-motion: #a7240euce
|
||||
|
||||
) {
|
||||
.fade {
|
||||
transition: none
|
||||
}
|
||||
@@ -2828,7 +2842,9 @@ input[type=button].btn-block, input[type=reset].btn-block, input[type=submit].bt
|
||||
transition: height .35s ease
|
||||
}
|
||||
|
||||
@media (prefers-#a7240euced-motion: #a7240euce) {
|
||||
@media (prefers-#a7240euced-motion: #a7240euce
|
||||
|
||||
) {
|
||||
.collapsing {
|
||||
transition: none
|
||||
}
|
||||
@@ -3439,7 +3455,9 @@ input[type=button].btn-block, input[type=reset].btn-block, input[type=submit].bt
|
||||
transition: transform .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out
|
||||
}
|
||||
|
||||
@media (prefers-#a7240euced-motion: #a7240euce) {
|
||||
@media (prefers-#a7240euced-motion: #a7240euce
|
||||
|
||||
) {
|
||||
.custom-switch .custom-control-label:after {
|
||||
transition: none
|
||||
}
|
||||
@@ -3621,7 +3639,9 @@ input[type=button].btn-block, input[type=reset].btn-block, input[type=submit].bt
|
||||
appearance: none
|
||||
}
|
||||
|
||||
@media (prefers-#a7240euced-motion: #a7240euce) {
|
||||
@media (prefers-#a7240euced-motion: #a7240euce
|
||||
|
||||
) {
|
||||
.custom-range::-webkit-slider-thumb {
|
||||
transition: none
|
||||
}
|
||||
@@ -3652,7 +3672,9 @@ input[type=button].btn-block, input[type=reset].btn-block, input[type=submit].bt
|
||||
appearance: none
|
||||
}
|
||||
|
||||
@media (prefers-#a7240euced-motion: #a7240euce) {
|
||||
@media (prefers-#a7240euced-motion: #a7240euce
|
||||
|
||||
) {
|
||||
.custom-range::-moz-range-thumb {
|
||||
transition: none
|
||||
}
|
||||
@@ -3685,7 +3707,9 @@ input[type=button].btn-block, input[type=reset].btn-block, input[type=submit].bt
|
||||
appearance: none
|
||||
}
|
||||
|
||||
@media (prefers-#a7240euced-motion: #a7240euce) {
|
||||
@media (prefers-#a7240euced-motion: #a7240euce
|
||||
|
||||
) {
|
||||
.custom-range::-ms-thumb {
|
||||
transition: none
|
||||
}
|
||||
@@ -3738,7 +3762,9 @@ input[type=button].btn-block, input[type=reset].btn-block, input[type=submit].bt
|
||||
transition: background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out
|
||||
}
|
||||
|
||||
@media (prefers-#a7240euced-motion: #a7240euce) {
|
||||
@media (prefers-#a7240euced-motion: #a7240euce
|
||||
|
||||
) {
|
||||
.custom-control-label:before, .custom-file-label, .custom-select {
|
||||
transition: none
|
||||
}
|
||||
@@ -4249,7 +4275,7 @@ input[type=button].btn-block, input[type=reset].btn-block, input[type=submit].bt
|
||||
|
||||
.card-footer {
|
||||
padding: .75rem 1.25rem;
|
||||
background-color: rgba(0, 0, 0, .03);
|
||||
background-color: #ffffff;
|
||||
border-top: 1px solid rgba(0, 0, 0, .125)
|
||||
}
|
||||
|
||||
@@ -4551,7 +4577,9 @@ input[type=button].btn-block, input[type=reset].btn-block, input[type=submit].bt
|
||||
transition: color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out
|
||||
}
|
||||
|
||||
@media (prefers-#a7240euced-motion: #a7240euce) {
|
||||
@media (prefers-#a7240euced-motion: #a7240euce
|
||||
|
||||
) {
|
||||
.badge {
|
||||
transition: none
|
||||
}
|
||||
@@ -4772,9 +4800,9 @@ a.badge-dark.focus, a.badge-dark:focus {
|
||||
}
|
||||
|
||||
.alert-success {
|
||||
color: #316f5d;
|
||||
background-color: #dff7f0;
|
||||
border-color: #d2f4ea
|
||||
color: #2e2e2e;
|
||||
background-color: #82aa8b;
|
||||
border-color: #82aa8b
|
||||
}
|
||||
|
||||
.alert-success hr {
|
||||
@@ -4893,7 +4921,9 @@ a.badge-dark.focus, a.badge-dark:focus {
|
||||
transition: width .6s ease
|
||||
}
|
||||
|
||||
@media (prefers-#a7240euced-motion: #a7240euce) {
|
||||
@media (prefers-#a7240euced-motion: #a7240euce
|
||||
|
||||
) {
|
||||
.progress-bar {
|
||||
transition: none
|
||||
}
|
||||
@@ -4909,7 +4939,9 @@ a.badge-dark.focus, a.badge-dark:focus {
|
||||
animation: progress-bar-stripes 1s linear infinite
|
||||
}
|
||||
|
||||
@media (prefers-#a7240euced-motion: #a7240euce) {
|
||||
@media (prefers-#a7240euced-motion: #a7240euce
|
||||
|
||||
) {
|
||||
.progress-bar-animated {
|
||||
-webkit-animation: none;
|
||||
animation: none
|
||||
@@ -5358,7 +5390,9 @@ a.close.disabled {
|
||||
transform: translateY(-50px)
|
||||
}
|
||||
|
||||
@media (prefers-#a7240euced-motion: #a7240euce) {
|
||||
@media (prefers-#a7240euced-motion: #a7240euce
|
||||
|
||||
) {
|
||||
.modal.fade .modal-dialog {
|
||||
transition: none
|
||||
}
|
||||
@@ -5838,7 +5872,9 @@ a.close.disabled {
|
||||
transition: transform .6s ease-in-out
|
||||
}
|
||||
|
||||
@media (prefers-#a7240euced-motion: #a7240euce) {
|
||||
@media (prefers-#a7240euced-motion: #a7240euce
|
||||
|
||||
) {
|
||||
.carousel-item {
|
||||
transition: none
|
||||
}
|
||||
@@ -5873,7 +5909,9 @@ a.close.disabled {
|
||||
transition: opacity 0s .6s
|
||||
}
|
||||
|
||||
@media (prefers-#a7240euced-motion: #a7240euce) {
|
||||
@media (prefers-#a7240euced-motion: #a7240euce
|
||||
|
||||
) {
|
||||
.carousel-fade .active.carousel-item-left, .carousel-fade .active.carousel-item-right {
|
||||
transition: none
|
||||
}
|
||||
@@ -5894,7 +5932,9 @@ a.close.disabled {
|
||||
transition: opacity .15s ease
|
||||
}
|
||||
|
||||
@media (prefers-#a7240euced-motion: #a7240euce) {
|
||||
@media (prefers-#a7240euced-motion: #a7240euce
|
||||
|
||||
) {
|
||||
.carousel-control-next, .carousel-control-prev {
|
||||
transition: none
|
||||
}
|
||||
@@ -5961,7 +6001,9 @@ a.close.disabled {
|
||||
transition: opacity .6s ease
|
||||
}
|
||||
|
||||
@media (prefers-#a7240euced-motion: #a7240euce) {
|
||||
@media (prefers-#a7240euced-motion: #a7240euce
|
||||
|
||||
) {
|
||||
.carousel-indicators li {
|
||||
transition: none
|
||||
}
|
||||
@@ -10117,9 +10159,9 @@ footer a:hover {
|
||||
}
|
||||
|
||||
.btn-light:hover {
|
||||
background: transparent;
|
||||
background-color: hsla(0, 0%, 18%, .5);
|
||||
color: #cfd5cd;
|
||||
border: 1px solid #cfd5cd
|
||||
border: 1px solid hsla(0, 0%, 18%, .5)
|
||||
}
|
||||
|
||||
.btn-dark {
|
||||
@@ -10402,7 +10444,7 @@ footer a:hover {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
textarea, input:not([type="submit"]):not([class="multiselect__input"]), select {
|
||||
textarea, input:not([type="submit"]):not([class="multiselect__input"]):not([class="select2-search__field"]), select {
|
||||
background-color: white !important;
|
||||
border-radius: .25rem !important;
|
||||
border: 1px solid #ced4da !important;
|
||||
@@ -10413,10 +10455,10 @@ textarea, input:not([type="submit"]):not([class="multiselect__input"]), select {
|
||||
color: #212529 !important;
|
||||
}
|
||||
|
||||
.multiselect__tag-icon:hover,.multiselect__tag-icon:focus {
|
||||
.multiselect__tag-icon:hover, .multiselect__tag-icon:focus {
|
||||
background-color: #a7240e !important;
|
||||
}
|
||||
|
||||
.multiselect__tag-icon:after {
|
||||
color: #212529 !important
|
||||
}
|
||||
}
|
||||
|
||||
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
@@ -141,17 +141,17 @@ class ShoppingListTable(tables.Table):
|
||||
|
||||
class InviteLinkTable(tables.Table):
|
||||
link = tables.TemplateColumn(
|
||||
"<a href='{% url 'view_signup' record.uuid %}' >" + _('Link') + "</a>"
|
||||
"<input value='{{ request.scheme }}://{{ request.get_host }}{% url 'view_invite' record.uuid %}' class='form-control' />"
|
||||
)
|
||||
delete = tables.TemplateColumn(
|
||||
"<a href='{% url 'delete_invite_link' record.id %}' >" + _('Delete') + "</a>" # noqa: E501
|
||||
delete_link = tables.TemplateColumn(
|
||||
"<a href='{% url 'delete_invite_link' record.pk %}' >" + _('Delete') + "</a>", verbose_name=_('Delete')
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = InviteLink
|
||||
template_name = 'generic/table_template.html'
|
||||
fields = (
|
||||
'username', 'group', 'valid_until', 'created_by', 'created_at'
|
||||
'username', 'group', 'valid_until',
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{% extends "base.html" %}
|
||||
{% load crispy_forms_filters %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
|
||||
{% load account socialaccount %}
|
||||
|
||||
@@ -8,6 +9,7 @@
|
||||
|
||||
{% block content %}
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12" style="text-align: center">
|
||||
<h3>{% trans "Sign In" %}</h3>
|
||||
@@ -17,6 +19,7 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="col-6 offset-3">
|
||||
<hr>
|
||||
<form class="login" method="POST" action="{% url 'account_login' %}">
|
||||
{% csrf_token %}
|
||||
{{ form | crispy }}
|
||||
@@ -25,12 +28,12 @@
|
||||
<input type="hidden" name="{{ redirect_field_name }}" value="{{ redirect_field_value }}"/>
|
||||
{% endif %}
|
||||
|
||||
<button class="btn btn-primary" type="submit">{% trans "Sign In" %}</button>
|
||||
<a class="btn btn-success" href="{% url 'account_signup' %}">{% trans "Sign Up" %}</a>
|
||||
<button class="btn btn-success" type="submit">{% trans "Sign In" %}</button>
|
||||
<a class="btn btn-secondary" href="{% url 'account_signup' %}">{% trans "Sign Up" %}</a>
|
||||
|
||||
{% if settings.EMAIL_HOST != '' %}
|
||||
<a class="btn btn-secondary"
|
||||
href="{% url 'account_reset_password' %}">{% trans "Reset Password" %}</a>
|
||||
{% if EMAIL_ENABLED %}
|
||||
<a class="btn btn-warning float-right"
|
||||
href="{% url 'account_reset_password' %}">{% trans "Reset My Password" %}</a>
|
||||
{% endif %}
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -8,25 +8,31 @@
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h3>{% trans "Password Reset" %}</h3>
|
||||
{% if user.is_authenticated %}
|
||||
{% include "account/snippets/already_logged_in.html" %}
|
||||
{% endif %}
|
||||
<div class="row">
|
||||
<div class="col-12" style="text-align: center">
|
||||
<h3>{% trans "Password Reset" %}</h3>
|
||||
{% if user.is_authenticated %}
|
||||
{% include "account/snippets/already_logged_in.html" %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if settings.EMAIL_HOST != '' %}
|
||||
<p>{% trans "Forgotten your password? Enter your e-mail address below, and we'll send you an e-mail allowing you to reset it." %}</p>
|
||||
|
||||
<form method="POST" action="{% url 'account_reset_password' %}" class="password_reset">
|
||||
{% csrf_token %}
|
||||
{{ form | crispy }}
|
||||
<input type="submit" class="btn btn-success" value="{% trans 'Reset My Password' %}"/>
|
||||
<a class="btn btn-primary" href="{% url 'account_login' %}">{% trans "Sign In" %}</a>
|
||||
<a class="btn btn-info" href="{% url 'account_signup' %}">{% trans "Sign Up" %}</a>
|
||||
</form>
|
||||
{% else %}
|
||||
<p>{% trans 'Password reset is disabled on this instance.' %}</p>
|
||||
{% endif %}
|
||||
<div class="row">
|
||||
<div class="col-6 offset-3">
|
||||
<hr>
|
||||
{% if EMAIL_ENABLED %}
|
||||
<p>{% trans "Forgotten your password? Enter your e-mail address below, and we'll send you an e-mail allowing you to reset it." %}</p>
|
||||
|
||||
<form method="POST" action="{% url 'account_reset_password' %}" class="password_reset">
|
||||
{% csrf_token %}
|
||||
{{ form | crispy }}
|
||||
<input type="submit" class="btn btn-warning float-right" value="{% trans 'Reset My Password' %}"/>
|
||||
</form>
|
||||
{% else %}
|
||||
<p>{% trans 'Password reset is disabled on this instance.' %}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{% endblock %}
|
||||
@@ -1,19 +1,74 @@
|
||||
{% extends "base.html" %}
|
||||
{% load crispy_forms_filters %}
|
||||
{% load crispy_forms_filters %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans 'Register' %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h3>{% trans 'Create your Account' %}</h3>
|
||||
<div class="row">
|
||||
<div class="col-12" style="text-align: center">
|
||||
<h3>{% trans "Create an Account" %}</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{{ form|crispy }}
|
||||
<button class="btn btn-success" type="submit"><i class="fas fa-save"></i> {% trans 'Create User' %}</button>
|
||||
</form>
|
||||
<div class="row">
|
||||
<div class="col-6 offset-3">
|
||||
<hr>
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
<p>{% trans 'Already have an account?' %} <a href="{% url 'account_login' %}">{% trans "Sign In" %}</a></p>
|
||||
<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>
|
||||
<div class="form-group">
|
||||
{{ form.password1 |as_crispy_field }}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
{{ form.password2 |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 class="btn btn-success" type="submit"><i class="fas fa-save"></i> {% trans 'Create User' %}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<p>{% trans 'Already have an account?' %} <a href="{% url 'account_login' %}">{% trans "Sign In" %}</a></p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
@@ -5,9 +5,14 @@
|
||||
{% block title %}{% trans "Sign Up Closed" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{% trans "Sign Up Closed" %}</h1>
|
||||
<div class="row">
|
||||
<div class="col-6 offset-3">
|
||||
<hr>
|
||||
<h1>{% trans "Sign Up Closed" %}</h1>
|
||||
|
||||
<p>{% trans "We are sorry, but the sign up is currently closed." %}</p>
|
||||
<p>{% trans "We are sorry, but the sign up is currently closed." %}</p>
|
||||
|
||||
<a class="btn btn-primary" href="{% url 'account_login' %}">{% trans "Sign In" %}</a>
|
||||
<a class="btn btn-primary" href="{% url 'account_login' %}">{% trans "Sign In" %}</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -31,6 +31,7 @@
|
||||
|
||||
<!-- Bootstrap 4 -->
|
||||
<link id="id_main_css" href="{% theme_url request %}" rel="stylesheet">
|
||||
<link href="{% static 'css/app.min.css' %}" rel="stylesheet">
|
||||
<script src="{% static 'js/jquery-3.5.1.min.js' %}"></script>
|
||||
|
||||
<script src="{% static 'js/popper.min.js' %}"></script>
|
||||
@@ -65,9 +66,9 @@
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
|
||||
{% if request.user.is_authenticated and request.user.userpreference.theme == request.user.userpreference.TANDOOR %}
|
||||
{% if not request.user.is_authenticated or request.user.userpreference.theme == request.user.userpreference.TANDOOR %}
|
||||
<a class="navbar-brand p-0 me-2 justify-content-center" href="/" aria-label="Tandoor">
|
||||
<img class="brand-icon" src="{% static 'assets/brand_logo.svg' %}" alt="" style="height: 5vh;">
|
||||
<img class="brand-icon" src="{% static 'assets/brand_logo.png' %}" alt="" style="height: 5vh;">
|
||||
</a>
|
||||
{% endif %}
|
||||
<div class="collapse navbar-collapse" id="navbarText">
|
||||
@@ -139,7 +140,7 @@
|
||||
{% page_help request.resolver_match.url_name as help_button %}
|
||||
{% if help_button %}{{ help_button|safe }}{% endif %}
|
||||
|
||||
<li class="nav-item dropdown {% if request.resolver_match.url_name in 'view_settings,view_history,view_system,docs_markdown' %}active{% endif %}">
|
||||
<li class="nav-item dropdown {% if request.resolver_match.url_name in 'view_space,view_settings,view_history,view_system,docs_markdown' %}active{% endif %}">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdownMenuLink" data-toggle="dropdown"
|
||||
aria-haspopup="true" aria-expanded="false"><i
|
||||
class="fas fa-user-alt"></i> {{ user.get_user_name }}
|
||||
|
||||
@@ -14,7 +14,12 @@
|
||||
<br/>
|
||||
<br/>
|
||||
<div class="text-center">
|
||||
<i class="fas fa-sync fa-spin fa-10x"></i>
|
||||
|
||||
{% if not request.user.is_authenticated or request.user.userpreference.theme == request.user.userpreference.TANDOOR %}
|
||||
<img class="spinner-tandoor"/>
|
||||
{% else %}
|
||||
<i class="fas fa-sync fa-spin fa-10x"></i>
|
||||
{% endif %}
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
@@ -35,7 +35,11 @@
|
||||
|
||||
<div v-if="!recipe" class="text-center">
|
||||
<br/>
|
||||
<i class="fas fa-spinner fa-spin fa-8x"></i>
|
||||
{% if not request.user.is_authenticated or request.user.userpreference.theme == request.user.userpreference.TANDOOR %}
|
||||
<img class="spinner-tandoor"/>
|
||||
{% else %}
|
||||
<i class="fas fa-spinner fa-spin fa-8x"></i>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div v-if="recipe">
|
||||
@@ -668,7 +672,7 @@
|
||||
this.recipe.steps[step].ingredients[id] = new_unit
|
||||
},
|
||||
addKeyword: function (tag) {
|
||||
let new_keyword = {'label':tag,'name':tag}
|
||||
let new_keyword = {'label': tag, 'name': tag}
|
||||
this.recipe.keywords.push(new_keyword)
|
||||
},
|
||||
searchKeywords: function (query) {
|
||||
|
||||
@@ -37,7 +37,11 @@
|
||||
<template v-if="shopping_list !== undefined">
|
||||
|
||||
<div class="text-center" v-if="loading">
|
||||
<i class="fas fa-spinner fa-spin fa-8x"></i>
|
||||
{% if not request.user.is_authenticated or request.user.userpreference.theme == request.user.userpreference.TANDOOR %}
|
||||
<img class="spinner-tandoor"/>
|
||||
{% else %}
|
||||
<i class="fas fa-spinner fa-spin fa-8x"></i>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div v-else-if="edit_mode">
|
||||
<div class="row">
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{% extends "base.html" %}
|
||||
{% load django_tables2 %}
|
||||
{% load crispy_forms_filters %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
@@ -13,7 +14,9 @@
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h3>{{ request.space.name }}</h3>
|
||||
<h3>{{ request.space.name }} <small>{% if HOSTED %}
|
||||
<a href="https://tandoor.dev/manage">{% trans 'Manage Subscription' %}</a>{% endif %}</small></h3>
|
||||
|
||||
<br/>
|
||||
|
||||
<div class="row">
|
||||
@@ -90,18 +93,19 @@
|
||||
</td>
|
||||
<td>
|
||||
{% if u.user != request.user %}
|
||||
<div class="input-group mb-3">
|
||||
<select v-model="users['{{ u.pk }}']" class="custom-select form-control" style="height: 44px">
|
||||
<option>{% trans 'admin' %}</option>
|
||||
<option>{% trans 'user' %}</option>
|
||||
<option>{% trans 'guest' %}</option>
|
||||
<option>{% trans 'remove' %}</option>
|
||||
</select>
|
||||
<span class="input-group-append">
|
||||
<div class="input-group mb-3">
|
||||
<select v-model="users['{{ u.pk }}']" class="custom-select form-control"
|
||||
style="height: 44px">
|
||||
<option value="admin">{% trans 'admin' %}</option>
|
||||
<option value="user">{% trans 'user' %}</option>
|
||||
<option value="guest">{% trans 'guest' %}</option>
|
||||
<option value="remove">{% trans 'remove' %}</option>
|
||||
</select>
|
||||
<span class="input-group-append">
|
||||
<a class="btn btn-warning"
|
||||
:href="editUserUrl({{ u.pk }}, {{ u.space.pk }})" >{% trans 'Update' %}</a>
|
||||
:href="editUserUrl({{ u.pk }}, {{ u.space.pk }})">{% trans 'Update' %}</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
{% trans 'You cannot edit yourself.' %}
|
||||
{% endif %}
|
||||
@@ -115,6 +119,17 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col col-md-12">
|
||||
<h4>{% trans 'Invite Links' %}</h4>
|
||||
{% render_table invite_links %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block script %}
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
style="height:50%"
|
||||
href="{% bookmarklet request %}"
|
||||
title="{% trans 'Drag me to your bookmarks to import recipes from anywhere' %}">
|
||||
<img src="{% static 'assets/favicon-16x16.png' %}">{% trans 'Bookmark Me!' %} </a>
|
||||
<img src="{% static 'assets/favicon-16x16.png' %}"> {% trans 'Bookmark Me!' %} </a>
|
||||
</div>
|
||||
<nav class="nav nav-pills flex-sm-row" style="margin-bottom:10px">
|
||||
<a class="nav-link active" href="#nav-url" data-toggle="tab" role="tab" aria-controls="nav-url"
|
||||
@@ -156,7 +156,11 @@
|
||||
|
||||
<div v-if="loading" class="text-center">
|
||||
<br/>
|
||||
<i class="fas fa-spinner fa-spin fa-8x"></i>
|
||||
{% if not request.user.is_authenticated or request.user.userpreference.theme == request.user.userpreference.TANDOOR %}
|
||||
<img class="spinner-tandoor"/>
|
||||
{% else %}
|
||||
<i class="fas fa-spinner fa-spin fa-8x"></i>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- recipe preview before Import -->
|
||||
|
||||
@@ -9,7 +9,7 @@ register = template.Library()
|
||||
@register.simple_tag
|
||||
def theme_url(request):
|
||||
if not request.user.is_authenticated:
|
||||
return static('themes/flatly.min.css')
|
||||
return static('themes/tandoor.min.css')
|
||||
themes = {
|
||||
UserPreference.BOOTSTRAP: 'themes/bootstrap.min.css',
|
||||
UserPreference.FLATLY: 'themes/flatly.min.css',
|
||||
|
||||
@@ -48,7 +48,8 @@ urlpatterns = [
|
||||
path('no-group', views.no_groups, name='view_no_group'),
|
||||
path('no-space', views.no_space, name='view_no_space'),
|
||||
path('no-perm', views.no_perm, name='view_no_perm'),
|
||||
path('signup/<slug:token>', views.signup, name='view_signup'),
|
||||
path('signup/<slug:token>', views.signup, name='view_signup'), # TODO deprecated with 0.16.2 remove at some point
|
||||
path('invite/<slug:token>', views.invite_link, name='view_invite'),
|
||||
path('system/', views.system, name='view_system'),
|
||||
path('search/', views.search, name='view_search'),
|
||||
path('search/v2/', views.search_v2, name='view_search_v2'),
|
||||
|
||||
@@ -57,7 +57,6 @@ from cookbook.serializer import (FoodSerializer, IngredientSerializer,
|
||||
ViewLogSerializer, CookLogSerializer, RecipeBookEntrySerializer,
|
||||
RecipeOverviewSerializer, SupermarketSerializer, ImportLogSerializer,
|
||||
BookmarkletImportSerializer, SupermarketCategorySerializer)
|
||||
from recipes.settings import DEMO
|
||||
|
||||
|
||||
class StandardFilterMixin(ViewSetMixin):
|
||||
@@ -390,7 +389,7 @@ class RecipeViewSet(viewsets.ModelViewSet):
|
||||
obj, data=request.data, partial=True
|
||||
)
|
||||
|
||||
if DEMO:
|
||||
if self.request.space.demo:
|
||||
raise PermissionDenied(detail='Not available in demo', code=None)
|
||||
|
||||
if serializer.is_valid():
|
||||
@@ -537,7 +536,7 @@ def get_recipe_file(request, recipe_id):
|
||||
|
||||
@group_required('user')
|
||||
def sync_all(request):
|
||||
if DEMO:
|
||||
if request.space.demo:
|
||||
messages.add_message(
|
||||
request, messages.ERROR, _('This feature is not available in the demo version!')
|
||||
)
|
||||
|
||||
@@ -20,12 +20,20 @@ from cookbook.forms import BatchEditForm, SyncForm
|
||||
from cookbook.helper.permission_helper import group_required, has_group_permission
|
||||
from cookbook.helper.recipe_url_import import parse_cooktime
|
||||
from cookbook.models import (Comment, Food, Ingredient, Keyword, Recipe,
|
||||
RecipeImport, Step, Sync, Unit)
|
||||
RecipeImport, Step, Sync, Unit, UserPreference)
|
||||
from cookbook.tables import SyncTable
|
||||
|
||||
|
||||
@group_required('user')
|
||||
def sync(request):
|
||||
if request.space.max_recipes != 0 and Recipe.objects.filter(space=request.space).count() >= request.space.max_recipes: # TODO move to central helper function
|
||||
messages.add_message(request, messages.WARNING, _('You have reached the maximum number of recipes for your space.'))
|
||||
return HttpResponseRedirect(reverse('index'))
|
||||
|
||||
if request.space.max_users != 0 and UserPreference.objects.filter(space=request.space).count() > request.space.max_users:
|
||||
messages.add_message(request, messages.WARNING, _('You have more users than allowed in your space.'))
|
||||
return HttpResponseRedirect(reverse('index'))
|
||||
|
||||
if request.method == "POST":
|
||||
if not has_group_permission(request.user, ['admin']):
|
||||
messages.add_message(request, messages.ERROR, _('You do not have the required permissions to view this page!'))
|
||||
@@ -109,6 +117,14 @@ def batch_edit(request):
|
||||
@group_required('user')
|
||||
@atomic
|
||||
def import_url(request):
|
||||
if request.space.max_recipes != 0 and Recipe.objects.filter(space=request.space).count() >= request.space.max_recipes: # TODO move to central helper function
|
||||
messages.add_message(request, messages.WARNING, _('You have reached the maximum number of recipes for your space.'))
|
||||
return HttpResponseRedirect(reverse('index'))
|
||||
|
||||
if request.space.max_users != 0 and UserPreference.objects.filter(space=request.space).count() > request.space.max_users:
|
||||
messages.add_message(request, messages.WARNING, _('You have more users than allowed in your space.'))
|
||||
return HttpResponseRedirect(reverse('index'))
|
||||
|
||||
if request.method == 'POST':
|
||||
data = json.loads(request.body)
|
||||
data['cookTime'] = parse_cooktime(data.get('cookTime', ''))
|
||||
@@ -132,13 +148,11 @@ def import_url(request):
|
||||
recipe.steps.add(step)
|
||||
|
||||
for kw in data['keywords']:
|
||||
# if k := Keyword.objects.filter(name=kw['text'], space=request.space).first():
|
||||
# recipe.keywords.add(k)
|
||||
# elif data['all_keywords']:
|
||||
# k = Keyword.objects.create(name=kw['text'], space=request.space)
|
||||
# recipe.keywords.add(k)
|
||||
k, created = Keyword.objects.get_or_create(name=kw['text'].strip(), space=request.space)
|
||||
recipe.keywords.add(k)
|
||||
if k := Keyword.objects.filter(name=kw['text'], space=request.space).first():
|
||||
recipe.keywords.add(k)
|
||||
elif data['all_keywords']:
|
||||
k = Keyword.objects.create(name=kw['text'], space=request.space)
|
||||
recipe.keywords.add(k)
|
||||
|
||||
for ing in data['recipeIngredient']:
|
||||
ingredient = Ingredient()
|
||||
@@ -200,6 +214,7 @@ def import_url(request):
|
||||
|
||||
return render(request, 'url_import.html', context)
|
||||
|
||||
|
||||
class Object(object):
|
||||
pass
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import os
|
||||
|
||||
from django.contrib import messages
|
||||
from django.db.models import Q
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.shortcuts import get_object_or_404, render
|
||||
from django.urls import reverse, reverse_lazy
|
||||
@@ -45,7 +46,7 @@ def convert_recipe(request, pk):
|
||||
|
||||
@group_required('user')
|
||||
def internal_recipe_update(request, pk):
|
||||
if request.space.max_recipes != 0 and Recipe.objects.filter(space=request.space).count() > request.space.max_recipes:
|
||||
if request.space.max_recipes != 0 and Recipe.objects.filter(space=request.space).count() > request.space.max_recipes: # TODO move to central helper function
|
||||
messages.add_message(request, messages.WARNING, _('You have reached the maximum number of recipes for your space.'))
|
||||
return HttpResponseRedirect(reverse('view_recipe', args=[pk]))
|
||||
|
||||
@@ -287,14 +288,15 @@ def edit_ingredients(request):
|
||||
new_unit = units_form.cleaned_data['new_unit']
|
||||
old_unit = units_form.cleaned_data['old_unit']
|
||||
if new_unit != old_unit:
|
||||
recipe_ingredients = Ingredient.objects.filter(unit=old_unit, step__recipe__space=request.space).all()
|
||||
for i in recipe_ingredients:
|
||||
i.unit = new_unit
|
||||
i.save()
|
||||
with scopes_disabled():
|
||||
recipe_ingredients = Ingredient.objects.filter(unit=old_unit).filter(Q(step__recipe__space=request.space) | Q(step__recipe__isnull=True)).all()
|
||||
for i in recipe_ingredients:
|
||||
i.unit = new_unit
|
||||
i.save()
|
||||
|
||||
old_unit.delete()
|
||||
success = True
|
||||
messages.add_message(request, messages.SUCCESS, _('Units merged!'))
|
||||
old_unit.delete()
|
||||
success = True
|
||||
messages.add_message(request, messages.SUCCESS, _('Units merged!'))
|
||||
else:
|
||||
messages.add_message(request, messages.ERROR, _('Cannot merge with the same object!'))
|
||||
|
||||
@@ -303,7 +305,7 @@ def edit_ingredients(request):
|
||||
new_food = food_form.cleaned_data['new_food']
|
||||
old_food = food_form.cleaned_data['old_food']
|
||||
if new_food != old_food:
|
||||
ingredients = Ingredient.objects.filter(food=old_food, step__recipe__space=request.space).all()
|
||||
ingredients = Ingredient.objects.filter(food=old_food).filter(Q(step__recipe__space=request.space) | Q(step__recipe__isnull=True)).all()
|
||||
for i in ingredients:
|
||||
i.food = new_food
|
||||
i.save()
|
||||
|
||||
@@ -23,7 +23,7 @@ from cookbook.integration.recettetek import RecetteTek
|
||||
from cookbook.integration.recipesage import RecipeSage
|
||||
from cookbook.integration.rezkonv import RezKonv
|
||||
from cookbook.integration.safron import Safron
|
||||
from cookbook.models import Recipe, ImportLog
|
||||
from cookbook.models import Recipe, ImportLog, UserPreference
|
||||
|
||||
|
||||
def get_integration(request, export_type):
|
||||
@@ -57,6 +57,14 @@ def get_integration(request, export_type):
|
||||
|
||||
@group_required('user')
|
||||
def import_recipe(request):
|
||||
if request.space.max_recipes != 0 and Recipe.objects.filter(space=request.space).count() >= request.space.max_recipes: # TODO move to central helper function
|
||||
messages.add_message(request, messages.WARNING, _('You have reached the maximum number of recipes for your space.'))
|
||||
return HttpResponseRedirect(reverse('index'))
|
||||
|
||||
if request.space.max_users != 0 and UserPreference.objects.filter(space=request.space).count() > request.space.max_users:
|
||||
messages.add_message(request, messages.WARNING, _('You have more users than allowed in your space.'))
|
||||
return HttpResponseRedirect(reverse('index'))
|
||||
|
||||
if request.method == "POST":
|
||||
form = ImportForm(request.POST, request.FILES)
|
||||
if form.is_valid():
|
||||
|
||||
@@ -28,11 +28,11 @@ class RecipeCreate(GroupRequiredMixin, CreateView):
|
||||
fields = ('name',)
|
||||
|
||||
def form_valid(self, form):
|
||||
if Recipe.objects.filter(space=self.request.space).count() >= self.request.space.max_recipes:
|
||||
if self.request.space.max_recipes != 0 and Recipe.objects.filter(space=self.request.space).count() >= self.request.space.max_recipes: # TODO move to central helper function
|
||||
messages.add_message(self.request, messages.WARNING, _('You have reached the maximum number of recipes for your space.'))
|
||||
return HttpResponseRedirect(reverse('index'))
|
||||
|
||||
if UserPreference.objects.filter(space=self.request.space).count() > self.request.space.max_users:
|
||||
if self.request.space.max_users != 0 and UserPreference.objects.filter(space=self.request.space).count() > self.request.space.max_users:
|
||||
messages.add_message(self.request, messages.WARNING, _('You have more users than allowed in your space.'))
|
||||
return HttpResponseRedirect(reverse('index'))
|
||||
|
||||
@@ -225,9 +225,9 @@ class InviteLinkCreate(GroupRequiredMixin, CreateView):
|
||||
if InviteLink.objects.filter(space=self.request.space, created_at__gte=datetime.now() - timedelta(hours=4)).count() < 20:
|
||||
message = _('Hello') + '!\n\n' + _('You have been invited by ') + escape(self.request.user.username)
|
||||
message += _(' to join their Tandoor Recipes space ') + escape(self.request.space.name) + '.\n\n'
|
||||
message += _('Click the following link to activate your account: ') + self.request.build_absolute_uri(reverse('view_signup', args=[str(obj.uuid)])) + '\n\n'
|
||||
message += _('Click the following link to activate your account: ') + self.request.build_absolute_uri(reverse('view_invite', args=[str(obj.uuid)])) + '\n\n'
|
||||
message += _('If the link does not work use the following code to manually join the space: ') + str(obj.uuid) + '\n\n'
|
||||
message += _('The invitation is valid until ') + obj.valid_until + '\n\n'
|
||||
message += _('The invitation is valid until ') + str(obj.valid_until) + '\n\n'
|
||||
message += _('Tandoor Recipes is an Open Source recipe manager. Check it out on GitHub ') + 'https://github.com/vabene1111/recipes/'
|
||||
|
||||
send_mail(
|
||||
@@ -236,7 +236,6 @@ class InviteLinkCreate(GroupRequiredMixin, CreateView):
|
||||
None,
|
||||
[obj.email],
|
||||
fail_silently=False,
|
||||
|
||||
)
|
||||
messages.add_message(self.request, messages.SUCCESS,
|
||||
_('Invite link successfully send to user.'))
|
||||
@@ -246,7 +245,7 @@ class InviteLinkCreate(GroupRequiredMixin, CreateView):
|
||||
except (SMTPException, BadHeaderError, TimeoutError):
|
||||
messages.add_message(self.request, messages.ERROR, _('Email to user could not be send, please share link manually.'))
|
||||
|
||||
return HttpResponseRedirect(reverse('list_invite_link'))
|
||||
return HttpResponseRedirect(reverse('view_space'))
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(InviteLinkCreate, self).get_context_data(**kwargs)
|
||||
|
||||
@@ -3,6 +3,7 @@ import re
|
||||
from datetime import datetime
|
||||
from uuid import UUID
|
||||
|
||||
from allauth.account.forms import SignupForm
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth import update_session_auth_hash
|
||||
@@ -25,15 +26,14 @@ from rest_framework.authtoken.models import Token
|
||||
from cookbook.filters import RecipeFilter
|
||||
from cookbook.forms import (CommentForm, Recipe, RecipeBookEntryForm, User,
|
||||
UserCreateForm, UserNameForm, UserPreference,
|
||||
UserPreferenceForm, SpaceJoinForm, SpaceCreateForm)
|
||||
UserPreferenceForm, SpaceJoinForm, SpaceCreateForm, AllAuthSignupForm)
|
||||
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,
|
||||
Food)
|
||||
from cookbook.tables import (CookLogTable, RecipeTable, RecipeTableSmall,
|
||||
ViewLogTable)
|
||||
ViewLogTable, InviteLinkTable)
|
||||
from cookbook.views.data import Object
|
||||
from recipes.settings import DEMO
|
||||
from recipes.version import BUILD_REF, VERSION_NUMBER
|
||||
|
||||
|
||||
@@ -126,7 +126,7 @@ def no_space(request):
|
||||
return HttpResponseRedirect(reverse('index'))
|
||||
|
||||
if join_form.is_valid():
|
||||
return HttpResponseRedirect(reverse('view_signup', args=[join_form.cleaned_data['token']]))
|
||||
return HttpResponseRedirect(reverse('view_invite', args=[join_form.cleaned_data['token']]))
|
||||
else:
|
||||
if settings.SOCIAL_DEFAULT_ACCESS:
|
||||
request.user.userpreference.space = Space.objects.first()
|
||||
@@ -134,7 +134,7 @@ def no_space(request):
|
||||
request.user.groups.add(Group.objects.get(name=settings.SOCIAL_DEFAULT_GROUP))
|
||||
return HttpResponseRedirect(reverse('index'))
|
||||
if 'signup_token' in request.session:
|
||||
return HttpResponseRedirect(reverse('view_signup', args=[request.session.pop('signup_token', '')]))
|
||||
return HttpResponseRedirect(reverse('view_invite', args=[request.session.pop('signup_token', '')]))
|
||||
|
||||
create_form = SpaceCreateForm()
|
||||
join_form = SpaceJoinForm()
|
||||
@@ -284,7 +284,7 @@ def shopping_list(request, pk=None):
|
||||
|
||||
@group_required('guest')
|
||||
def user_settings(request):
|
||||
if DEMO:
|
||||
if request.space.demo:
|
||||
messages.add_message(request, messages.ERROR, _('This feature is not available in the demo version!'))
|
||||
return redirect('index')
|
||||
|
||||
@@ -420,7 +420,7 @@ def setup(request):
|
||||
return render(request, 'setup.html', {'form': form})
|
||||
|
||||
|
||||
def signup(request, token):
|
||||
def invite_link(request, token):
|
||||
with scopes_disabled():
|
||||
try:
|
||||
token = UUID(token, version=4)
|
||||
@@ -445,48 +445,16 @@ def signup(request, token):
|
||||
messages.add_message(request, messages.SUCCESS, _('Successfully joined space.'))
|
||||
return HttpResponseRedirect(reverse('index'))
|
||||
else:
|
||||
request.session['signup_token'] = token
|
||||
request.session['signup_token'] = str(token)
|
||||
return HttpResponseRedirect(reverse('account_signup'))
|
||||
|
||||
if request.method == 'POST':
|
||||
updated_request = request.POST.copy()
|
||||
if link.username != '':
|
||||
updated_request.update({'name': link.username})
|
||||
messages.add_message(request, messages.ERROR, _('Invite Link not valid or already used!'))
|
||||
return HttpResponseRedirect(reverse('index'))
|
||||
|
||||
form = UserCreateForm(updated_request)
|
||||
|
||||
if form.is_valid():
|
||||
if form.cleaned_data['password'] != form.cleaned_data['password_confirm']: # noqa: E501
|
||||
form.add_error('password', _('Passwords dont match!'))
|
||||
else:
|
||||
user = User(username=form.cleaned_data['name'], )
|
||||
try:
|
||||
validate_password(form.cleaned_data['password'], user=user)
|
||||
user.set_password(form.cleaned_data['password'])
|
||||
user.save()
|
||||
messages.add_message(request, messages.SUCCESS, _('User has been created, please login!'))
|
||||
|
||||
link.used_by = user
|
||||
link.save()
|
||||
|
||||
request.user.groups.clear()
|
||||
user.groups.add(link.group)
|
||||
|
||||
user.userpreference.space = link.space
|
||||
user.userpreference.save()
|
||||
return HttpResponseRedirect(reverse('account_login'))
|
||||
except ValidationError as e:
|
||||
for m in e:
|
||||
form.add_error('password', m)
|
||||
else:
|
||||
form = UserCreateForm()
|
||||
|
||||
if link.username != '':
|
||||
form.fields['name'].initial = link.username
|
||||
form.fields['name'].disabled = True
|
||||
return render(request, 'account/signup.html', {'form': form, 'link': link})
|
||||
|
||||
messages.add_message(request, messages.ERROR, _('Invite Link not valid or already used!'))
|
||||
return HttpResponseRedirect(reverse('index'))
|
||||
# TODO deprecated with 0.16.2 remove at some point
|
||||
def signup(request, token):
|
||||
return HttpResponseRedirect(reverse('view_invite', args=[token]))
|
||||
|
||||
|
||||
@group_required('admin')
|
||||
@@ -506,7 +474,10 @@ def space(request):
|
||||
|
||||
counts.recipes_no_keyword = Recipe.objects.filter(keywords=None, space=request.space).count()
|
||||
|
||||
return render(request, 'space.html', {'space_users': space_users, 'counts': counts})
|
||||
invite_links = InviteLinkTable(InviteLink.objects.filter(valid_until__gte=datetime.today(), used_by=None, space=request.space).all())
|
||||
RequestConfig(request, paginate={'per_page': 25}).configure(invite_links)
|
||||
|
||||
return render(request, 'space.html', {'space_users': space_users, 'counts': counts, 'invite_links': invite_links})
|
||||
|
||||
|
||||
# TODO super hacky and quick solution, safe but needs rework
|
||||
|
||||
@@ -27,7 +27,6 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
SECRET_KEY = os.getenv('SECRET_KEY') if os.getenv('SECRET_KEY') else 'INSECURE_STANDARD_KEY_SET_IN_ENV'
|
||||
|
||||
DEBUG = bool(int(os.getenv('DEBUG', True)))
|
||||
DEMO = bool(int(os.getenv('DEMO', False)))
|
||||
|
||||
SOCIAL_DEFAULT_ACCESS = bool(int(os.getenv('SOCIAL_DEFAULT_ACCESS', False)))
|
||||
SOCIAL_DEFAULT_GROUP = os.getenv('SOCIAL_DEFAULT_GROUP', 'guest')
|
||||
@@ -65,6 +64,17 @@ CRISPY_TEMPLATE_PACK = 'bootstrap4'
|
||||
DJANGO_TABLES2_TEMPLATE = 'cookbook/templates/generic/table_template.html'
|
||||
DJANGO_TABLES2_PAGE_RANGE = 8
|
||||
|
||||
HCAPTCHA_SITEKEY = os.getenv('HCAPTCHA_SITEKEY', '')
|
||||
HCAPTCHA_SECRET = os.getenv('HCAPTCHA_SECRET', '')
|
||||
|
||||
ACCOUNT_SIGNUP_FORM_CLASS = 'cookbook.forms.AllAuthSignupForm'
|
||||
|
||||
TERMS_URL = os.getenv('TERMS_URL', '')
|
||||
PRIVACY_URL = os.getenv('PRIVACY_URL', '')
|
||||
IMPRINT_URL = os.getenv('IMPRINT_URL', '')
|
||||
|
||||
HOSTED = bool(int(os.getenv('HOSTED', False)))
|
||||
|
||||
MESSAGE_TAGS = {
|
||||
messages.ERROR: 'danger'
|
||||
}
|
||||
@@ -82,6 +92,7 @@ INSTALLED_APPS = [
|
||||
'django.contrib.sites',
|
||||
'django.contrib.staticfiles',
|
||||
'django.contrib.postgres',
|
||||
'django_prometheus',
|
||||
'django_tables2',
|
||||
'corsheaders',
|
||||
'django_filters',
|
||||
@@ -92,6 +103,7 @@ INSTALLED_APPS = [
|
||||
'django_cleanup.apps.CleanupConfig',
|
||||
'webpack_loader',
|
||||
'django_js_reverse',
|
||||
'hcaptcha',
|
||||
'allauth',
|
||||
'allauth.account',
|
||||
'allauth.socialaccount',
|
||||
@@ -101,11 +113,18 @@ INSTALLED_APPS = [
|
||||
SOCIAL_PROVIDERS = os.getenv('SOCIAL_PROVIDERS').split(',') if os.getenv('SOCIAL_PROVIDERS') else []
|
||||
INSTALLED_APPS = INSTALLED_APPS + SOCIAL_PROVIDERS
|
||||
|
||||
ACCOUNT_SIGNUP_EMAIL_ENTER_TWICE = True
|
||||
ACCOUNT_MAX_EMAIL_ADDRESSES = 3
|
||||
ACCOUNT_EMAIL_REQUIRED = True
|
||||
ACCOUNT_EMAIL_CONFIRMATION_EXPIRE_DAYS = 90
|
||||
|
||||
SOCIALACCOUNT_PROVIDERS = ast.literal_eval(
|
||||
os.getenv('SOCIALACCOUNT_PROVIDERS') if os.getenv('SOCIALACCOUNT_PROVIDERS') else '{}')
|
||||
|
||||
ENABLE_SIGNUP = bool(int(os.getenv('ENABLE_SIGNUP', False)))
|
||||
|
||||
ENABLE_METRICS = bool(int(os.getenv('ENABLE_METRICS', False)))
|
||||
|
||||
MIDDLEWARE = [
|
||||
'corsheaders.middleware.CorsMiddleware',
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
@@ -120,6 +139,9 @@ MIDDLEWARE = [
|
||||
'cookbook.helper.scope_middleware.ScopeMiddleware',
|
||||
]
|
||||
|
||||
if ENABLE_METRICS:
|
||||
MIDDLEWARE += 'django_prometheus.middleware.PrometheusAfterMiddleware',
|
||||
|
||||
# Auth related settings
|
||||
AUTHENTICATION_BACKENDS = [
|
||||
'django.contrib.auth.backends.ModelBackend',
|
||||
@@ -180,6 +202,7 @@ TEMPLATES = [
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
'django.template.context_processors.media',
|
||||
'cookbook.helper.context_processors.context_settings',
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -301,12 +324,15 @@ STATIC_URL = os.getenv('STATIC_URL', '/static/')
|
||||
STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles")
|
||||
|
||||
if os.getenv('S3_ACCESS_KEY', ''):
|
||||
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
|
||||
DEFAULT_FILE_STORAGE = 'cookbook.helper.CustomStorageClass.CachedS3Boto3Storage'
|
||||
|
||||
AWS_ACCESS_KEY_ID = os.getenv('S3_ACCESS_KEY', '')
|
||||
AWS_SECRET_ACCESS_KEY = os.getenv('S3_SECRET_ACCESS_KEY', '')
|
||||
AWS_STORAGE_BUCKET_NAME = os.getenv('S3_BUCKET_NAME', '')
|
||||
AWS_QUERYSTRING_AUTH = bool(int(os.getenv('S3_QUERYSTRING_AUTH', True)))
|
||||
AWS_QUERYSTRING_EXPIRE = int(os.getenv('S3_QUERYSTRING_EXPIRE', 3600))
|
||||
AWS_S3_SIGNATURE_VERSION = os.getenv('S3_SIGNATURE_VERSION', 's3v4')
|
||||
AWS_S3_REGION_NAME = os.getenv('S3_REGION_NAME', None)
|
||||
|
||||
if os.getenv('S3_ENDPOINT_URL', ''):
|
||||
AWS_S3_ENDPOINT_URL = os.getenv('S3_ENDPOINT_URL', '')
|
||||
|
||||
@@ -15,6 +15,7 @@ Including another URLconf
|
||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||
"""
|
||||
from django.conf import settings
|
||||
from django.conf.urls import url
|
||||
from django.contrib import admin
|
||||
from django.urls import include, path, re_path
|
||||
from django.views.i18n import JavaScriptCatalog
|
||||
@@ -33,6 +34,9 @@ urlpatterns = [
|
||||
),
|
||||
]
|
||||
|
||||
if settings.ENABLE_METRICS:
|
||||
urlpatterns += url('', include('django_prometheus.urls')),
|
||||
|
||||
if settings.GUNICORN_MEDIA or settings.DEBUG:
|
||||
urlpatterns += re_path(r'^media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}),
|
||||
urlpatterns += re_path(r'^jsreverse.json$', reverse_views.urls_js, name='js_reverse'),
|
||||
|
||||
@@ -37,4 +37,6 @@ pytest==6.2.4
|
||||
pytest-django==4.3.0
|
||||
django-cors-headers==3.7.0
|
||||
django-storages==1.11.1
|
||||
boto3==1.17.84
|
||||
boto3==1.17.84
|
||||
django-prometheus==2.1.0
|
||||
django-hCaptcha==0.1.0
|
||||
@@ -23,7 +23,7 @@
|
||||
<loading-spinner></loading-spinner>
|
||||
<br/>
|
||||
<br/>
|
||||
<h5 style="text-align: center">{{ $t('import-running') }}</h5>
|
||||
<h5 style="text-align: center">{{ $t('Importing') }}...</h5>
|
||||
|
||||
</template>
|
||||
<template v-else>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="row">
|
||||
<div class="col" style="text-align: center">
|
||||
<i class="fas fa-spinner fa-spin fa-10x"></i>
|
||||
<img class="spinner-tandoor" alt="loading spinner" src="" style="height: 30vh"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user