Nested one-to-many relations in flask-admin - flask-admin

I have such models in my project:
class Ingredient(Base):
__tablename__ = 'ingredient'
id = Column(Integer, primary_key=True, index=True)
name = Column(String)
measurement_unit = Column(String)
ingredient_amount = relationship('IngredientAmount', back_populates='ingredient')
def __str__(self):
return self.name
class IngredientAmount(Base):
__tablename__ = 'ingredient_amount'
id = Column(Integer, primary_key=True, index=True, nullable=True)
ingredient_id = Column(Integer, ForeignKey('ingredient.id'))
recipe_id = Column(Integer, ForeignKey('recipe.id'))
amount = Column(Integer)
ingredient = relationship('Ingredient', back_populates='ingredient_amount')
recipes = relationship(
'Recipe',
back_populates='ingredients',
cascade='all, delete'
)
class Recipe(Base):
__tablename__ = 'recipe'
id = Column(Integer, primary_key=True, index=True)
text = Column(String)
name = Column(String, unique=True)
author_id = Column(Integer, ForeignKey('user.id'))
ingredients = relationship(
'IngredientAmount',
back_populates='recipes',
cascade='all, delete'
)
So when I look at recipe in flask-admin i'd like to see ingredients which loads IngredientAmount instance and IngredientAmount instance loads Ingredient instance. I tried to implement view as:
class RecipeView(ModelView):
can_edit = True
can_create = True
can_delete = True
can_view_details = True
column_auto_select_related = True
column_display_pk = True
column_display_all_relations = True
column_display_actions = True
def get_query(self):
return self.session.query(Recipe).order_by(Recipe.id).options(
joinedload(Recipe.ingredients).joinedload(IngredientAmount.ingredient)
).options(joinedload(Recipe.tags)).options(
joinedload(Recipe.author)
)
But I see <models.IngredientAmount object at 0x7f6e7b7edd80>. Is it possible to do what I want?

Related

Flask SQLAlchemy, IntegrityError, ForeignKeyViolation - Creating relationships between tables

I'm trying to create a Flask app where I can return a list of 'workspaces' from a postgresql database, a list of 'categories and a list of 'projects'.
In projects.py, when a GET method is called, the database will return all of the 'workspaces' and all of the 'categories' and get both the id and name for each, I can then put this in a dropdown button for the user to select. This part of the code works fine.
When a POST method is called, this should add a new row to the 'projects' table to define a new project, along with this should be the workspace_id and the categories_id that the user selected.
This part of the code is not working, I get the following error message:
sqlalchemy.exc.IntegrityError: (psycopg2.errors.ForeignKeyViolation) insert or update on table "projects" violates foreign key constraint "workspace_id"
DETAIL: Key (id)=(19) is not present in table "workspaces".
[SQL: INSERT INTO projects (name, description, category_id, workspace_id, visibility_level, slug) VALUES (%(name)s, %(description)s, %(category_id)s, %(workspace_id)s, %(visibility_level)s, %(slug)s) RETURNING projects.id]
[parameters: {'name': 'Brand New Project', 'description': 'gfhfghfg', 'category_id': '1', 'workspace_id': '1', 'visibility_level': '1', 'slug': 'brand-new-project'}]
models.py:
from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship
from taskmanagement.database import Base
class Projects(Base):
__tablename__ = 'projects'
id = Column(Integer, primary_key=True)
name = Column(String(140), unique=True)
description = Column(String(140), unique=True)
category_id = Column(Integer, ForeignKey('categories.id'), unique=False, nullable=False)
category = relationship('Categories', lazy=True, uselist=False, primaryjoin="Categories.id == Projects.category_id")
workspace_id = Column(Integer, ForeignKey('workspaces.id'), unique=False, nullable=False)
workspace = relationship('Workspaces', lazy=True, uselist=False, primaryjoin="Projects.workspace_id == Workspaces.id")
visibility_level = Column(Integer, unique=False)
slug = Column(String(240), unique=True)
def __init__(self, name=None, description=None, category_id=None, workspace_id=None, visibility_level=None, slug=None):
self.name = name
self.description = description
self.category_id = category_id
self.workspace_id = workspace_id
self.visibility_level = visibility_level
self.slug = slug
def __repr__(self):
return f'<Projects {self.name!r}>'
class Workspaces(Base):
__tablename__ = 'workspaces'
id = Column(Integer, primary_key=True)
name = Column(String(50), unique=True)
def __init__(self, name=None):
self.name = name
def __repr__(self):
return f'<Workspaces {self.name!r}>'
class Categories(Base):
__tablename__ = 'categories'
id = Column(Integer, primary_key=True)
name = Column(String(50), unique=True)
def __init__(self, name=None):
self.name = name
def __repr__(self):
return f'<Workspaces {self.name!r}>'
Projects.py
#projects.route('/projects/add', methods = ['POST', 'GET'])
def project_add():
if request.method == 'GET':
categories = Categories.query.all()
workspaces = Workspaces.query.all()
categories_results = [
{
"id": category.id,
"name": category.name
} for category in categories]
workspaces_results = [
{
"id": workspace.id,
"name": workspace.name
} for workspace in workspaces]
results = {"categories": categories_results, "workspaces": workspaces_results}
return render_template('project_add.html', title='Add Project', results=results)
else:
name = request.form['project_name']
slug = request.form['project_slug']
description = request.form['project_desc']
visibility_level = request.form['project_visibility']
category_id = request.form['category_id']
workspace_id = request.form['workspace_id']
form_results = {
"name": request.form['project_name'],
"slug": request.form['project_slug'],
"description": request.form['project_desc'],
"visibility_level": request.form['project_visibility'],
"category_id": request.form['category_id'],
"workspace_id": int(request.form['workspace_id'])
}
#return form_results
results = Projects(name, description, category_id, workspace_id, visibility_level, slug)
db_session.add(results)
db_session.commit()
I'm completely stuck of this problem. Can anyone tell me what I'm doing wrong?

Return Django ORM union result in Django-Graphene

I am trying to query two separate objects and return them as a single result set. I've tried using a Union and an Interface, but not sure what I'm doing wrong there.
My model:
class BaseActivity(models.Model):
class Meta:
abstract = True
name = models.CharField(db_column="activity_type_name", unique=True, max_length=250)
created_at = CreationDateTimeField()
modified_at = ModificationDateTimeField()
created_by = models.UUIDField()
modified_by = models.UUIDField()
deleted_at = models.DateTimeField(blank=True, null=True)
deleted_by = models.UUIDField(blank=True, null=True)
class Activity(BaseActivity):
class Meta:
db_table = "activity_type"
ordering = ("sort_order",)
id = models.UUIDField(
db_column="activity_type_id",
primary_key=True,
default=uuid.uuid4,
editable=False,
)
sort_order = models.IntegerField()
def __str__(self):
return self.name
class CustomActivity(BaseActivity):
class Meta:
db_table = "organization_custom_activity_type"
id = models.UUIDField(
db_column="organization_custom_activity_type",
primary_key=True,
default=uuid.uuid4,
editable=False,
)
farm = models.ForeignKey("farm.Farm", db_column="organization_id", on_delete=models.DO_NOTHING, related_name="custom_activity_farm")
My schema:
class FarmActivities(graphene.ObjectType):
id = graphene.String()
name = graphene.String()
class ActivityType(DjangoObjectType):
class Meta:
model = Activity
fields = ("id", "name", "requires_crop", "sort_order")
class CustomActivityType(DjangoObjectType):
class Meta:
model = CustomActivity
fields = ("id", "name", "farm")
And the query:
class Query(graphene.ObjectType):
get_farm_activities = graphene.Field(FarmActivities, farm=graphene.String(required=True))
def resolve_get_farm_activities(self, info, farm):
farm_activities = Activity.objects.values("id", "name").filter(
farmtypeactivityrel__farm_type__farm=farm, deleted_at=None
)
custom_activities = CustomActivity.objects.values("id", "name").filter(farm=farm, deleted_at=None)
return list(chain(farm_activities, custom_activities))
With this, I do get a list back from the query, but it's not going thru the resolver when I call getFarmActivities.
Literally the list returns:
ExecutionResult(data={'getFarmActivities': {'id': None, 'name': None}}, errors=None)
How to resolve graphene.Union Type?
That provided the hint I needed to get this working. I had to build a Union that would parse the model and not the schema type.
class FarmActivities(graphene.Union):
class Meta:
types = (ActivityType, CustomActivityType)
#classmethod
def resolve_type(cls, instance, info):
if isinstance(instance, Activity):
return ActivityType
if isinstance(instance, CustomActivity):
return CustomActivityType
return FarmActivities.resolve_type(instance, info)
Which then allowed me to run the query like so:
def resolve_get_farm_activities(self, info, farm):
farm_activities = Activity.objects.filter(
farmtypeactivityrel__farm_type__farm=farm
)
custom_activities = CustomActivity.objects.filter(farm=farm)
return list(chain(farm_activities, custom_activities))
And the API query:
query farmParam($farm: String!)
{
getFarmActivities(farm: $farm)
{
... on CustomActivityType
{
id
name
}
... on ActivityType
{
id
name
}
}
}

Why are default values for PostgreSQL on insert not working?

I have a PostgreSQL database on Heroku and I've connected to that using PopSQL. Here is the model for the table (using Flask + SQLAlchemy) -
class User(db.Model):
__tablename__ = 'users'
email = Column(String, primary_key=True)
group = Column(String)
age = Column(String)
gender = Column(String)
category = Column(String)
isEnrolled = Column(Boolean)
logCount = Column(Integer, default=0)
logged = Column(ARRAY(Boolean), default=[None]*14)
medMin = Column(ARRAY(Integer), default=[None]*14)
def __init__(self, email, age, group ,gender, category):
self.email = email
self.age = age
self.group = group
self.gender = gender
self.category = category
The table was created using db.create_all()
In PopSQL, when I do an INSERT of the form:
INSERT INTO users(email,"group",age,gender,category)
VALUES('abc#gmail.com','other','20-30','Female','intrinsic');
The columns of logCount, logged and medMin are set to null and not their default values. Why is this happening?

Login_user fails to get userid with NotImplementedError

My app fails whenever I call login_user with the error
"NotImplementedError: No id attribute - override get_id". Why does this fail?
#app.route('/login', methods=['GET', 'POST'])
def login():
if current_user.is_authenticated:
return redirect(url_for('home'))
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(email=form.email.data).first()
if user and bcrypt.check_password_hash(user.password, form.password.data):
login_user(user, remember=True)
else:
flash('Login Unsuccessful. Please check email and password', 'danger')
return redirect(url_for('login'))
return render_template('login.html', title='login', form=form)
#login_manager.user_loader
def load_user(userid):
return User.query.get(userid)
class User(UserMixin):
def get_id(self):
return self.userid
class User(UserMixin, db.Model):
userid = db.Column(db.String(50), primary_key=True)
name = db.Column(db.String(200), nullable=False)
username = db.Column(db.String(200), unique=True, nullable=False)
designation = db.Column(db.String(200), nullable=False)
password = db.Column(db.String(200), nullable=False)
dateofjoin = db.Column(db.DateTime, nullable=True)
email = db.Column(db.String(200), unique=True, nullable=False)
address = db.Column(db.String(200), nullable=False)
mobile = db.Column(db.String(200), nullable=False)
def __repr__(self):
return f"User('{self.userid}','{self.username}', '{self.email}', '{self.designation}', '{self.password}')"
In your User model rename primary_key 'userid' to 'id'. Update the db and try again. The get_id method looks for 'id'. It is hardcoded into flask-login. Or you can edit get_id method itself in flask_login's mixin.py module. But I am not sure it won't break somewhere else.
def get_id(self):
try:
return text_type(self.id) # Rename 'id' to 'userid'
except AttributeError:
raise NotImplementedError('No `id` attribute - override `get_id`')

how to perform join query with sql alchemy with postgres

ExpenseList table have two foreign keys, i want to fetch list of expenses from this category with corresponding category. Currently it is returning category_id only when i am applying following query:
query = ExpenseList.query.filter_by(user_id=6)
return make_response(jsonify([i.serialize for i in query.all()])),200
Its returning response like this:
[
{
"category": 3,
"created_on": "Sun, 05 Nov 2017 09:40:19 GMT",
"money_spent": "50",
"name": "DVD"
},
{
"category": 3,
"created_on": "Sun, 05 Nov 2017 09:40:39 GMT",
"money_spent": "100",
"name": "Movie"
}
]
Model schema is
class ExpenseList(db.Model):
__tablename__ = 'expense_list'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(500), unique=True, nullable=False)
money_spent = db.Column(db.String(500), nullable=False)
category_id = db.Column(db.Integer, db.ForeignKey('category_list.id'))
user_id = db.Column(db.Integer, db.ForeignKey('users.id'))
is_recurring = db.Column(db.Boolean,nullable=False, default=False)
created_on = db.Column(db.DateTime, nullable=False)
#property
def serialize(self):
return {
'name' : self.name,
'money_spent' : self.money_spent,
'category' : self.category_id,
'created_on' : self.created_on
}
class CategoryList(db.Model):
__tablename__ = 'category_list'
id = db.Column(db.Integer,primary_key = True, autoincrement=True)
name = db.Column(db.String(500), unique=True, autoincrement=True)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'))
created_on = db.Column(db.DateTime, nullable=False)
How can i get category name instead of category_id in response object, I also tried backrefs, lazy etc but don't know where i am going wrong.
Here is a join You want
query(ExpenseList.name.label('expense_name'),
ExpenseList.money_spent,
CategoryList.name.label('category_name'),
ExpenseList.created_on)\
.join(CategoryList, ExpenseList.category_id == CategoryList.id)\
.filter_by(ExpenseList.user_id=6)
and a worrying part in Your CategoryList model is:
name = db.Column(db.String(500), unique=True, autoincrement=True)