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:
parent
e14851531d
commit
a6c46a7420
22 changed files with 715 additions and 61 deletions
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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}"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue