SQL Alchemy AttributeError: 'str' object has no attribute '_sa_instance_state' - flask

I'm trying to add a new element to a table of a database but I'm keeping getting this error.
My database is this:
class User(UserMixin, db.Model):
__tablename__ = "users"
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(100), unique=True)
password = db.Column(db.String(100))
name = db.Column(db.String(100))
posts = relationship("BlogPost", back_populates="author")
class BlogPost(db.Model):
__tablename__ = "blog_posts"
id = db.Column(db.Integer, primary_key=True)
# Create Foreign Key, "users.id" the users refers to the tablename of User.
author_id = db.Column(db.Integer, db.ForeignKey("users.id"))
# Create reference to the User object, the "posts" refers to the posts property in the User class.
author = relationship("User", back_populates="posts")
title = db.Column(db.String(250), unique=True, nullable=False)
subtitle = db.Column(db.String(250), nullable=False)
date = db.Column(db.String(250), nullable=False)
body = db.Column(db.Text, nullable=False)
img_url = db.Column(db.String(250), nullable=False)
and the function I'm using to create a new blog post is this:
form = CreatePostForm()
if form.validate_on_submit():
new_post = BlogPost(
title=form.title.data,
subtitle=form.subtitle.data,
body=form.body.data,
img_url=form.img_url.data,
author=current_user.name,
author_id=current_user.id,
date=date.today().strftime("%B %d, %Y")
)
db.session.add(new_post)
db.session.commit()
return redirect(url_for("get_all_posts"))
return render_template("make-post.html", form=form)
I specify that before linking the two tables, the code was working just fine. The problem occured when I created a relationship among the two tables.
Thanks for your kind help in advance!

The relational database uses the foreign key to reference related records. This always refers to the unique primary key of another table. So you define a link in which you define a column foreign key, which points to the primary key of another table. This means that the referenced data record can be queried with an SQL JOIN statement.
You can also define a relationship in SQLAlchemy. This automatically loads the columns of the referenced dataset and creates an instance of an object of the respective model class. You can request one or more objects directly from the model, depending on the relationship (ONE <-> ONE, ONE <-> MANY, MANY <-> ONE, MANY <-> MANY). This is the virtual relationship. It is only there to do the class mapping and is optional and not a property of the database.
When creating an object using SQLAlchemy, as you did in your route, you can either set the referencing id (foreign key; author_id), or you pass over the entire referenced object (author / current_user). If you use the first variant, you set the foreign key directly. If the second solution is chosen, the primary key (id) column is automatically assigned to the column that is defined as the foreign key (author_id) in the background.
The actual relationship is defined by the link between the foreign key and the primary key. This is a property of the database. The lookup of the objects is defined by the SQLAlchemy relationship and reflects part of the ORM (Object Relational Mapping). It is a property of SQLAlchemy.
Your mistake was that you passed a string to the constructor instead of an object from your model class. So the mapping could not work.

Related

Relative Primary Key in Flask_SQLAlchemy

Context/Minimal Example: I'm relatively new to Database design and trying to design a small app in Flask/Flask_SQLAlchemy that tracks inventory.
I have a User table:
class Users(db.Model):
user_id = db.Column(db.Integer, primary_key=True)
first_name = db.Column(db.String(25))
items = db.relationship('Item', lazy="dynamic")
and a Item table:
class Item(db.Model):
user_id = db.Column(db.Integer, db.ForeignKey('users.user_id'), index=True)
sku = db.Column(db.Integer, index=True, autoincrement=True)
name = db.String(10)
I would like to create a system where the Item.sku is unique... but only based on the user.id.
E.g. two users can have items of the same Sku, but one user may not have multiple items of the same sku. (And preferably have the sku automatically increment itself).
To me, this is a constraint that makes sense- sku+user_id should always be a unique combination, so I can save space and simplicity by using it as a primary key, as well as increasing the ?normalization? of the database.
However, I've spent a fair amount of time now reading and trying to figure out how to do this and I keep running into problems. Is there an easy way of accomplishing this, or is there something wrong with my logic that has lead to this design? Are there downsides to this I'm missing?
So far I've tried:
Setting both user_id and sku to primary_key=true
Setting them both to index=True (as you can see here)
Adding a table_args = db.PrimaryKeyConstraint (As discussed here https://www.reddit.com/r/flask/comments/g3tje5/composite_key_for_flasksqlalchemy/)
From what I've read the term of what I'm trying to accomplish here is a compound primary key, and that flask_sqlalchemy does support it, but with all of these I get exceptions that a constraint is failing or a parameter is missing.
Thanks for any help or advice you can provide.
Yes, a composite PK on (user_id, sku) will work, as in this example using vanilla SQLAlchemy ORM:
import sqlalchemy as db
from sqlalchemy.orm import declarative_base, relationship, Session
engine = db.create_engine("sqlite://")
Base = declarative_base()
class Users(Base):
__tablename__ = "users"
user_id = db.Column(db.Integer, primary_key=True)
first_name = db.Column(db.String(25))
items = relationship('Item', lazy="dynamic")
class Item(Base):
__tablename__ = "item"
user_id = db.Column(db.Integer, db.ForeignKey('users.user_id'), primary_key=True)
sku = db.Column(db.Integer, index=True, primary_key=True)
name = db.String(10)
Base.metadata.create_all(engine)
with Session(engine) as sess:
gord = Users(first_name="Gord", items=[Item(sku=1)])
anne = Users(first_name="Anne", items=[Item(sku=1), Item(sku=2)])
sess.add_all([gord, anne])
sess.commit()
# okay so far
# now try to add a duplicate
gord.items.append(Item(sku=1))
sess.flush()
"""
sqlalchemy.exc.IntegrityError: (sqlite3.IntegrityError) UNIQUE constraint failed: item.user_id, item.sku
[SQL: INSERT INTO item (user_id, sku) VALUES (?, ?)]
[parameters: (1, 1)]
"""

Using `select_related` with a table with composite primary key [Django]

Is there a way for Django to support composite primary key in combination of select_related or prefetch_related?
I have a Records table with a composite primary key (device_id, created) with a schema like this (simplified):
class Records(models.Model):
class Meta:
managed = False
unique_together = (("device", "created"),)
# The primary key is really (device_id, created)
device = models.ForeignKey("hardware.Device", primary_key=True, on_delete=models.CASCADE)
created = models.DateTimeField(db_index=True)
value = models.FloatField(default=0)
A record belongs to a Device, which is modeled like this (simplified):
class Device(models.Model):
car = models.ForeignKey("cars.Car", on_delete=models.CASCADE)
last_record_at = models.DateTimeField(null=True, blank=True)
I wish to run a query that will return a list of devices, but for each one also
contain the last record.
In theory, this would be something like that:
Device.objects.filter(...).select_related("car", "last_record")
But obviously "last_record" is not a foreign key, since Records contains a composite primary key which Django doesn't support.
What would be the best way to do this, other than rewriting the query in raw sql? Is there a reasonable way to override select_related to handle composite keys?

Django - Remove Unique Constraint from a field

I have a model like
class LoginAttempts(models.Model):
user = models.OneToOneField(User, unique=False)
counter = models.IntegerField(null=True)
login_timestamp = models.DateTimeField(auto_now=True)
The table created in db is like
However If I create another entry with user_id = 362 it fails with IntegrityError: duplicate key value violates unique constraint. Id is already my primary key, I want to have same user having different counters instead of creating a new table referencing them since this is simple table.
How to achieve the same or what what could be the best way. I want to restrict user to some specified number of failed logins.
If you want a relationship that permits more than one LoginAttempt for a User, you should not use OneToOneField. by definition, that implies only one item on each side. Instead, use ForeignKey.
The very nature of a OneToOneField is that it's a ForeignKey with a unique constraint.
However, if you don't want separate entries, then update the counter and login_timestamp fields:
from django.utils import timezone
def update_attempts_for_user(user):
attempts, created = LoginAttempts.objects.get_or_create(user=user, defaults={'counter': 1, 'login_timestamp': timezone.now())
if not created:
attempts.counter += 1
attempts.login_timestamp = timezone.now()
attempts.save(update_fields=['counter', 'login_timestamp'])

Set relation to many-to-many relationship

I have this many-to-many relationship that works correctly. However, now I need to have another class with a relation to this many-to-many.
currencies = db.Table('currencies_many',
db.Column('id', db.Integer, primary_key=True),
db.Column('currency_id', db.Integer, db.ForeignKey('currencies.id')),
db.Column('bank_id', db.Integer, db.ForeignKey('banks.id'))
)
class Bank(db.Model):
__tablename__ = 'banks'
id = db.Column(db.Integer, primary_key=True)
bank_name = db.Column(db.String(300))
currencies = db.relationship('Currency', secondary=currencies,
backref=db.backref('banks', lazy='dynamic'))
class Currency(db.Model):
__tablename__ = 'currencies'
id = db.Column(db.Integer, primary_key=True)
currency_name = db.Column(db.String(300))
What I mean is, for example, an order, I need to have the association to many to many.
class Order(db.Model):
__tablename__ = 'orders'
id = db.Column(db.Integer, primary_key=True)
bank_currency_identification = db.Column(db.Integer, db.ForeignKey('currencies_many.id'))
How can I do that? In my example I don't have db.relationship for bank_currency_identification, it is correct?
So if I understand your question correctly, you want to reference the currencies_many table from your orders table. If so, you are correct in having a foreign key relationship with the currencies_many table.
However, down the road you may come into some trouble when you want to query orders from your banks table. I would suggest, although it seems redundant, to create a one-to-many relationship between Order and Bank as well as between Order and Currency.
bank_id = db.Column(db.Integer, db.ForeignKey('bank.id'))
currency_id = db.Column(db.Integer, db.ForeignKey('currency.id'))
And then in the Bank class
orders = db.relationship('Order', backref='bank')
This gives you a much cleaner querying interface.
bank_orders = bank.orders
As well as makes your data model cleaner. It would be awkward to have to query orders from an intermediate table that also houses the currency. Just my two cents, but having an easy to understand Data model is better than making awkward relationships to save some redundancy.

Is it possible to instruct Django to save a model instance to a particular table based on its fields?

I'm attempting to construct a Django application that models an existing set of tables. These tables all have the same fields, plus custom fields per table. What I'm wanting to do is model this structure, and have records save to a particular table based on what table model they are attached to.
These tables can be created quite often, so it is unfeasible to construct new models per table.
Perhaps the code will demonstrate what I'm trying to do more clearly:
class CustomField(models.Model):
column_name = models.CharField(max_length=100)
description = models.CharField(max_length=255, blank=True, null=True)
class CustomData(models.Model):
custom_field = models.ForeignKey(CustomField)
value = models.CharField(max_length=100, blank=True, null=True)
# value will always be a nullable varchar(100)
class Table(models.Model):
table_name = models.CharField(max_length=255)
name = models.CharField(max_length=100)
custom_fields = models.ManyToManyField(CustomField)
class Record(models.Model):
table = models.ForeignKey(Table)
... list of common fields omitted ...
custom_values = models.ManyToManyField(CustomData)
When saving a new record that has a foreign key to 'table_1', I would like the eventual operation to be along the lines of insert into table_1 (..fields..) values (..field values..)
Is this possible? I guess I could hook into signals or the save method, but I'd like to find the simplest approach if such exists.
You can create unmanaged models dynamically. You just need to create a dict mapping column names to the data values. Once you have that, you can do the following:
from django.db import models
# This is the dict you created, mapping column names to values
values = {col_1: value_1, col_2: value_2, col_3: value_3, ... }
# Create a dict defining the custom field types, eg {col_name: django_field}
attrs = dict((c, models.CharField(max_length=100, blank=True, null=True)) for c in values)
# Add a Meta class to define the table name, eg table_1
class Meta:
app_label = myapp
db_table = 'table_1'
managed = False
attrs['Meta'] = Meta
attrs['__module__'] = 'path.to.your.apps.module'
DynamicModel = type('MyModel', (models.Model,), attrs)
# Save your data
DynamicModel.objects.create(**values)
Wrap this up in a function, and put it in your .save() method on Record. If you have any common fields, you can add them to attrs, or even better: create an abstract model with all the common fields and inherit that in the last line above instead of models.Model.