diff --git a/migrations/versions/a08fba8a0180_.py b/migrations/versions/a08fba8a0180_.py new file mode 100644 index 0000000000..ac1c62450b --- /dev/null +++ b/migrations/versions/a08fba8a0180_.py @@ -0,0 +1,78 @@ +"""empty message + +Revision ID: a08fba8a0180 +Revises: +Create Date: 2025-03-05 14:44:28.016416 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'a08fba8a0180' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('accessories', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=100), nullable=False), + sa.Column('brand', sa.String(length=100), nullable=False), + sa.Column('description', sa.Text(), nullable=True), + sa.Column('animal_type', sa.String(length=50), nullable=False), + sa.Column('pathologies', sa.Text(), nullable=True), + sa.Column('price', sa.Float(), nullable=False), + sa.Column('url', sa.String(length=255), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('foods', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=100), nullable=False), + sa.Column('brand', sa.String(length=100), nullable=False), + sa.Column('description', sa.Text(), nullable=True), + sa.Column('ingredients', sa.Text(), nullable=False), + sa.Column('weight', sa.Float(), nullable=False), + sa.Column('price', sa.Float(), nullable=False), + sa.Column('age', sa.String(), nullable=False), + sa.Column('animal_type', sa.String(length=50), nullable=False), + sa.Column('size', sa.String(length=30), nullable=True), + sa.Column('pathologies', sa.Text(), nullable=True), + sa.Column('url', sa.String(length=255), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('user', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=80), nullable=False), + sa.Column('email', sa.String(length=120), nullable=False), + sa.Column('password', sa.String(length=80), nullable=False), + sa.Column('is_active', sa.Boolean(), nullable=False), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('email') + ) + op.create_table('pet', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=100), nullable=False), + sa.Column('size', sa.String(length=100), nullable=True), + sa.Column('breed', sa.String(length=100), nullable=True), + sa.Column('age', sa.String(), nullable=False), + sa.Column('animal_type', sa.String(), nullable=False), + sa.Column('pathologies', sa.Text(), nullable=False), + sa.Column('user_id', sa.Integer(), nullable=True), + sa.Column('url', sa.String(length=255), nullable=True), + sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), + sa.PrimaryKeyConstraint('id') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('pet') + op.drop_table('user') + op.drop_table('foods') + op.drop_table('accessories') + # ### end Alembic commands ### diff --git a/requirements.txt b/requirements.txt index 4eac45f4f8..39d8350c51 100644 --- a/requirements.txt +++ b/requirements.txt @@ -24,3 +24,4 @@ sqlalchemy==1.3.23 urllib3==1.26.3; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4' werkzeug==1.0.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' wtforms==2.3.3 + diff --git a/requirements.txt.save b/requirements.txt.save new file mode 100644 index 0000000000..5022733cbd --- /dev/null +++ b/requirements.txt.save @@ -0,0 +1,26 @@ +-i https://pypi.org/simple +alembic==1.5.4; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5' +certifi==2020.12.5 +click==7.1.2; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' +cloudinary==1.24.0 +flask==1.1.2 +flask-admin==1.5.7 +flask-cors==3.0.10 +flask-migrate==2.6.0 +flask-sqlalchemy==2.4.4 +flask-swagger==0.2.14 +gunicorn==20.0.4 +itsdangerous==1.1.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' +jinja2==2.11.3; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' +mako==1.1.4; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' +markupsafe==1.1.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' +psycopg2-binary==2.8.6 +python-dateutil==2.8.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' +python-dotenv==0.15.0 +python-editor==1.0.4 +pyyaml==5.4.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5' +six==1.15.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' +sqlalchemy==1.3.23 +urllib3==1.26.3; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4' +werkzeug==1.0.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' +wtforms==2. diff --git a/src/api/admin.py b/src/api/admin.py index 3eecb64140..3d28afa51e 100644 --- a/src/api/admin.py +++ b/src/api/admin.py @@ -1,7 +1,7 @@ import os from flask_admin import Admin -from .models import db, User +from .models import db, User, Food, Accessories, Pet from flask_admin.contrib.sqla import ModelView def setup_admin(app): @@ -12,6 +12,8 @@ def setup_admin(app): # Add your models here, for example this is how we add a the User model to the admin admin.add_view(ModelView(User, db.session)) - + admin.add_view(ModelView(Food, db.session)) + admin.add_view(ModelView(Accessories, db.session)) + admin.add_view(ModelView(Pet, db.session)) # You can duplicate that line to add mew models # admin.add_view(ModelView(YourModelName, db.session)) \ No newline at end of file diff --git a/src/api/commands.py b/src/api/commands.py index 19806164d3..35837a3a4e 100644 --- a/src/api/commands.py +++ b/src/api/commands.py @@ -1,6 +1,6 @@ import click -from api.models import db, User +from api.models import db, User, Food, Accessories, Pet """ In this file, you can add as many commands as you want using the @app.cli.command decorator @@ -20,8 +20,11 @@ def insert_test_users(count): print("Creating test users") for x in range(1, int(count) + 1): user = User() + user.name = "name"+ str(x) user.email = "test_user" + str(x) + "@test.com" user.password = "123456" + user.address = "asd" + user.phone = 12324 user.is_active = True db.session.add(user) db.session.commit() @@ -29,6 +32,133 @@ def insert_test_users(count): print("All test users created") - @app.cli.command("insert-test-data") - def insert_test_data(): - pass \ No newline at end of file + @app.cli.command("insert_data_catfood") + def insert_data_catfood(): + catfood = Food() + catfood.name = "KA AYURVEDA Only Fish Gatos Sin Gluten" + catfood.brand="Feral" + catfood.description = "asd" + catfood.ingredients = "ads" + catfood.weight = 1. + catfood.price = 1. + catfood.animal_type = "asd" + catfood.age = "sd" + catfood.pathologies = "asd" + db.session.add(catfood) + db.session.commit() + + + + + @app.cli.command("insert_data_dogfood") + def insert_data_food(): + dogfood = Food() + dogfood.name = "Special Care hepatic/renal" + dogfood.brand="nfnatcane" + dogfood.description = "asd" + dogfood.ingredients = "ads" + dogfood.price = 1. + dogfood.pathologies = "renal" + dogfood.animal_type = "perro" + dogfood.age = "senior" + dogfood.size = "medium" + dogfood.weight = 1. + db.session.add(dogfood) + db.session.commit() + + dogfood = Food() + dogfood.name = "CARE DIGESTIVE (DOG)" + dogfood.brand="ownat" + dogfood.description = "asd" + dogfood.ingredients = "ads" + dogfood.price = 1. + dogfood.pathologies = "renal" + dogfood.animal_type = "perro" + dogfood.age = "senior" + dogfood.size = "medium" + dogfood.weight = 1. + db.session.add(dogfood) + db.session.commit() + + + + @app.cli.command("insert_data_exoticfood") + def insert_data_exoticfood(): + exoticfood = Food() + exoticfood.name = "critical care" + exoticfood.brand="oxbow" + exoticfood.description = "asd" + exoticfood.ingredients = "ads" + exoticfood.price = 1. + exoticfood.pathologies = "asd" + exoticfood.animal_type = "asd" + exoticfood.age = "asd" + exoticfood.weight = 1. + db.session.add(exoticfood) + db.session.commit() + + + + @app.cli.command("insert_data_accessories") + def insert_data_accessories(): + accessories= Accessories() + accessories.name = "critical care" + accessories.brand="oxbow" + accessories.description = "asd" + accessories.price = 1. + accessories.animal_type = "" + accessories.pathologies = "" + accessories.url = "" + db.session.add(accessories) + db.session.commit() + + + @app.cli.command("insert_data_pet") + def insert_data_pet(): + pet= Pet() + pet.name = "asd" + pet.size="" + pet.breed= "asd" + pet.age= "cachorro" + pet.animal_type = "gato" + pet.pathologies="obesidad, diabetes" + pet.user_id = 1 + db.session.add(pet) + db.session.commit() + + pet= Pet() + pet.name = "recoleto" + pet.size="medium" + pet.breed= "asd" + pet.age="senior" + pet.animal_type = "perro" + pet.pathologies="renal" + pet.user_id = 1 + db.session.add(pet) + db.session.commit() + + pet= Pet() + pet.name = "asd" + pet.size="oxbow" + pet.breed= "asd" + pet.age= "2" + pet.animal_type = "cobaya" + pet.pathologies="escorbuto" + db.session.add(pet) + db.session.commit() + + pet= Pet() + pet.name = "asd" + pet.size="oxbow" + pet.breed= "asd" + pet.age= "60" + pet.animal_type = "loro" + pet.pathologies="" + db.session.add(pet) + db.session.commit() + + + + + + diff --git a/src/api/models.py b/src/api/models.py index dccd8421ee..24597a509c 100644 --- a/src/api/models.py +++ b/src/api/models.py @@ -1,9 +1,17 @@ + from flask_sqlalchemy import SQLAlchemy +from sqlalchemy import ForeignKey + + + + + db = SQLAlchemy() class User(db.Model): id = db.Column(db.Integer, primary_key=True) + name= db.Column(db.String(80), nullable=False) email = db.Column(db.String(120), unique=True, nullable=False) password = db.Column(db.String(80), unique=False, nullable=False) is_active = db.Column(db.Boolean(), unique=False, nullable=False) @@ -15,5 +23,166 @@ def serialize(self): return { "id": self.id, "email": self.email, + "name": self.name, + "is_active": self.is_active # do not serialize the password, its a security breach - } \ No newline at end of file + } + + +class Food(db.Model): + __tablename__ = 'foods' + + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(100), nullable=False) + brand = db.Column(db.String(100), nullable=False) + description = db.Column(db.Text, nullable=True) + ingredients = db.Column(db.Text, nullable=False) + weight = db.Column(db.Float, nullable=False) + price = db.Column(db.Float, nullable=False) + age = db.Column(db.String, nullable=False) + animal_type = db.Column(db.String(50), nullable=False) + size = db.Column(db.String(30), nullable=True) + pathologies = db.Column(db.Text, nullable=True) + # is_hypoallergenic = db.Column(db.Boolean, default=False) + # is_gluten_free = db.Column(db.Boolean, default=False) + # protein_source = db.Column(db.String(100), nullable=False) + # fat_content = db.Column(db.Float, nullable=True) + # omega3_content = db.Column(db.Float, nullable=True) + # taurine_content = db.Column(db.Float, nullable=True) + # suitable_for_senior = db.Column(db.Boolean, default=False) + # suitable_for_sterilized = db.Column(db.Boolean, default=False) + # croquette_shape = db.Column(db.String(50), nullable=True) + # weight_kg = db.Column(db.Float, nullable=False) + # price_eur = db.Column(db.Float, nullable=False) + url = db.Column(db.String(255), nullable=True) + + def __repr__(self): + return f'' + + def serialize(self): + return { + "id": self.id, + "name": self.name, + "brand": self.brand, + "description": self.description, + "ingredients": self.ingredients, + "weight": self.weight, #peso en kilos + "price": self.price, #precio en euros + "animal_type": self.animal_type, + "size": self.size, + "pathologies": self.pathologies, + "url": self.url + } + +class Accessories(db.Model): + __tablename__ = 'accessories' + + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(100), nullable=False) + brand = db.Column(db.String(100), nullable=False) + description = db.Column(db.Text, nullable=True) + animal_type = db.Column(db.String(50), nullable=False) + pathologies = db.Column(db.Text, nullable=True) + price = db.Column(db.Float, nullable=False) + # is_hypoallergenic = db.Column(db.Boolean, default=False) + # is_gluten_free = db.Column(db.Boolean, default=False) + # protein_source = db.Column(db.String(100), nullable=False) + # fat_content = db.Column(db.Float, nullable=True) + # omega3_content = db.Column(db.Float, nullable=True) + # taurine_content = db.Column(db.Float, nullable=True) + # suitable_for_senior = db.Column(db.Boolean, default=False) + # suitable_for_sterilized = db.Column(db.Boolean, default=False) + # croquette_shape = db.Column(db.String(50), nullable=True) + # weight_kg = db.Column(db.Float, nullable=False) + # price_eur = db.Column(db.Float, nullable=False) + url = db.Column(db.String(255), nullable=True) + + def __repr__(self): + return f'' + def serialize(self): + return { + "id": self.id, + "name": self.name, + "brand": self.brand, + "description": self.description, + "animal_type": self.animal_type, + "price": self.price, #precio en euros + "pathologies": self.pathologies, + "url": self.url + } + +# class ExoticFood(db.Model): +# __tablename__ = '' + +# id = db.Column(db.Integer, primary_key=True) +# name = db.Column(db.String(100), nullable=False) +# brand = db.Column(db.String(100), nullable=False) +# description = db.Column(db.Text, nullable=True) +# ingredients = db.Column(db.Text, nullable=False) +# # is_hypoallergenic = db.Column(db.Boolean, default=False) +# # is_gluten_free = db.Column(db.Boolean, default=False) +# # protein_source = db.Column(db.String(100), nullable=False) +# # fat_content = db.Column(db.Float, nullable=True) +# # omega3_content = db.Column(db.Float, nullable=True) +# # taurine_content = db.Column(db.Float, nullable=True) +# # suitable_for_senior = db.Column(db.Boolean, default=False) +# # suitable_for_sterilized = db.Column(db.Boolean, default=False) +# # croquette_shape = db.Column(db.String(50), nullable=True) +# # weight_kg = db.Column(db.Float, nullable=False) +# # price_eur = db.Column(db.Float, nullable=False) +# url = db.Column(db.String(255), nullable=True) + +# def __repr__(self): +# return f'' +# def serialize(self): +# return { +# "id": self.id, +# "name": self.name, +# "brand": self.brand, +# "description": self.description, +# "ingredients": self.ingredients, +# "weight_kg": self.weight_kg, +# "price_eur": self.price_eur, +# "url": self.url +# } + +class Pet(db.Model): + __tablename__ = 'pet' + + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(100), nullable=False) + size = db.Column(db.String(100), nullable=True) # cambiar raza por tamaño + breed = db.Column(db.String(100), nullable=True) # cambiar raza por tamaño + age = db.Column(db.String, nullable=False) + animal_type= db.Column(db.String, nullable=False) + pathologies = db.Column(db.Text, nullable=False) # patología contemple peso + user_id = db.Column(db.ForeignKey("user.id")) + + # is_hypoallergenic = db.Column(db.Boolean, default=False) + # is_gluten_free = db.Column(db.Boolean, default=False) + # protein_source = db.Column(db.String(100), nullable=False) + # fat_content = db.Column(db.Float, nullable=True) + # omega3_content = db.Column(db.Float, nullable=True) + # taurine_content = db.Column(db.Float, nullable=True) + # suitable_for_senior = db.Column(db.Boolean, default=False) + # suitable_for_sterilized = db.Column(db.Boolean, default=False) + # croquette_shape = db.Column(db.String(50), nullable=True) + # weight_kg = db.Column(db.Float, nullable=False) + # price_eur = db.Column(db.Float, nullable=False) + url = db.Column(db.String(255), nullable=True) + + def __repr__(self): + return f'' + def serialize(self): + return { + "id": self.id, + "name": self.name, + "size": self.size, + "age": self.age, + "breed": self.breed, + "animal_type": self.animal_type, + "pathologies": self.pathologies, + "url": self.url + } + + diff --git a/src/api/routes.py b/src/api/routes.py index 45d8de4e19..cd44d3cc22 100644 --- a/src/api/routes.py +++ b/src/api/routes.py @@ -2,9 +2,11 @@ This module takes care of starting the API Server, Loading the DB and Adding the endpoints """ from flask import Flask, request, jsonify, url_for, Blueprint -from api.models import db, User +from api.models import db, User, Food, Pet, Accessories from api.utils import generate_sitemap, APIException from flask_cors import CORS +from sqlalchemy import select +import json api = Blueprint('api', __name__) @@ -12,12 +14,214 @@ CORS(api) -@api.route('/hello', methods=['POST', 'GET']) -def handle_hello(): +# @api.route('/hello', methods=['POST', 'GET']) +# def handle_hello(): + +# response_body = { +# "message": "Hello! I'm a message that came from the backend, check the network tab on the google inspector and you will see the GET request" +# } + +# return jsonify(response_body), 200 + + +@api.route('/') +def sitemap(): + return generate_sitemap(api) + +# Obtener todos los alimentos +@api.route('/foods', methods=['GET']) +def get_foods(): + foods = Food.query.all() + if not foods: + return "food not found", 404 + else: + return jsonify([food.serialize() for food in foods]), 200 + + +# Obtener un alimento por ID +@api.route('/foods/', methods=['GET']) +def get_food(food_id): + food = Food.query.get(food_id) + if not food: + return jsonify({"error": "Food not found"}), 404 + return jsonify(food.serialize()), 200 + + +@api.route('/users', methods=['GET']) +def get_users(): + users = User.query.all() + if not users: + return "not users found", 404 + return jsonify([user.serialize() for user in users]), 200 + + +# Obtener un usuario por ID +@api.route('/users/', methods=['GET']) +def get_user(user_id): + user = User.query.get(user_id) + if not user: + return jsonify({"error": "user not found"}), 404 + return jsonify(user.serialize()), 200 + + +@api.route('/pets', methods=['GET']) +def get_pets(): + pets = Pet.query.all() + if not pets: + return "no pets found", 404 + return jsonify([pet.serialize() for pet in pets]), 200 + + +# Obtener una mascota por ID +@api.route('/pets/', methods=['GET']) +def get_pet(pet_id): + pet = Pet.query.get(pet_id) + if not pet: + return jsonify({"error": "pet not found"}), 404 + return jsonify(pet.serialize()), 200 + +#obtener sugerencias de comida según mascota +@api.route('/foods/suggestions/', methods=['GET']) +def get_pet_suggestions(pet_id): + pet = Pet.query.get(pet_id).serialize() + # Problema: Un animal puede tener varias patologias en su campo, habría que coger este campo y tratarlo, + # separar las patologias en una lista y hacer la query para cada patologia. + # Solucion simple: limitar a 1 patologia cada animal por ahora + #if para pet# anymal_type == perro, animal size #si no no hace falta size + if pet["animal_type"] == "perro": + food_suggestions = db.session.execute(select(Food).where(Food.animal_type==pet["animal_type"]), + Food.size==pet["size"], + Food.age==pet["age"], + Food.pathologies==pet["pathologies"]).all() + else: + food_suggestions = db.session.execute(select(Food).where(Food.animal_type==pet["animal_type"]), + Food.age==pet["age"], + Food.pathologies==pet["pathologies"]).all() + if not food_suggestions : + return "no suggestions found", 404 + return [food[0].serialize() for food in food_suggestions], 200 + +# Obtener todos los accesorios +@api.route('/accessories', methods=['GET']) +def get_accessories(): + accessories = Accessories.query.all() + if not accessories: + return "no accessories found", 404 + return jsonify([accessory.serialize() for accessory in accessories]), 200 + + +# Obtener una accesotio por ID +@api.route('/accessories/', methods=['GET']) +def get_accessory(accessories_id): + accessories = Accessories.query.get(accessories_id) + if not accessories: + return jsonify({"error": "accessories not found"}), 404 + return jsonify(accessories.serialize()) + +# Crear un nuevo alimento +@api.route('/foods', methods=['POST']) +def create_food(): + data = request.get_json() + new_food = Food( + name=data["name"], + brand=data["brand"], + description=data.get("description"), + ingredients=data["ingredients"], + animal_type=data["animal_type"], + pathologies=data["pathologies"], # Recibe lista JSON + size=data["size"], + weight=data["weight"], + price=data["price"], + url=data.get("url") + ) + db.session.add(new_food) + db.session.commit() + return jsonify(new_food.serialize()), 201 + +#crear nuevo usuario +@api.route('/users', methods=['POST']) +def create_user(): + data = request.get_json() + new_user = User( + name=data["name"], + email=data["email"], + password=data["password"], + is_active=data["is_active"] +) + db.session.add(new_user) + db.session.commit() + return jsonify(new_user.serialize()), 201 + +#crear un nuevo accesorio +@api.route('/accessories', methods=['POST']) +def create_accessory(): + data = request.get_json() + new_accessory = Accessories( + name=data["name"], + brand=data["brand"], + description=data["description"], + animal_type=data["animal_type"], + pathologies=data["pathologies"], + price=data["price"], + url=data["url"] +) + db.session.add(new_accessory) + db.session.commit() + return jsonify(new_accessory.serialize()), 201 + +#crear una nueva mascota +@api.route('/pets', methods=['POST']) +def create_pet(): + data = request.get_json() + new_accessory = Pet( + name=data["name"], + size=data["size"], + breed=data["breed"], + age=data["age"], + animal_type=data["animal_type"], + pathologies=data["pathologies"], + user_id=data["user_id"], + + +) + db.session.add(new_accessory) + db.session.commit() + return jsonify(new_accessory.serialize()), 201 + +# @api.route('/foods/', methods=['PUT']) +# def update_food(food_id): +# food = Food.query.get(food_id) +# if not food: +# return jsonify({"message": "Food not found"}), 404 + +# data = request.get_json() + +# food.name = data.get("name", food.name) +# food.brand = data.get("brand", food.brand) +# food.description = data.get("description", food.description) +# food.ingredients = data.get("ingredients", food.ingredients) +# food.weight = data.get("weight", food.weight) +# food.price = data.get("price", food.price) +# food.animal_type = data.get("animal_type", food.animal_type) +# food.size = data.get("size", food.size) +# food.pathologies = data.get("pathologies", food.patologies) +# food.url = data.get("url", food.url) + +# db.session.commit() + +# return jsonify({ +# "id": food.id, +# "name": food.name, +# "brand": food.brand, +# "description": food.description, +# "ingredients": food.ingredients, +# "animal_type": food.animal_type, +# "price": food.price, +# "weight": food.weight, +# "size" : food.size, +# "pathologies": food.pathologies, +# "url": food.url +# }) - response_body = { - "message": "Hello! I'm a message that came from the backend, check the network tab on the google inspector and you will see the GET request" - } - return jsonify(response_body), 200