So I have this program that notes down recipes. a recipe has a number of ingredients and they have a many to many relationship. There is a table of ingredients and they are linked through the recipe_ingredients table to a recipe.
I made the following code to update a recipe (lets say I removed all the ingredients and added a new one), however, instead of clearing the old ingredients, it just appends everything. Am I doing something wrong here?
Update recipe code:
#app.route('/recipe', methods=['POST'])
#expects_json(recipeSchema)
def updateRecipe():
recipeToUpdate = session.query(classes.Recipe).filter(classes.Recipe.id==flask.request.json["id"]).first()
if recipeToUpdate is not None:
newRecipe = flask.request.json
if "name" in newRecipe.keys():
recipeToUpdate.name = newRecipe["name"]
if "amountofpeople" in newRecipe.keys():
recipeToUpdate.amountofpeople = newRecipe["amountofpeople"]
if "ingredients" in newRecipe.keys():
newIngredientsArray = []
for ingredient in newRecipe["ingredients"]:
if "id" in ingredient.keys():
newIngredient = session.query(classes.Ingredient).filter(classes.Ingredient.id==ingredient["id"]).first()
newRecipeIngredient = classes.RecipeIngredients(recipe_id=recipeToUpdate.id, ingredient_id=newIngredient.id, ingredient_amount=1)
if "ingredient_amount" in ingredient.keys():
newRecipeIngredient.ingredient_amount = ingredient["ingredient_amount"]
newIngredientsArray.append(newRecipeIngredient)
recipeToUpdate.ingredients[:] = newIngredientsArray
session.commit()
return mapRecipes(recipeToUpdate)
else:
return {"id": id, "Error": "Not found"}
Recipe:
class Recipe(Base):
__tablename__ = 'recipes'
id = Column(Integer, Sequence('recipe_id_seq'), primary_key=True, autoincrement=True)
amountofpeople = Column(Integer)
name = Column(String)
ingredients = relationship("RecipeIngredients", back_populates="recipe")
Ingredient:
class Ingredient(Base):
__tablename__ = 'ingredients'
id = Column(Integer, primary_key=True)
name = Column(String)
amount = Column(Integer)
unit = Column(Enum(Units))
recipe = relationship('RecipeIngredients', back_populates="ingredient")
Recipe_ingredients:
class RecipeIngredients(Base):
__tablename__ = 'recipe_ingredients'
id = Column(Integer, primary_key=True)
recipe_id = Column(Integer, ForeignKey('recipes.id'))
ingredient_id = Column(Integer, ForeignKey('ingredients.id'))
ingredient_amount = Column(Integer)
recipe = relationship('Recipe', back_populates="ingredients")
ingredient = relationship('Ingredient', back_populates="recipe")
I found a solution, however, I am not sure that it is the right one:
I added: session.query(classes.RecipeIngredients).filter(classes.RecipeIngredients.recipe_id==recipeToUpdate.id).delete()
This will first remove all the previous relations before making new ones. but I wonder if there might be a cleaner way by defining a sync in the classes.
#app.route('/recipe', methods=['POST'])
#expects_json(recipeSchema)
def updateRecipe():
recipeToUpdate = session.query(classes.Recipe).filter(classes.Recipe.id==flask.request.json["id"]).first()
if recipeToUpdate is not None:
newRecipe = flask.request.json
if "name" in newRecipe.keys():
recipeToUpdate.name = newRecipe["name"]
if "amountofpeople" in newRecipe.keys():
recipeToUpdate.amountofpeople = newRecipe["amountofpeople"]
if "ingredients" in newRecipe.keys():
session.query(classes.RecipeIngredients).filter(classes.RecipeIngredients.recipe_id==recipeToUpdate.id).delete()
newIngredientsArray = []
for ingredient in newRecipe["ingredients"]:
if "id" in ingredient.keys():
newIngredient = session.query(classes.Ingredient).filter(classes.Ingredient.id==ingredient["id"]).first()
newRecipeIngredient = classes.RecipeIngredients(recipe_id=recipeToUpdate.id, ingredient_id=newIngredient.id, ingredient_amount=1)
if "ingredient_amount" in ingredient.keys():
newRecipeIngredient.ingredient_amount = ingredient["ingredient_amount"]
newIngredientsArray.append(newRecipeIngredient)
recipeToUpdate.ingredients[:] = newIngredientsArray
session.commit()
return mapRecipes(recipeToUpdate)
else:
return {"id": id, "Error": "Not found"}
Related
I'm trying to build a flask app and can't figure out a way to extract and match the date of birth of the student with the current date. I'm using sqlalchemy and postgresql.
Below are the models.
class School(db.Model, UserMixin):
__tablename__ = 'school'
id = db.Column(db.Integer, primary_key=True)
email = db.Column(EmailType, nullable = False, unique = True)
username = db.Column(db.String(20), nullable = False, unique = True)
name = db.Column(db.String(100))
class Student(db.Model):
__tablename__ = 'Student'
id = db.Column(db.Integer, primary_key = True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable = False)
fname = db.Column(db.String(100))
lname = db.Column(db.String(100))
dob = db.Column(db.Date(), nullable = True)
email = db.Column(EmailType, nullable = False)
user = relationship("School", foreign_keys=[school_id])
verified = db.Column(db.Boolean, default = False, nullable = False)
I want to build a route from where school can send birthday wishes to verified students.
I have tried
#app.route('/dashboard/birthday/',methods=['GET', 'POST'])
#login_required
def birthday():
error = None
user = current_user
id = user.id
total_std = Student.query.filter_by(user_id=id)
verified_std = total_std.filter_by(verified=True).all()
birth = verified_std.dob.strftime('%m/%d/%Y')
ttl = len(birth) #this is to check if things are working or not
return render_template_string('test.html', ttl=ttl)
When I'm running this I'm getting an error
AttributeError: 'list' object has no attribute 'dob'
I want to make a route from where schools can send birthday wishes to verified students only.
I am trying to create a model class Document which optionally contains 2 pointers to 2 other elements of the same class (table).
original_id which should hold the index of another member of the table which is "the" original version of the document (can be null).
first_id which should hold the index of another member of the table which is the "first" document that was stored (can be null).
I defined my class as
class Document(Base):
__tablename__ = 'documents'
id = Column(Integer, primary_key=True)
name = Column(String)
original_id = Column(Integer, ForeignKey('documents.id'))
original = relationship('Document', foreign_keys= original_id)
first_id = Column(Integer, ForeignKey('documents.id'))
first = relationship('Document', foreign_keys= first_id)
def __repr__(self):
return f'{self.name} ({self.id})'
But when I test it as follows:
d1 = Document(name='Dee one')
d2 = Document(name='Dee two')
d1.original.append(d2) # d1 has for original document d2
I am surprised by the implementation in terms of ids stored in what row (after committing)
>>> d1.original_id
None
>>> d2.original_id
1
>>> d1.original
[Dee two (2)]
>>> d2.original
[]
I wanted d1.original_id to hold 2 (the index for Dee two).
Obviously I am doing something wrong, but I am struggling to understand what. It seems I am ending up with a many to one relation but in the wrong direction.
I am using SQLAlchemy 1.3.5
Originally, I wanted to have a class that contains 2 nullable pointers to other Nodes. To make the relationship clearer, let's call the class Person, and the links Father and Mother. Every person has at most a known Father and a known Mother.
So following the docs, I created the class as
class Person(Base):
__tablename__ = 'persons'
id = Column(Integer, primary_key=True)
name = Column(String)
father_id = Column(Integer, ForeignKey('persons.id'))
father = relationship('Person')
When doing so, the father relationship is 1-to-many, i.e. a Person can have many fathers, and a father can be only the father of 1. Which is not what is expected.
Following Many to One relationship with SQLAlchemy in the same table I found a way to make that a 1 to many relationship. Enter the remote_side parameter:
class Person(Base):
__tablename__ = 'persons'
id = Column(Integer, primary_key=True)
name = Column(String)
father_id = Column(Integer, ForeignKey('persons.id'))
father = relationship('Person', remote_side=id)
^^^^^^^^^^^^^^
Now the relationship is as expected. However with multiple links (father and mother), this fails again. I.e. if I refine the class as:
class Person(Base):
__tablename__ = 'persons'
id = Column(Integer, primary_key=True)
name = Column(String)
father_id = Column(Integer, ForeignKey('persons.id'))
father = relationship('Person', remote_side= id)
mother_id = Column(Integer, ForeignKey('persons.id'))
mother = relationship('Person', remote_side= id)
SQLAlchemy fails with an "AmbiguousForeignKeysError". The solution is then to specify what field is used to link to father and mother, as follows:
class Person(Base):
__tablename__ = 'persons'
id = Column(Integer, primary_key=True)
name = Column(String)
father_id = Column(Integer, ForeignKey('persons.id'))
father = relationship('Person', remote_side= id, foreign_keys=father_id)
^^^^^^^^^^^^^^^^^^^^^^
mother_id = Column(Integer, ForeignKey('persons.id'))
mother = relationship('Person', remote_side= id, foreign_keys=mother_id)
^^^^^^^^^^^^^^^^^^^^^^
Now that works as expected.
So in some way, this is an extension of the solution provided in Many to One relationship with SQLAlchemy in the same table
Many thanks Gord Thomson for your remarks.
I currently having an issue with a self reference relationship.
I have the table Customer which can have a parent (also a Customer) like this:
class CustomerModel(db.Model):
__tablename__ = 'customer'
id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey('customer.id'))
parent = relationship("CustomerModel", foreign_keys=[parent_id])
So my problem is that when I'm trying to load the parent, the following query is built by SQLAlchemy:
Lets take this customer for example: Customer(id=1, parent_id=10)
SELECT *
FROM customer
WHERE 1 = customer.parent_id
So the WHERE condition is wrong because it compares the parent_id to the id of the customer I'm trying to load the parent from.
The correct query should be:
SELECT *
FROM customer
WHERE 10 = customer.parent_id
What am I doing wrong?
So i finally found the answer.
I need to add the param remote_side like this:
class CustomerModel(db.Model):
__tablename__ = 'customer'
id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey('customer.id'))
parent = relationship("CustomerModel", foreign_keys=[parent_id], remote_side=[id])
I am trying to make a circular one-to-one relationship (not sure what the correct term is) with SQLAlchemy that looks the following:
class Parent(Base):
__tablename__ = 'parents'
id = db.Column(Integer, primary_key=True)
child_id = db.Column(db.Integer,db.ForeignKey("children.id", use_alter=True))
child = db.relationship("Child",
uselist=False,
foreign_keys=[child_id],
post_update=True)
class Child(Base):
__tablename__ = 'children'
id = db.Column(db.Integer, primary_key=True)
parent_id = db.Column(db.Integer, db.ForeignKey("parents.id"))
user = db.relationship("Parent",
uselist=False,
foreign_keys=[parent_id])
Everything works as expected until I try to db.drop_all() and I get an error that the sqlalchemy.sql.schema.ForeignKeyConstraint name is None. Am I doing something wrong when trying to make this circular one-to-one relationship? I would really like to be able to query just the single column to get the id of the other one, hence the circular reference.
From the SQLAlchemy Docs:
class Parent(Base):
__tablename__ = 'parent'
id = Column(Integer, primary_key=True)
child = relationship("Child", uselist=False, back_populates="parent")
class Child(Base):
__tablename__ = 'child'
id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey('parent.id'))
parent = relationship("Parent", back_populates="child")
Then you can Parent.child or Child.parent all day long
I create a many-to-many relationship between a Person (as an author) and a Reference (as a scientific publication).
ReferenceAuthor = sa.Table('ReferenceAuthor', _Base.metadata,
sa.Column('ReferenceID', sa.Integer, sa.ForeignKey('Reference.ID'), primary_key=True),
sa.Column('PersonID', sa.Integer, sa.ForeignKey('Person.ID'), primary_key=True),
sa.Column('Index', sa.Integer)
)
class Reference(_Base):
__tablename__ = 'Reference'
_id = sa.Column('ID', sa.Integer, primary_key=True)
_authors = sao.relationship('Person', secondary=ReferenceAuthor)
class Person(_Base):
__tablename__ = 'Person'
_id = sa.Column('ID', sa.Integer, primary_key=True)
I would like to have the objects in Reference().authors ordered by ReferenceAuthor.Index.
The three tables here come from a foreign sqlite3-database. I am not able to change the structure of that tables. I have to deal with them like they are.
I could finde some examples about order_by in relationship() but nothing combined with the use of secondary=.
I tried an association_proxy and read the docs about that.
class ReferenceAuthor(_Base):
__tablename__ = 'ReferenceAuthor'
_reference_id = sa.Column('ReferenceID', sa.Integer, sa.ForeignKey('Reference.ID'), primary_key=True)
_person_id = sa.Column('PersonID', sa.Integer, sa.ForeignKey('Person.ID'), primary_key=True)
_index = sa.Column('Index', sa.Integer)
_person = sao.relationship('Person')
class Reference(_Base):
__tablename__ = 'Reference'
_id = sa.Column('ID', sa.Integer, primary_key=True)
_authors = saa.association_proxy('ReferenceAuthor', 'Person')
When I read the docs I think I understand the concept behind it. But with my code it doesn't work and I don't understand why. The error is
sqlalchemy.exc.InvalidRequestError: Mapper 'Mapper|Reference|Reference' has no property 'ReferenceAuthor'.
Solution 1:
Depending on the nice comment-answer of #univerio:
class ReferenceAuthor(_Base):
__tablename__ = 'ReferenceAuthor'
_reference_id = sa.Column('ReferenceID', sa.Integer, sa.ForeignKey('Reference.ID'), primary_key=True)
_person_id = sa.Column('PersonID', sa.Integer, sa.ForeignKey('Person.ID'), primary_key=True)
_index = sa.Column('Index', sa.Integer)
_person = sao.relationship('Person')
class Reference(_Base):
__tablename__ = 'Reference'
_id = sa.Column('ID', sa.Integer, primary_key=True)
_reference_authors = sao.relationship('ReferenceAuthor',
order_by=ReferenceAuthor._index)
_authors = saa.association_proxy('_reference_authors', '_person')
Solution 2:
Got this hint from the sqla-mailinglist. Depending on the first code in my initial question:
_authors = sao.relationship('Person',
secondary=ReferenceAuthor,
order_by=ReferenceAuthor.c.Index)
This looks quite simpler.
Side question: What is the difference between that two solutions?