1
0
Fork 0
mirror of https://github.com/mealie-recipes/mealie.git synced 2025-08-02 20:15:24 +02:00

Feature: Shopping List Label Section Improvements (#2090)

* added backend for shopping list label config

* updated codegen

* refactored shopping list ops to service
removed unique contraint
removed label settings from main route/schema
added new route for label settings

* codegen

* made sure label settings output in position order

* implemented submenu for label order drag and drop

* removed redundant label and tweaked formatting

* added view by label to user preferences

* made items draggable within each label section

* moved reorder labels to its own button

* made dialog scrollable

* fixed broken model

* refactored labels to use a service
moved shopping list label logic to service
modified label seeder to use service

* added tests

* fix for first label missing the tag icon

* fixed wrong mapped type

* added statement to create existing relationships

* fix restore test, maybe
This commit is contained in:
Michael Genson 2023-02-21 21:58:41 -06:00 committed by GitHub
parent e14851531d
commit a6c46a7420
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 715 additions and 61 deletions

View file

@ -0,0 +1,191 @@
import random
from fastapi.testclient import TestClient
from mealie.repos.repository_factory import AllRepositories
from mealie.schema.group.group_shopping_list import ShoppingListOut
from mealie.schema.labels.multi_purpose_label import MultiPurposeLabelOut
from mealie.services.seeder.seeder_service import SeederService
from tests.utils import api_routes, jsonify
from tests.utils.factories import random_int, random_string
from tests.utils.fixture_schemas import TestUser
def create_labels(api_client: TestClient, unique_user: TestUser, count: int = 10) -> list[MultiPurposeLabelOut]:
labels: list[MultiPurposeLabelOut] = []
for _ in range(count):
response = api_client.post(api_routes.groups_labels, json={"name": random_string()}, headers=unique_user.token)
labels.append(MultiPurposeLabelOut.parse_obj(response.json()))
return labels
def test_new_list_creates_list_labels(api_client: TestClient, unique_user: TestUser):
labels = create_labels(api_client, unique_user)
response = api_client.post(
api_routes.groups_shopping_lists, json={"name": random_string()}, headers=unique_user.token
)
new_list = ShoppingListOut.parse_obj(response.json())
assert len(new_list.label_settings) == len(labels)
label_settings_label_ids = [setting.label_id for setting in new_list.label_settings]
for label in labels:
assert label.id in label_settings_label_ids
def test_new_label_creates_list_labels(api_client: TestClient, unique_user: TestUser):
# create a list with some labels
create_labels(api_client, unique_user)
response = api_client.post(
api_routes.groups_shopping_lists, json={"name": random_string()}, headers=unique_user.token
)
new_list = ShoppingListOut.parse_obj(response.json())
existing_label_settings = new_list.label_settings
# create more labels and make sure they were added to the list's label settings
new_labels = create_labels(api_client, unique_user)
response = api_client.get(api_routes.groups_shopping_lists_item_id(new_list.id), headers=unique_user.token)
updated_list = ShoppingListOut.parse_obj(response.json())
updated_label_settings = updated_list.label_settings
assert len(updated_label_settings) == len(existing_label_settings) + len(new_labels)
label_settings_ids = [setting.id for setting in updated_list.label_settings]
for label_setting in existing_label_settings:
assert label_setting.id in label_settings_ids
label_settings_label_ids = [setting.label_id for setting in updated_list.label_settings]
for label in new_labels:
assert label.id in label_settings_label_ids
def test_seed_label_creates_list_labels(database: AllRepositories, api_client: TestClient, unique_user: TestUser):
CREATED_LABELS = 21
# create a list with some labels
create_labels(api_client, unique_user)
response = api_client.post(
api_routes.groups_shopping_lists, json={"name": random_string()}, headers=unique_user.token
)
new_list = ShoppingListOut.parse_obj(response.json())
existing_label_settings = new_list.label_settings
# seed labels and make sure they were added to the list's label settings
group = database.groups.get_one(unique_user.group_id)
seeder = SeederService(database, None, group) # type: ignore
seeder.seed_labels("en-US")
response = api_client.get(api_routes.groups_shopping_lists_item_id(new_list.id), headers=unique_user.token)
updated_list = ShoppingListOut.parse_obj(response.json())
updated_label_settings = updated_list.label_settings
assert len(updated_label_settings) == len(existing_label_settings) + CREATED_LABELS
label_settings_ids = [setting.id for setting in updated_list.label_settings]
for label_setting in existing_label_settings:
assert label_setting.id in label_settings_ids
def test_delete_label_deletes_list_labels(api_client: TestClient, unique_user: TestUser):
new_labels = create_labels(api_client, unique_user)
response = api_client.post(
api_routes.groups_shopping_lists, json={"name": random_string()}, headers=unique_user.token
)
new_list = ShoppingListOut.parse_obj(response.json())
existing_label_settings = new_list.label_settings
label_to_delete = random.choice(new_labels)
api_client.delete(api_routes.groups_labels_item_id(label_to_delete.id), headers=unique_user.token)
response = api_client.get(api_routes.groups_shopping_lists_item_id(new_list.id), headers=unique_user.token)
updated_list = ShoppingListOut.parse_obj(response.json())
assert len(updated_list.label_settings) == len(existing_label_settings) - 1
label_settings_label_ids = [setting.label_id for setting in updated_list.label_settings]
for label in new_labels:
if label.id == label_to_delete.id:
assert label.id not in label_settings_label_ids
else:
assert label.id in label_settings_label_ids
def test_update_list_doesnt_change_list_labels(api_client: TestClient, unique_user: TestUser):
create_labels(api_client, unique_user)
original_name = random_string()
updated_name = random_string()
response = api_client.post(
api_routes.groups_shopping_lists, json={"name": original_name}, headers=unique_user.token
)
new_list = ShoppingListOut.parse_obj(response.json())
assert new_list.name == original_name
assert new_list.label_settings
updated_list_data = new_list.dict()
updated_list_data.pop("created_at", None)
updated_list_data.pop("update_at", None)
updated_list_data["name"] = updated_name
updated_list_data["label_settings"][0]["position"] = random_int(999, 9999)
response = api_client.put(
api_routes.groups_shopping_lists_item_id(new_list.id),
json=jsonify(updated_list_data),
headers=unique_user.token,
)
updated_list = ShoppingListOut.parse_obj(response.json())
assert updated_list.name == updated_name
assert updated_list.label_settings == new_list.label_settings
def test_update_list_labels(api_client: TestClient, unique_user: TestUser):
create_labels(api_client, unique_user)
response = api_client.post(
api_routes.groups_shopping_lists, json={"name": random_string()}, headers=unique_user.token
)
new_list = ShoppingListOut.parse_obj(response.json())
changed_setting = random.choice(new_list.label_settings)
changed_setting.position = random_int(999, 9999)
response = api_client.put(
api_routes.groups_shopping_lists_item_id_label_settings(new_list.id),
json=jsonify(new_list.label_settings),
headers=unique_user.token,
)
updated_list = ShoppingListOut.parse_obj(response.json())
original_settings_by_id = {setting.id: setting for setting in new_list.label_settings}
for setting in updated_list.label_settings:
assert setting.id in original_settings_by_id
assert original_settings_by_id[setting.id].shopping_list_id == setting.shopping_list_id
assert original_settings_by_id[setting.id].label_id == setting.label_id
if setting.id == changed_setting.id:
assert setting.position == changed_setting.position
else:
assert original_settings_by_id[setting.id].position == setting.position
def test_list_label_order(api_client: TestClient, unique_user: TestUser):
response = api_client.post(
api_routes.groups_shopping_lists, json={"name": random_string()}, headers=unique_user.token
)
new_list = ShoppingListOut.parse_obj(response.json())
for i, setting in enumerate(new_list.label_settings):
if not i:
continue
assert setting.position > new_list.label_settings[i - 1].position
random.shuffle(new_list.label_settings)
response = api_client.put(
api_routes.groups_shopping_lists_item_id_label_settings(new_list.id),
json=jsonify(new_list.label_settings),
headers=unique_user.token,
)
updated_list = ShoppingListOut.parse_obj(response.json())
for i, setting in enumerate(updated_list.label_settings):
if not i:
continue
assert setting.position > updated_list.label_settings[i - 1].position

View file

@ -11,7 +11,7 @@ from mealie.services.backups_v2.backup_v2 import BackupV2
def dict_sorter(d: dict) -> Any:
possible_keys = {"created_at", "id"}
return next((d[key] for key in possible_keys if key in d), 1)
return next((d[key] for key in possible_keys if key in d and d[key]), 1)
# For Future Use

View file

@ -276,6 +276,11 @@ def groups_shopping_lists_item_id(item_id):
return f"{prefix}/groups/shopping/lists/{item_id}"
def groups_shopping_lists_item_id_label_settings(item_id):
"""`/api/groups/shopping/lists/{item_id}/label-settings`"""
return f"{prefix}/groups/shopping/lists/{item_id}/label-settings"
def groups_shopping_lists_item_id_recipe_recipe_id(item_id, recipe_id):
"""`/api/groups/shopping/lists/{item_id}/recipe/{recipe_id}`"""
return f"{prefix}/groups/shopping/lists/{item_id}/recipe/{recipe_id}"