mirror of
https://github.com/TandoorRecipes/recipes.git
synced 2025-12-28 04:33:14 -05:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0cf89ca33c | ||
|
|
68ceada28b | ||
|
|
192ca44d89 | ||
|
|
3204aaf161 | ||
|
|
6cf6791fe6 | ||
|
|
6ebb18a932 | ||
|
|
7bde1bf44f | ||
|
|
3075ec066f | ||
|
|
3ddc3c3768 | ||
|
|
7b7b726ec5 | ||
|
|
83cf03a10b | ||
|
|
7b56719716 |
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: awalsh128/cache-apt-pkgs-action@v1.4.3
|
||||
with:
|
||||
packages: libsasl2-dev python3-dev libldap2-dev libssl-dev
|
||||
packages: libsasl2-dev python3-dev libxml2-dev libxmlsec1-dev libxslt-dev libxmlsec1-openssl libldap2-dev libssl-dev gcc musl-dev postgresql-dev zlib-dev jpeg-dev libwebp-dev openssl-dev libffi-dev cargo openldap-dev python3-dev xmlsec-dev xmlsec build-base g++ curl
|
||||
version: 1.0
|
||||
|
||||
# Setup python & dependencies
|
||||
|
||||
@@ -113,7 +113,7 @@ class RecipeShoppingEditor():
|
||||
if not self.servings:
|
||||
self.servings = getattr(self.mealplan, 'servings', None) or getattr(self.recipe, 'servings', 1.0)
|
||||
|
||||
self._shopping_list_recipe = ShoppingListRecipe.objects.create(recipe=self.recipe, mealplan=self.mealplan, servings=self.servings)
|
||||
self._shopping_list_recipe = ShoppingListRecipe.objects.create(recipe=self.recipe, mealplan=self.mealplan, servings=self.servings, space=self.space, created_by=self.created_by)
|
||||
|
||||
if ingredients:
|
||||
self._add_ingredients(ingredients=ingredients)
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
# Generated by Django 4.2.18 on 2025-03-14 10:50
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('cookbook', '0219_connectorconfig_supports_description_field'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='shoppinglistrecipe',
|
||||
name='created_by',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='shoppinglistrecipe',
|
||||
name='space',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,42 @@
|
||||
# Generated by Django 4.2.18 on 2025-03-14 10:50
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
from django.db.models import F, Count
|
||||
from django_scopes import scopes_disabled
|
||||
|
||||
|
||||
def add_space_and_owner_to_shopping_list_recipe(apps, schema_editor):
|
||||
print('migrating shopping list recipe space attribute, this might take a while ...')
|
||||
with scopes_disabled():
|
||||
ShoppingListRecipe = apps.get_model('cookbook', 'ShoppingListRecipe')
|
||||
|
||||
# delete all shopping list recipes that do not have entries as those are of no use anyway
|
||||
ShoppingListRecipe.objects.annotate(entry_count=Count('entries')).filter(entry_count__lte=0).delete()
|
||||
|
||||
shopping_list_recipes = ShoppingListRecipe.objects.all().prefetch_related('entries')
|
||||
update_list = []
|
||||
|
||||
for slr in shopping_list_recipes:
|
||||
if entry := slr.entries.first():
|
||||
if entry.space and entry.created_by:
|
||||
slr.space = entry.space
|
||||
slr.created_by = entry.created_by
|
||||
update_list.append(slr)
|
||||
else:
|
||||
print(slr, 'missing data on entry')
|
||||
else:
|
||||
print(slr, 'missing entry')
|
||||
|
||||
ShoppingListRecipe.objects.bulk_update(update_list, ['space', 'created_by'], batch_size=500)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('cookbook', '0220_shoppinglistrecipe_created_by_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(add_space_and_owner_to_shopping_list_recipe),
|
||||
]
|
||||
@@ -0,0 +1,26 @@
|
||||
# Generated by Django 4.2.18 on 2025-03-14 12:41
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('cookbook', '0221_migrate_shoppinglistrecipe_space_created_by'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='shoppinglistrecipe',
|
||||
name='created_by',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='shoppinglistrecipe',
|
||||
name='space',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
|
||||
),
|
||||
]
|
||||
@@ -1179,31 +1179,18 @@ class MealPlan(ExportModelOperationsMixin('meal_plan'), models.Model, Permission
|
||||
|
||||
class ShoppingListRecipe(ExportModelOperationsMixin('shopping_list_recipe'), models.Model, PermissionModelMixin):
|
||||
name = models.CharField(max_length=32, blank=True, default='')
|
||||
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE, null=True, blank=True) # TODO make required after old shoppinglist deprecated
|
||||
servings = models.DecimalField(default=1, max_digits=8, decimal_places=4)
|
||||
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE, null=True, blank=True)
|
||||
mealplan = models.ForeignKey(MealPlan, on_delete=models.CASCADE, null=True, blank=True)
|
||||
|
||||
objects = ScopedManager(space='recipe__space')
|
||||
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
space = models.ForeignKey(Space, on_delete=models.CASCADE)
|
||||
|
||||
@staticmethod
|
||||
def get_space_key():
|
||||
return 'recipe', 'space'
|
||||
|
||||
def get_space(self):
|
||||
return self.recipe.space
|
||||
objects = ScopedManager(space='space')
|
||||
|
||||
def __str__(self):
|
||||
return f'Shopping list recipe {self.id} - {self.recipe}'
|
||||
|
||||
def get_owner(self):
|
||||
try:
|
||||
if not self.entries.exists():
|
||||
return 'orphan'
|
||||
else:
|
||||
return getattr(self.entries.first(), 'created_by', None)
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
|
||||
class ShoppingListEntry(ExportModelOperationsMixin('shopping_list_entry'), models.Model, PermissionModelMixin):
|
||||
list_recipe = models.ForeignKey(ShoppingListRecipe, on_delete=models.CASCADE, null=True, blank=True, related_name='entries')
|
||||
|
||||
@@ -1093,6 +1093,7 @@ class ShoppingListRecipeSerializer(serializers.ModelSerializer):
|
||||
mealplan_from_date = serializers.ReadOnlyField(source='mealplan.from_date')
|
||||
mealplan_type = serializers.ReadOnlyField(source='mealplan.meal_type.name')
|
||||
servings = CustomDecimalField()
|
||||
created_by = UserSerializer(read_only=True)
|
||||
|
||||
def get_name(self, obj):
|
||||
if not isinstance(value := obj.servings, Decimal):
|
||||
@@ -1106,6 +1107,11 @@ class ShoppingListRecipeSerializer(serializers.ModelSerializer):
|
||||
or obj.recipe.name
|
||||
) + f' ({value:.2g})'
|
||||
|
||||
def create(self, validated_data):
|
||||
validated_data['space'] = self.context['request'].space
|
||||
validated_data['created_by'] = self.context['request'].user
|
||||
return super().create(validated_data)
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
# TODO remove once old shopping list
|
||||
if 'servings' in validated_data and self.context.get('view', None).__class__.__name__ != 'ShoppingListViewSet':
|
||||
@@ -1116,8 +1122,8 @@ class ShoppingListRecipeSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = ShoppingListRecipe
|
||||
fields = ('id', 'recipe_name', 'name', 'recipe', 'mealplan', 'servings', 'mealplan_note', 'mealplan_from_date',
|
||||
'mealplan_type')
|
||||
read_only_fields = ('id',)
|
||||
'mealplan_type', 'created_by')
|
||||
read_only_fields = ('id', 'created_by',)
|
||||
|
||||
|
||||
class ShoppingListEntrySerializer(WritableNestedModelSerializer):
|
||||
|
||||
@@ -12,7 +12,7 @@ DETAIL_URL = 'api:shoppinglistrecipe-detail'
|
||||
|
||||
@pytest.fixture()
|
||||
def obj_1(space_1, u1_s1, recipe_1_s1):
|
||||
r = ShoppingListRecipe.objects.create(recipe=recipe_1_s1, servings=1)
|
||||
r = ShoppingListRecipe.objects.create(recipe=recipe_1_s1, servings=1, space=space_1, created_by=auth.get_user(u1_s1))
|
||||
for ing in r.recipe.steps.first().ingredients.all():
|
||||
ShoppingListEntry.objects.create(list_recipe=r, ingredient=ing, food=ing.food, unit=ing.unit, amount=ing.amount, created_by=auth.get_user(u1_s1), space=space_1)
|
||||
return r
|
||||
|
||||
@@ -5,7 +5,20 @@ package by hhursev.
|
||||
|
||||
If you have the skills to add new sites or help resolve issues you are indirectly helping Tandoor.
|
||||
|
||||
## Unofficial mobile app
|
||||
## Unofficial mobile apps
|
||||
|
||||
### kitshn
|
||||
|
||||
Unofficial Tandoor recipes client
|
||||
|
||||
Maintained by [Aimo](https://github.com/aimok04/kitshn)
|
||||
|
||||
- Website: [https://kitshn.app/](https://kitshn.app/)
|
||||
- Appstores: [Apple](https://apps.apple.com/us/app/kitshn-for-tandoor/id6740168361), [Android](https://play.google.com/store/apps/details?id=de.kitshn.android)
|
||||
|
||||
|
||||
|
||||
### Untare (discontinued)
|
||||
|
||||
Maintained by [phantomate](https://github.com/phantomate/Untare)
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ drf-writable-nested==0.7.1
|
||||
django-oauth-toolkit==2.4.0
|
||||
django-debug-toolbar==4.3.0
|
||||
bleach==6.2.0
|
||||
gunicorn==22.0.0
|
||||
gunicorn==23.0.0
|
||||
lxml==5.3.1
|
||||
Markdown==3.5.1
|
||||
Pillow==11.1.0
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"warning_feature_beta": "Ця функція зараз в БЕТІ (тестується). Будь ласка, очікуйте помилок і можливих порушень і майбутньому (можлива втрата даних), коли користуєтесь цією функцією.",
|
||||
"warning_feature_beta": "Наразі ця функціональність знаходиться в стані BETA (тестування). Будь ласка, будьте готові до помилок і можливих змін у майбутньому (можливо, втрата даних, пов'язаних з функціональністю) під час використання.",
|
||||
"err_fetching_resource": "Виникла помилка при отриманні ресурсу!",
|
||||
"err_creating_resource": "Виникла помилка при створенні ресурсу!",
|
||||
"err_updating_resource": "Виникла помилка при оновленні ресурсу!",
|
||||
@@ -440,5 +440,6 @@
|
||||
"Default_Unit": "Одиниця замовчуванням",
|
||||
"recipe_property_info": "Ви також можете додати властивості до продуктів, щоб розрахувати їх автоматично на основі вашого рецепту!",
|
||||
"per_serving": "на порцію",
|
||||
"err_importing_recipe": "Виникла помилка при імпортуванні рецепту!"
|
||||
"err_importing_recipe": "Виникла помилка при імпортуванні рецепту!",
|
||||
"food_inherit_info": "Поля їжі, які повинні успадковуватися за змовчуванням."
|
||||
}
|
||||
|
||||
@@ -156,6 +156,15 @@
|
||||
"@babel/highlight" "^7.22.13"
|
||||
chalk "^2.4.2"
|
||||
|
||||
"@babel/code-frame@^7.26.2":
|
||||
version "7.26.2"
|
||||
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85"
|
||||
integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==
|
||||
dependencies:
|
||||
"@babel/helper-validator-identifier" "^7.25.9"
|
||||
js-tokens "^4.0.0"
|
||||
picocolors "^1.0.0"
|
||||
|
||||
"@babel/compat-data@^7.22.6", "@babel/compat-data@^7.22.9":
|
||||
version "7.22.9"
|
||||
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.9.tgz#71cdb00a1ce3a329ce4cbec3a44f9fef35669730"
|
||||
@@ -385,6 +394,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f"
|
||||
integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==
|
||||
|
||||
"@babel/helper-string-parser@^7.25.9":
|
||||
version "7.25.9"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz#1aabb72ee72ed35789b4bbcad3ca2862ce614e8c"
|
||||
integrity sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==
|
||||
|
||||
"@babel/helper-validator-identifier@^7.22.15", "@babel/helper-validator-identifier@^7.22.5":
|
||||
version "7.22.15"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.15.tgz#601fa28e4cc06786c18912dca138cec73b882044"
|
||||
@@ -395,6 +409,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0"
|
||||
integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==
|
||||
|
||||
"@babel/helper-validator-identifier@^7.25.9":
|
||||
version "7.25.9"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz#24b64e2c3ec7cd3b3c547729b8d16871f22cbdc7"
|
||||
integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==
|
||||
|
||||
"@babel/helper-validator-option@^7.22.15":
|
||||
version "7.22.15"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz#694c30dfa1d09a6534cdfcafbe56789d36aba040"
|
||||
@@ -410,13 +429,12 @@
|
||||
"@babel/types" "^7.22.17"
|
||||
|
||||
"@babel/helpers@^7.22.15":
|
||||
version "7.22.15"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.22.15.tgz#f09c3df31e86e3ea0b7ff7556d85cdebd47ea6f1"
|
||||
integrity sha512-7pAjK0aSdxOwR+CcYAqgWOGy5dcfvzsTIfFTb2odQqW47MDfv14UaJDY6eng8ylM2EaeKXdxaSWESbkmaQHTmw==
|
||||
version "7.26.10"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.26.10.tgz#6baea3cd62ec2d0c1068778d63cb1314f6637384"
|
||||
integrity sha512-UPYc3SauzZ3JGgj87GgZ89JVdC5dj0AoetR5Bw6wj4niittNyFh6+eOGonYvJ1ao6B8lEa3Q3klS7ADZ53bc5g==
|
||||
dependencies:
|
||||
"@babel/template" "^7.22.15"
|
||||
"@babel/traverse" "^7.22.15"
|
||||
"@babel/types" "^7.22.15"
|
||||
"@babel/template" "^7.26.9"
|
||||
"@babel/types" "^7.26.10"
|
||||
|
||||
"@babel/highlight@^7.22.13":
|
||||
version "7.22.13"
|
||||
@@ -437,6 +455,13 @@
|
||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.0.tgz#da950e622420bf96ca0d0f2909cdddac3acd8719"
|
||||
integrity sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==
|
||||
|
||||
"@babel/parser@^7.26.9":
|
||||
version "7.26.10"
|
||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.10.tgz#e9bdb82f14b97df6569b0b038edd436839c57749"
|
||||
integrity sha512-6aQR2zGE/QFi8JpDLjUZEPYOs7+mhKXm86VaKFiLP35JQwQb6bwUE+XbvkH0EptsYhbNBSUGaUBLKqxH1xSgsA==
|
||||
dependencies:
|
||||
"@babel/types" "^7.26.10"
|
||||
|
||||
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.22.15":
|
||||
version "7.22.15"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.22.15.tgz#02dc8a03f613ed5fdc29fb2f728397c78146c962"
|
||||
@@ -1199,9 +1224,9 @@
|
||||
integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==
|
||||
|
||||
"@babel/runtime@^7.11.2", "@babel/runtime@^7.12.13", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4":
|
||||
version "7.22.15"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.15.tgz#38f46494ccf6cf020bd4eed7124b425e83e523b8"
|
||||
integrity sha512-T0O+aa+4w0u06iNmapipJXMV4HoUir03hpx3/YqXXhu9xim3w+dVphjFWl1OH8NbZHw5Lbm9k45drDkgq2VNNA==
|
||||
version "7.26.10"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.10.tgz#a07b4d8fa27af131a633d7b3524db803eb4764c2"
|
||||
integrity sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.14.0"
|
||||
|
||||
@@ -1214,7 +1239,16 @@
|
||||
"@babel/parser" "^7.22.15"
|
||||
"@babel/types" "^7.22.15"
|
||||
|
||||
"@babel/traverse@^7.22.15", "@babel/traverse@^7.22.17", "@babel/traverse@^7.22.5", "@babel/traverse@^7.7.0":
|
||||
"@babel/template@^7.26.9":
|
||||
version "7.26.9"
|
||||
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.26.9.tgz#4577ad3ddf43d194528cff4e1fa6b232fa609bb2"
|
||||
integrity sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.26.2"
|
||||
"@babel/parser" "^7.26.9"
|
||||
"@babel/types" "^7.26.9"
|
||||
|
||||
"@babel/traverse@^7.22.17", "@babel/traverse@^7.22.5", "@babel/traverse@^7.7.0":
|
||||
version "7.23.2"
|
||||
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.2.tgz#329c7a06735e144a506bdb2cad0268b7f46f4ad8"
|
||||
integrity sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==
|
||||
@@ -1248,6 +1282,14 @@
|
||||
"@babel/helper-validator-identifier" "^7.22.20"
|
||||
to-fast-properties "^2.0.0"
|
||||
|
||||
"@babel/types@^7.26.10", "@babel/types@^7.26.9":
|
||||
version "7.26.10"
|
||||
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.10.tgz#396382f6335bd4feb65741eacfc808218f859259"
|
||||
integrity sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ==
|
||||
dependencies:
|
||||
"@babel/helper-string-parser" "^7.25.9"
|
||||
"@babel/helper-validator-identifier" "^7.25.9"
|
||||
|
||||
"@codemirror/autocomplete@^6.0.0", "@codemirror/autocomplete@^6.13.0", "@codemirror/autocomplete@^6.7.1":
|
||||
version "6.13.0"
|
||||
resolved "https://registry.yarnpkg.com/@codemirror/autocomplete/-/autocomplete-6.13.0.tgz#fa7df3b2809863df0da4556f72ac4263ea4d7adb"
|
||||
|
||||
Reference in New Issue
Block a user