Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions backend/app/api/register_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
api.household.register_blueprint(
api.expenseHousehold, url_prefix="/<int:household_id>/expense"
)
api.household.register_blueprint(
api.loyaltyCardHousehold, url_prefix="/<int:household_id>/loyalty-card"
)
api.household.register_blueprint(
api.itemHousehold, url_prefix="/<int:household_id>/item"
)
Expand All @@ -34,6 +37,7 @@
apiv1.register_blueprint(api.household, url_prefix="/household")
apiv1.register_blueprint(api.category, url_prefix="/category")
apiv1.register_blueprint(api.expense, url_prefix="/expense")
apiv1.register_blueprint(api.loyaltyCard, url_prefix="/loyalty-card")
apiv1.register_blueprint(api.item, url_prefix="/item")
apiv1.register_blueprint(api.onboarding, url_prefix="/onboarding")
apiv1.register_blueprint(api.recipe, url_prefix="/recipe")
Expand Down
7 changes: 5 additions & 2 deletions backend/app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@
from flask_bcrypt import Bcrypt
from flask_jwt_extended import JWTManager
from flask_apscheduler import APScheduler
import sqlite_icu
try:
import sqlite_icu
except ImportError:
sqlite_icu = None
import os


Expand Down Expand Up @@ -285,7 +288,7 @@ def __call__(self, *args: object, **kwargs: object) -> object:


# Load ICU extension for sqlite
if DB_URL.drivername == "sqlite":
if DB_URL.drivername == "sqlite" and sqlite_icu is not None:

def load_extension(conn, unused):
conn.enable_load_extension(True)
Expand Down
1 change: 1 addition & 0 deletions backend/app/controller/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@
from .health_controller import health
from .analytics import *
from .report import *
from .loyalty_card import loyaltyCard, loyaltyCardHousehold
5 changes: 5 additions & 0 deletions backend/app/controller/exportimport/import_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
importRecipe,
importExpense,
importShoppinglist,
importLoyaltyCard,
)
from app.service.recalculate_balances import recalculateBalances
from .schemas import ImportSchema
Expand Down Expand Up @@ -49,5 +50,9 @@ def importData(args, household_id):
for shoppinglist in args["shoppinglists"]:
importShoppinglist(household, shoppinglist)

if "loyalty_cards" in args:
for card in args["loyalty_cards"]:
importLoyaltyCard(household, card)

app.logger.info(f"Import took: {(time.time() - t0):.3f}s")
return jsonify({"msg": "DONE"})
11 changes: 11 additions & 0 deletions backend/app/controller/exportimport/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,20 @@ class Category(Schema):
photo = fields.String(allow_none=True)
category = fields.Nested(Category)

class LoyaltyCard(Schema):
class Meta:
unknown = EXCLUDE

name = fields.String(required=True, validate=lambda a: a and not a.isspace())
barcode_data = fields.String(allow_none=True)
barcode_type = fields.String(allow_none=True)
description = fields.String(allow_none=True)
color = fields.Integer(allow_none=True)

items = fields.List(fields.Nested(Item))
recipes = fields.List(fields.Nested(Recipe))
recipe_overwrite = fields.Boolean()
expenses = fields.List(fields.Nested(Expense))
loyalty_cards = fields.List(fields.Nested(LoyaltyCard))
member = fields.List(fields.String())
shoppinglists = fields.List(fields.String())
4 changes: 4 additions & 0 deletions backend/app/controller/household/household_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ def addHousehold(args):
household.planner_feature = args["planner_feature"]
if "expenses_feature" in args:
household.expenses_feature = args["expenses_feature"]
if "loyalty_cards_feature" in args:
household.loyalty_cards_feature = args["loyalty_cards_feature"]
if "view_ordering" in args:
household.view_ordering = args["view_ordering"]
if "link" in args:
Expand Down Expand Up @@ -111,6 +113,8 @@ def updateHousehold(args, household_id):
household.planner_feature = args["planner_feature"]
if "expenses_feature" in args:
household.expenses_feature = args["expenses_feature"]
if "loyalty_cards_feature" in args:
household.loyalty_cards_feature = args["loyalty_cards_feature"]
if "view_ordering" in args:
household.view_ordering = args["view_ordering"]
if "link" in args:
Expand Down
2 changes: 2 additions & 0 deletions backend/app/controller/household/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class Meta:
language = fields.String()
planner_feature = fields.Boolean()
expenses_feature = fields.Boolean()
loyalty_cards_feature = fields.Boolean()
view_ordering = fields.List(fields.String)
member = fields.List(fields.Integer)

Expand All @@ -27,6 +28,7 @@ class Meta:
language = fields.String()
planner_feature = fields.Boolean()
expenses_feature = fields.Boolean()
loyalty_cards_feature = fields.Boolean()
view_ordering = fields.List(fields.String)


Expand Down
2 changes: 2 additions & 0 deletions backend/app/controller/loyalty_card/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from .loyalty_card_controller import loyaltyCard, loyaltyCardHousehold

85 changes: 85 additions & 0 deletions backend/app/controller/loyalty_card/loyalty_card_controller.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
from app.errors import NotFoundRequest
from flask import jsonify, Blueprint
from flask_jwt_extended import jwt_required
from app.helpers import validate_args, authorize_household
from app.models import LoyaltyCard
from .schemas import AddLoyaltyCard, UpdateLoyaltyCard

loyaltyCard = Blueprint("loyalty_card", __name__)
loyaltyCardHousehold = Blueprint("loyalty_card", __name__)


@loyaltyCardHousehold.route("", methods=["GET"])
@jwt_required()
@authorize_household()
def getAllLoyaltyCards(household_id):
return jsonify(
[e.obj_to_dict() for e in LoyaltyCard.find_by_household(household_id)]
)


@loyaltyCard.route("/<int:id>", methods=["GET"])
@jwt_required()
def getLoyaltyCardById(id):
loyalty_card = LoyaltyCard.find_by_id(id)
if not loyalty_card:
raise NotFoundRequest()
loyalty_card.checkAuthorized()
return jsonify(loyalty_card.obj_to_full_dict())


@loyaltyCardHousehold.route("", methods=["POST"])
@jwt_required()
@authorize_household()
@validate_args(AddLoyaltyCard)
def addLoyaltyCard(args, household_id):
loyalty_card = LoyaltyCard()
loyalty_card.name = args["name"]
loyalty_card.household_id = household_id
if "barcode_type" in args:
loyalty_card.barcode_type = args["barcode_type"]
if "barcode_data" in args:
loyalty_card.barcode_data = args["barcode_data"]
if "description" in args:
loyalty_card.description = args["description"]
if "color" in args:
loyalty_card.color = args["color"]
loyalty_card.save()
return jsonify(loyalty_card.obj_to_dict())


@loyaltyCard.route("/<int:id>", methods=["POST"])
@jwt_required()
@validate_args(UpdateLoyaltyCard)
def updateLoyaltyCard(args, id):
loyalty_card = LoyaltyCard.find_by_id(id)
if not loyalty_card:
raise NotFoundRequest()
loyalty_card.checkAuthorized()

if "name" in args:
loyalty_card.name = args["name"]
if "barcode_type" in args:
loyalty_card.barcode_type = args["barcode_type"]
if "barcode_data" in args:
loyalty_card.barcode_data = args["barcode_data"]
if "description" in args:
loyalty_card.description = args["description"]
if "color" in args:
loyalty_card.color = args["color"]

loyalty_card.save()
return jsonify(loyalty_card.obj_to_dict())


@loyaltyCard.route("/<int:id>", methods=["DELETE"])
@jwt_required()
def deleteLoyaltyCardById(id):
loyalty_card = LoyaltyCard.find_by_id(id)
if not loyalty_card:
raise NotFoundRequest()
loyalty_card.checkAuthorized()

loyalty_card.delete()
return jsonify({"msg": "DONE"})

19 changes: 19 additions & 0 deletions backend/app/controller/loyalty_card/schemas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from marshmallow import fields, Schema


class AddLoyaltyCard(Schema):
name = fields.String(required=True, validate=lambda a: a and not a.isspace())
barcode_type = fields.String(allow_none=True)
barcode_data = fields.String(allow_none=True)
description = fields.String(allow_none=True)
color = fields.Integer(validate=lambda i: i >= 0, allow_none=True)


class UpdateLoyaltyCard(Schema):
name = fields.String(validate=lambda a: a and not a.isspace())
barcode_type = fields.String(allow_none=True)
barcode_data = fields.String(allow_none=True)
description = fields.String(allow_none=True)
color = fields.Integer(validate=lambda i: i >= 0, allow_none=True)


1 change: 1 addition & 0 deletions backend/app/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@
from .challenge_password_reset import ChallengePasswordReset
from .oidc import OIDCLink, OIDCRequest
from .report import Report
from .loyalty_card import LoyaltyCard
1 change: 0 additions & 1 deletion backend/app/models/file.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ class File(Model, DbModelAuthorizeMixin):
uselist=False,
),
)

def delete(self):
"""
Delete this instance of model from db
Expand Down
12 changes: 12 additions & 0 deletions backend/app/models/household.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
ExpenseCategory,
User,
File,
LoyaltyCard,
)
from app.helpers.db_model_base import DbModelBase

Expand All @@ -34,6 +35,9 @@ class Household(Model):
expenses_feature: Mapped[bool] = db.Column(
db.Boolean(), nullable=False, default=True
)
loyalty_cards_feature: Mapped[bool] = db.Column(
db.Boolean(), nullable=False, default=True
)
description: Mapped[str | None] = db.Column(db.String())
link: Mapped[str | None] = db.Column(db.String(255))
# For households that have verified their authenticity
Expand Down Expand Up @@ -97,6 +101,14 @@ class Household(Model):
cascade="all, delete-orphan",
),
)
loyaltyCards: Mapped[List["LoyaltyCard"]] = cast(
Mapped[List["LoyaltyCard"]],
db.relationship(
"LoyaltyCard",
back_populates="household",
cascade="all, delete-orphan",
),
)
member: Mapped[List["HouseholdMember"]] = cast(
Mapped[List["HouseholdMember"]],
db.relationship(
Expand Down
46 changes: 46 additions & 0 deletions backend/app/models/loyalty_card.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from typing import Any, Self, TYPE_CHECKING, cast
from app import db
from app.helpers import DbModelAuthorizeMixin
from sqlalchemy.orm import Mapped

Model = db.Model
if TYPE_CHECKING:
from app.models import Household
from app.helpers.db_model_base import DbModelBase

Model = DbModelBase


class LoyaltyCard(Model, DbModelAuthorizeMixin):
__tablename__ = "loyalty_card"

id: Mapped[int] = db.Column(db.Integer, primary_key=True)
name: Mapped[str] = db.Column(db.String(128), nullable=False)
barcode_type: Mapped[str | None] = db.Column(db.String(32))
barcode_data: Mapped[str | None] = db.Column(db.String(256))
description: Mapped[str | None] = db.Column(db.String(512))
color: Mapped[int | None] = db.Column(db.Integer)
household_id: Mapped[int] = db.Column(
db.Integer, db.ForeignKey("household.id"), nullable=False, index=True
)

household: Mapped["Household"] = cast(
Mapped["Household"],
db.relationship(
"Household",
back_populates="loyaltyCards",
uselist=False,
),
)

def obj_to_full_dict(self) -> dict[str, Any]:
return self.obj_to_dict()

@classmethod
def find_by_id(cls, id: int) -> Self | None:
return cls.query.filter(cls.id == id).first()

@classmethod
def find_by_household(cls, household_id: int) -> list[Self]:
return cls.query.filter(cls.household_id == household_id).order_by(cls.name).all()

1 change: 1 addition & 0 deletions backend/app/service/importServices/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
from .import_expense import importExpense
from .import_shoppinglist import importShoppinglist
from .import_item import importItem
from .import_loyalty_card import importLoyaltyCard
26 changes: 26 additions & 0 deletions backend/app/service/importServices/import_loyalty_card.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from app.models import LoyaltyCard


def importLoyaltyCard(household, card_data):
card = LoyaltyCard.query.filter_by(
household_id=household.id, name=card_data["name"]
).first()

if not card:
card = LoyaltyCard()
card.household_id = household.id
card.name = card_data["name"]

if "barcode_data" in card_data:
card.barcode_data = card_data["barcode_data"]

if "barcode_type" in card_data:
card.barcode_type = card_data["barcode_type"]

if "description" in card_data:
card.description = card_data["description"]

if "color" in card_data:
card.color = card_data["color"]

card.save()
Loading
Loading