So, before adding a few columns I had username, password and email and it was working fine. But when I tried to add a few more things such as age, gender, phone and address I got this error:
sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such table: user [SQL: 'SELECT user.id AS user_id, user.username AS user_username, user.email AS user_email, user.password AS user_password, user.age AS user_age, user.gender AS user_gender, user.phone AS user_phone, user.address AS user_address \nFROM user \nWHERE user.username = ?\n LIMIT ? OFFSET ?'] [parameters: ('agam-kashyap', 1, 0)] (Background on this error at: http://sqlalche.me/e/e3q8)
The error seems to be in these lines:
#app.route("/register",methods = ['GET','POST'])
def register():
form = RegistrationForm()
if form.validate_on_submit():
user = User(username=form.username.data, email = form.email.data, password = form.password.data, age = form.age.data, gender = form.gender.data, phone = form.phone.data, address = form.address.data)
db.create_all()
db.session.add(user)
db.session.commit()
return redirect(url_for('login'))
return render_template('register.html', title= 'Register' , form = form)
Also, here is my User class:
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(20), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
password = db.Column(db.String(60), nullable=False)
age = db.Column(db.String(3), nullable=False)
gender = db.Column(db.String(6), nullable=False)
phone = db.Column(db.String(10), nullable=False)
address = db.Column(db.String(200), nullable=False)
How can I solve this issue?
In keeping with Dave W. Smith's suggestion, the error is probably due to the fact that you did not perform any database migration operations for the new changes to take effect.
When a database model changes, as it does for you, the database must of course be updated. This is what you want to accomplish visibly.
So, you should note that the particularity with SQLAlchemy is that it creates tables from models only when they already exists.
It means that if you want to add new things to the database, modify or delete fields from your model, you have to destroy the old tables and recreate everything from scratch.
So the solution to work around this problem is to go through a database migration framework. Like a source code version control tool, a database migration framework tracks any changes that occur on the database schema. So it can apply incremental changes to the database.
For SQLAlchemy, there is an excellent database migration tool, I named: Alembic. But since you use Flask, you do not have to manipulate Alembic directly; There is a Flask extension that handles SQLAlchemy database migrations for Flask applications using Alembic: flask-migrate
As you are still a beginner with Flask, I recommend this excellent Miguel Grinberg's tutorial: the flask mega tutorial - part-iv :database. It will teach you the basics needed to work with Flask SQLAlchemy
Related
I am using Flask-Migrate (3.1.0) to handle my db(Postgres) migration for a flask application.
There's few answers on the internet but they are targeting models built using SQLAlchemy, the problem is I am building my models using Flask-SQLAlchemy, though I know there is no huge difference between them and Flask-SQLAlchemy is built on top of SQLAlchemy.
def class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), index=True)
email = db.Column(db.String(60), index=True)
date_of_birth = db.Column(db.DateTime, nullable=True)
team_member = db.Column(db.String(30), nullable = False)
is_monitored = db.Column(db.Boolean, default=False, nullable=False)
lifecycle = db.Column(db.Integer, default= 1, nullable=False)
shows=db.relationship('UserHistory', backref='active_state', lazy=True)`
Using flask db migrate to generate the migration script, I found no trace of the default value.
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('user', sa.Column('lifecycle', sa.Integer(), nullable=False))
op.add_column('user_history', sa.Column('lifecycle', sa.Integer(), nullable=False))
# ### end Alembic commands ###
As I define a column constraint to be not null, flask db upgrade fails which makes sense.
Is there any 'Flask-SQLAlchemy' solution to this issue rather than pure 'SQLAlchemy'? I don't know if mixing Flask-SQLAlchemy and SQLAlchemy is good idea .
Thanks
I am using Flask WTF for my application and created a form.
I have defined unique in model.
Flask WTF:
packet_id = StringField("Packet ID", validators=[DataRequired()])
Flask SQL-Alchemy Model : packet_id = db.Column(db.String(120), unique=True, nullable=False)
and handling it like this
def validate_packet_id(self, packet_id):
if packet_id.data is None:
raise ValidationError("Name field is required.")
else:
packet = TableName.query.filter_by(packet_id=packet_id.data).first()
if packet:
raise ValidationError('That packet ID already exists. Please choose a different one.')
This code works perfectly with the post method, but when I use update It shows an error that packet_id already exists.
Can anyone help me how to check the packet_id in DB except this current row id?
I am building an admin dashboard for my web app using Flask-Admin. For the user/address relationship, I am using a one to one relationship. On the user edit form, I'd like to be able to edit the individual components of the address (i.e. street address, city or zip) similar to what inline_models provides. Instead, flask-admin generates a select field and only allows me to select a different addresses.
I tried using inline_models = ['address'] in the UserModelView definition. However, I got the address object not iterable error due to the user/address relationship being configured to uselist=False. Switching uselist to True would affect other parts of my code, so I'd prefer to leave it as False.
From looking in flask-admin/contrib/sqla/forms, within the function get_forms, its being assigned a one to many tag which is what drives the use of a select field.
Before diving in further, I figured it best to see if anyone else has come across this or has a recommended fix/workaround.
models.py
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64))
address = db.relationship("Address", backref="user",
cascade="all, delete-orphan", lazy=False,
uselist=False, passive_deletes=True)
class Address(db.Model):
id = db.Column(db.Integer, primary_key=True)
line1 = db.Column(db.String(128))
zip = db.Column(db.String(20), index=True)
city = db.Column(db.String(64), index=True, nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey("user.id",
ondelete="CASCADE"))
admin.py
class UserModelView(ModelView):
column_list = [User.username, 'address']
form_columns = (User.username, 'address')
admin = Admin(name='Ask', template_mode='bootstrap3')
admin.add_view(UserModelView(User, db.session))
You can create 2 relations
# Relation for flask admin inline model
address_cms_relationsip = db.relationship(
"Address", backref="user", cascade="all, delete-orphan", lazy=False,
uselist=True, passive_deletes=True)
address_relationship = db.relationship(
"Address", cascade="all, delete-orphan", lazy=False,
uselist=False, passive_deletes=True)
#property
def address(self):
return self.address_relationship
In your code you can use property address
user: User # some User object
user.address.city
I already have an application using my SQLAlchemy model, and I'm now trying to add a flask admin website on top to manage some of the data in the DB. This model is already setup and working throughout the existing application so I know the relationships are configured correctly.
In my DB the data_package table has a FK to the supplier table, and this has been added as a relationship. However, when I create a flask model view to allow editing of the data_package I'm receiving the error:
Exception: Cannot find reverse relation for model
I've added a subset of the code below, there are a lot more columns and logic but this is the bare minimum I have to try and test a fix to the issue.The issue is happening because I am adding inline_models = (Supplier,) which I need as I want to have a select to allow the supplier to be changed for a data package.
The line of code throwing the exception is https://github.com/flask-admin/flask-admin/blob/master/flask_admin/contrib/sqla/form.py#L558. The reason for this is the relationship direction is defined as ONETOMANY, and there is a check to ensure the direction is MANYTOONE or MANYTOMANY
Base = declarative_base()
class Supplier(Base):
__tablename__ = 'supplier'
id = Column(Integer, primary_key=True)
name = Column(String(100), nullable=False)
class DataPackage(Base):
__tablename__ = 'data_package'
id = Column(Integer, primary_key=True)
name = Column(String(100), nullable=False)
supplier_id = Column(Integer, ForeignKey('supplier.id'), index=True, nullable=False)
supplier = relationship("Supplier", backref='packages')
class DataPackageAdminView(ModelViewOnly):
form_columns = ['name', 'supplier']
inline_models = (Supplier,)
def create_app():
settings.configure_orm()
app = Flask(__name__)
app.config.from_pyfile('config.py')
#app.route('/')
def index():
return 'Click me to get to Admin!'
admin = Admin(app, name='Test Editor', template_mode='bootstrap3')
admin.add_view(DataPackageAdminView(DataPackage, settings.Session, category='Data Sources'))
return app
if __name__ == '__main__':
app = create_app()
app.run(debug=True)
I've created UserProfile model in my application:
class UserProfile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
primary_key=True,
verbose_name=_('user'),
related_name='user_profile')
locality = models.CharField(max_length=85)
voivodship = models.CharField(max_length=20,
choices=Vovoidship.choices,
validators=[Vovoidship.validator])
postcode = models.IntegerField()
street = models.CharField(max_length=75)
building = models.SmallIntegerField()
premises = models.CharField(max_length=80)
phonenumber = PhoneNumberField(blank=True)
#staticmethod
def post_save_create(sender, instance, created, **kwargs):
if created:
profile, created = UserProfile.objects.get_or_create(user=instance)
post_save.connect(UserProfile.post_save_create, sender=User)
Now, I felt into my own trap. I don't want to loose constraints and keep the requirement in the database that address fields need to be filled up. I'm using django-allauth. While using the setting ACCOUNT_SIGNUP_FORM_CLASS = 'management.forms.SignupForm' solves the problem for traditional sign up form, if the user logs in first time using the social account I got hit by constraint violation for obvious reasons:
IntegrityError at /accounts/google/login/callback/
null value in column "postcode" violates not-null constraint
DETAIL: Failing row contains (4, , , null, , null, , ).
Hence the question, how to correctly implement the request for filling up the information for fields in the application UserProfile? I'm surprised that django-allauth doesn't have a build in handler for that the same way as ACCOUNT_SIGNUP_FORM_CLASS is done.
As I'm new to Django please assume I rather don't know something than it should be obvious. Thanks.
I think you need to:
1.- Create your custom Signup Class, for you to do the additional work
class SignupForm(forms.Form):
locality = forms.CharField(max_length=85)
voivodship = forms.CharField(max_length=20)
postcode = forms.IntegerField()
etc.
def signup(self, request, user):
# I think the profile will exist at this point based on
# your post_save_create. But if not, just create it here
if user.user_profile:
user.user_profile.locality = self.cleaned_data['locality']
user.user_profile.voivodship = self.cleaned_data['voivodship']
user.user_profile.postcode = self.cleaned_data['postcode']
...
user.user_profile.save()
2.- Set ACCOUNT_SIGNUP_FORM_CLASS = 'yourproject.yourapp.forms.SignupForm' to have allauth use your form
3.- Set SOCIALACCOUNT_AUTO_SIGNUP=False to ensure the form is presented even with social signup.
With some credits to davka I've managed to form a working solution which required creating UserProfile object inside signup() method of the SignupForm class, but because of database/model constrains it has be be filled with data during creation. The result:
class SignupForm(ModelForm):
first_name = CharField()
last_name = CharField()
class Meta:
model = UserProfile
exclude = ['user', 'phonenumber']
def signup(self, request, user):
user.first_name = self.cleaned_data['first_name']
user.last_name = self.cleaned_data['last_name']
profile, created = UserProfile.objects.get_or_create(
user=user, defaults={
'locality': self.cleaned_data['locality'],
'voivodship': self.cleaned_data['voivodship'],
'postcode': self.cleaned_data['postcode'],
'street': self.cleaned_data['street'],
'building': self.cleaned_data['building'],
'premises': self.cleaned_data['premises'],
})
if created: # This prevents saving if profile already exist
profile.save()
The solution doesn't totally fit into DRY principle, but shows the idea. Going further it could probably iterate over results matching model fields.
Two elements need to be set correctly in settings.py:
ACCOUNT_SIGNUP_FORM_CLASS = 'yourapp.forms.SignupForm' to enable this form in allauth
SOCIALACCOUNT_AUTO_SIGNUP = False this - contrary to the intuition - let the allauth display the form before finishing the signup if the user selected social sign in but don't have an account; it works safely if the account already exists (username and/or e-mail address depending on other settings) as just does't allow to finish registration (why they call it sign up?) and the user is forced to log in and link social account.