FLASK在提交后导致奇怪的IntegrityError违反唯一约束

时间:2018-07-16 12:18:53

标签: python sqlalchemy

更新/澄清

我确认,这种奇怪的行为仅发生在macOS机器上,将所有内容移至Windows机器(使用sqlite并执行新的init并迁移)不会导致错误...在High Sierra框中执行相同的操作确实会导致奇数错误。

是否有人熟悉Windows上的sqlalchemy与macOS之间的已知区别可能会有所帮助?


简短版本...尝试将任何条目提交到数据库后,即使表中根本不存在任何条目,我也收到完整性错误(唯一约束)...为什么?

详细信息

我已经使用postgresql和sqlalchemy构建了一个FLASK项目(大致基于Miguel Grinberg Flask Maga教程),前端有一个页面用于向用户注册确认电子邮件( fine )...为了节省时间,我编写了一条路线(请参阅下文),将已确认的用户预加载到Users数据库中,该用户是Users表中的 ONLY 用户,而我只能访问一次路线。

成功提交后,我得到一个 IntegrityError “重复的键值违反了唯一约束”。此路由只会将一个用户添加到现有的 EMPTY 用户表中。数据已成功保存到DB,用户可以登录,但会引发错误。我遇到了类似的错误(请参见下文),但我以此路线为例,因为它比我所写的其他视图短。

引起唯一约束错误的路由示例

@main.route('/popme')
#@login_required
def popme():
    ## add user
    u1 = User()
    u1.email = 'user@domain.com'
    u1.username = 'someuser'
    u1.password_hash = 'REMOVED'
    u1.confirmed = '1'
    u1.role_id = 3
    u1.name = 'Some User'
    db.session.add(u1)
    db.session.commit()
    flash('User someuser can now login!')
    return redirect(url_for('main.index'))

在将整个项目从Windows机器移至MacOS机器后,我才开始出现此错误。我在虚拟环境中运行Python 3.6,如果我使用sqlite3或postgresql,则会发生此错误。

我写了一条更长的路由,成功地预填充了约20个其他表(最后在commit()上执行,所有数据都存储在DB中),但是我收到一个IntegrityError错误消息,“重复的键值被违反唯一约束”。我已经销毁了数据库,进行了初始化,然后迁移了……每次抛出commit()调用IntegrityError时,每次在不同的表上,都没有明显的理由。

以下是用户模型

class User(UserMixin, db.Model):
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(64), unique=True, index=True)
    username = db.Column(db.String(64), unique=True, index=True)
    password_hash = db.Column(db.String(128))
    confirmed = db.Column(db.Boolean, default=False)
    role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
    name = db.Column(db.String(64))
    last_seen = db.Column(db.DateTime(), default=datetime.utcnow)

    def ping(self):
        self.last_seen = datetime.utcnow()
        db.session.add(self)

    @property
    def password(self):
        raise AttributeError('password is not a readable attribute')

    @password.setter
    def password(self, password):
        self.password_hash = generate_password_hash(password)

    def verify_password(self, password):
        return check_password_hash(self.password_hash, password)

    def generate_confirmation_token(self, expiration=3600):
        s = Serializer(current_app.config['SECRET_KEY'], expiration)
        return s.dumps({'confirm': self.id})

    def confirm(self, token):
        s = Serializer(current_app.config['SECRET_KEY'])
        try:
            data = s.loads(token)
        except:
            return False
        if data.get('confirm') != self.id:
            return False
        self.confirmed = True
        db.session.add(self)
        return True

    def generate_reset_token(self, expiration=3600):
        s = Serializer(current_app.config['SECRET_KEY'], expiration)
        return s.dumps({'reset': self.id})

    def reset_password(self, token, new_password):
        s = Serializer(current_app.config['SECRET_KEY'])
        try:
            data = s.loads(token)
        except:
            return False
        if data.get('reset') != self.id:
            return False
        self.password = new_password
        db.session.add(self)
        return True

    def generate_email_change_token(self, new_email, expiration=3600):
        s = Serializer(current_app.config['SECRET_KEY'], expiration)
        return s.dumps({'change_email': self.id, 'new_email': new_email})

    def change_email(self, token):
        s = Serializer(current_app.config['SECRET_KEY'])
        try:
            data = s.loads(token)
        except:
            return False
        if data.get('change_email') != self.id:
            return False
        new_email = data.get('new_email')
        if new_email is None:
            return False
        if self.query.filter_by(email=new_email).first() is not None:
            return False
        self.email = new_email
        db.session.add(self)
        return True

    def can(self, permissions):
        return self.role is not None and (self.role.permissions & permissions) == permissions

    def is_administrator(self):
        return self.can(Permission.ADMINISTRATOR)

    def __init__(self, **kwargs):
        super(User, self).__init__(**kwargs)
        if self.role is None:
            if self.email == current_app.config['FLASKY_ADMIN']:
                self.role = Role.query.filter_by(permissions=0xff).first()
            if self.role is None:
                self.role = Role.query.filter_by(default=True).first()

    def __repr__(self):
        return '<User %r>' % self.username

我已经尝试过Sql-alchemy Integrity error,但据我了解sqlalchemy会自动增加主键。

更新的完整性错误

sqlalchemy.exc.IntegrityError: (psycopg2.IntegrityError) duplicate key value violates unique constraint "ix_users_email"
DETAIL:  Key (email)=(worldbmd@gmail.com) already exists.
 [SQL: 'INSERT INTO users (email, username, password_hash, confirmed, role_id, name, last_seen) VALUES (%(email)s, %(username)s, %(password_hash)s, %(confirmed)s, %(role_id)s, %(name)s, %(last_seen)s) RETURNING users.id'] [parameters: {'email': 'user@domain.com', 'username': 'someuser', 'password_hash': 'REMOVED', 'confirmed': '1', 'role_id': 1, 'name': 'Some User', 'last_seen': datetime.datetime(2018, 7, 16, 17, 27, 13, 451593)}]

1 个答案:

答案 0 :(得分:-1)

完整性错误是由于尝试使用非唯一属性将水泡添加到水泡表中引起的。我认为您的模型如下所示:

class Blister(db.Model):
    __tablename__ = 'blisters'
    id = db.Column(db.Integer, primary_key=True)
    name= db.Column(db.String(64), unique=True)
    notes= db.Column(db.String(64))
    cost= db.Column(db.Float)

您正在尝试添加名称为Small round dome的水泡,该水泡已经存在于数据库的水泡表中,因此会导致完整性错误。