В чем причина моего sqlalchemy.InterfaceError и как я могу получить доступ к изображению, хранящемуся в виде шестнадцатеричного кода (используя подушку PIL) с помощью url_for ()? - PullRequest
0 голосов
/ 08 января 2020

Долгое время читатель, первый постер.

Я создаю основную c социальную сеть с python - flask и sqlalchemy. Это включает в себя фотографию профиля, фотографию заголовка и опубликовать фотографии. Я следовал учебному пособию Corey Schafer python flask sqlalchemy, чтобы реализовать функции заголовка и фотографии профиля, используя подушку PIL. Это было довольно просто и работает как ожидалось. Проблема заключается в попытке воспроизвести это в модели Post, в отличие от модели User.

Ниже приведена моя успешная реализация функций profile_img и header_img.

rout.py

def save_profile_img(form_profile_img):
    random_hex = secrets.token_hex(8)
    _, f_ext = os.path.splitext(form_profile_img.filename)
    profile_img_fn = random_hex + f_ext
    profile_img_path = os.path.join(app.root_path, "static/profile_pics", profile_img_fn)

    output_size = (225, 225)
    i = Image.open(form_profile_img)
    i.thumbnail(output_size)
    i.save(profile_img_path)

    return profile_img_fn

def save_header_img(form_header_img):
    random_hex = secrets.token_hex(8)
    _, f_ext = os.path.splitext(form_header_img.filename)
    header_img_fn = random_hex + f_ext
    header_img_path = os.path.join(app.root_path, "static/profile_pics", header_img_fn)
    form_header_img.save(header_img_path)

    output_size = (700, 700)
    i = Image.open(form_header_img)
    i.thumbnail(output_size)
    i.save(header_img_path)

    return header_img_fn

@app.route('/profile/<id>-<firstname>', methods=['GET', 'POST'])
@login_required
def profile(id, firstname):
    user = User.query.filter_by(id=id).first_or_404()
    firstname = User.query.filter_by(firstname=firstname).first_or_404()
    # edit profile form
    form = EditProfile()
    if form.validate_on_submit():
        if form.profile_img.data:
            profile_img_file = save_profile_img(form.profile_img.data)
            current_user.profile_img = profile_img_file
        if form.header_img.data:
            header_img_file = save_header_img(form.header_img.data)
            current_user.header_img = header_img_file
        current_user.firstname = form.firstname.data
        current_user.lastname = form.lastname.data
        current_user.email = form.email.data
        current_user.city = form.city.data
        db.session.commit()
        flash('Your account has been updated', 'success')
        return redirect(url_for('profile', id=current_user.id, firstname=current_user.firstname))
    elif request.method == 'GET':
        form.firstname.data = current_user.firstname
        form.lastname.data = current_user.lastname
        form.email.data = current_user.email
        form.city.data = current_user.city
    profile_img = url_for('static', filename='profile_pics/' + user.profile_img)
    header_img = url_for('static', filename='profile_pics/' + user.header_img)
    return render_template('profile.html', title='Profile', profile=profile, posts=posts, user=user, firstname=firstname, profile_img=profile_img, header_img=header_img, form=form)

model.py

class User(db.Model, UserMixin):
    id = db.Column(db.Integer, primary_key=True)
    firstname = db.Column(db.String(20), nullable=False)
    lastname = db.Column(db.String(20), nullable=False)
    profile_img = db.Column(db.String(50), nullable=False, default="default.png")
    header_img = db.Column(db.String(50), nullable=False, default="default_bg5.jpg")
    email = db.Column(db.String(50), nullable=False)
    password = db.Column(db.String(60), nullable=False)
    city = db.Column(db.String(50), nullable=False)
    posts = db.relationship('Post', backref='author', lazy=True)
    work_history = db.relationship('Employment', backref='author', lazy=True)
    education = db.relationship('Education', backref='author', lazy=True)
    about = db.relationship('About', backref='author', lazy=True)

    def __repr__(self):
        return f"User('{self.id}', '{self.firstname}', '{self.lastname}', '{self.email}', '{self.city}', '{self.profile_img}', '{self.header_img}')" 

forms.py

class EditProfile(FlaskForm):
    firstname = StringField('First Name', validators=[DataRequired(), Length(min=2, max=20)])
    lastname = StringField('Last Name',  validators=[DataRequired(), Length(min=2, max=20)])
    email = StringField('Email', validators=[DataRequired(), Email()])
    profile_img = FileField(validators=[FileAllowed(['jpg', 'png'])])
    header_img = FileField(validators=[FileAllowed(['jpg', 'png'])])
    city = SelectField('City', choices = [('Brampton', 'Brampton'), ('Etobicoke', 'Etobicoke'), ('Brampton', 'Brampton'), ('Markham', 'Markham'), ('Mississauga', 'Mississauga'), ('North York', 'North York'), ('Oakville', 'Oakville'), ('Ottawa', 'Ottawa'), ('Pickering', 'Pickering'), ('Scarborough', 'Scarborough'), ('Toronto', 'Toronto'), ('Vaughn', 'Vaughn')])
    submit = SubmitField('Update Account')

    def validate_email(self, email):
        if email.data != current_user.email:    
            user = User.query.filter_by(email=email.data).first()
            if user:
                raise ValidationError('Email is already in use.')

С помощью этой функции пользователь может нажать кнопку загрузки файла, выбрать файл JPG или PNG, просмотреть файл, отображаемый перед отправкой, и нажать кнопку «Отправить». Каждое изображение затем сохраняется в виде шестнадцатеричного файла в указанных файлах stati c. Я могу получить доступ к user.profile_img и user.header_img с помощью приведенных ниже операторов url_for(), которые находятся внизу маршрута, непосредственно перед оператором render template.

user = User.query.filter_by(id=id).first_or_404()
profile_img = url_for('static', filename='profile_pics/' + user.profile_img)
header_img = url_for('static', filename='profile_pics/' + user.header_img)

Затем я могу добавить эти в HTML с простым оператором jinja2 src="{{ profile_img }}" и src="{{ header_img }}"

ПРОБЛЕМА Что касается реализации функции post_img, я столкнулся с множеством проблем. На данный момент я не знаю, как получить доступ к изображениям с помощью оператора url_for, и я получаю ошибку sqlalchemyInterface при отправке. Однако файл действительно переименовывается в шестнадцатеричное и сохраняется в соответствующем файле stati c. У меня вопрос двоякий? Почему я получаю sqlalchemy.ex c .InterfaceError? и как я могу получить доступ к изображению через url_for с его post_ID, чтобы, если пользователь действительно загрузил изображение в свое сообщение, оно появилось, ниже будет form.py, rout.py, model.py и _postform. html.

rout.py

def save_post_img(form_post_img):
    random_hex = secrets.token_hex(8)
    _, f_ext = os.path.splitext(form_post_img.filename)
    post_img_fn = random_hex + f_ext
    post_img_path = os.path.join(app.root_path, "static/post_pics", post_img_fn)
    form_post_img.save(post_img_path)

    output_size = (700, 700)
    i = Image.open(form_post_img)
    i.thumbnail(output_size)
    i.save(post_img_path)

    return post_img_fn

@app.route('/')
@app.route('/index', methods=['GET', 'POST'])
def home():
    if current_user.is_authenticated == False:
        return redirect(url_for('register'))
    user = current_user
    profile_img = url_for('static', filename='profile_pics/' + user.profile_img)
    form = PostForm()
    if form.validate_on_submit():
        post = Post(content=form.content.data, post_img=form.post_img.data, author=current_user)
        if form.post_img.data:
            post_img_file = save_post_img(form.post_img.data)
            post_img = post_img_file
        db.session.add(post)
        db.session.commit()
        flash('Post Successful!', 'success')
        return redirect(url_for('profile', id=current_user.id, firstname=current_user.firstname))
    post_img = url_for('static', filename='post_pics/post_img.jpg')
    posts = Post.query.order_by(Post.date_posted.desc()).all()
    return render_template('index.html', posts=posts, profile_img=profile_img, form=form, user=user, post_img=post_img)

models.py

class Post(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    content = db.Column(db.String(1000), nullable=False)
    date_posted = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)  
    post_img = db.Column(db.String(50), nullable=True) 
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)

    def __repr__(self):
        return f"Post('{self.content}', '{self.post_img}', '{self.date_posted}')" 

forms.py

class PostForm(FlaskForm):
    content = TextAreaField('Content', validators=[DataRequired()])
    post_img= FileField(validators=[FileAllowed(['jpg', 'png'])])
    submit = SubmitField('Post')

_postform. html

<div class="content-section bg-light">
    <form novalidate action="" method="POST" enctype="multipart/form-data">
        {{ form.hidden_tag() }}
            <div class="form-group">
                    {% if form.content.errors %}
                    {{ form.content(placeholder="What's on your mind?", class="form-control form-control-lg is-invalid") }}
                    <div class="invalid-feedback">
                        {% for error in form.content.errors %}
                        <span>{{ error }}</span>
                        {% endfor %} 
                    </div>
                    {% else %}
                    {{ form.content(placeholder="What's on your mind?", class="form-control form-control-lg") }} 
                    {% endif %}    
            </div>
            <div class="container">
            <div class="row">
                <label for="file-upload" class="post-file-upload">
                    <i class='far fa-image fa-2x'></i>
                </label>
                    {{ form.post_img(placeholder='JPG or PNG', id="file-upload", type="file") }}
                {% if form.post_img.errors %}
                {% for error in form.post_img.errors %}
                    <span class='text-danger'>{{ error }}</span><br>
                {% endfor %}
                {% endif %}

            <div class="form-group">
                {{ form.submit(class="btn custom-btn") }}
            </div>
        </div>

        </div>

    </form>
</div> 

Ошибка трассировки

sqlalchemy.exc.InterfaceError
sqlalchemy.exc.InterfaceError: (sqlite3.InterfaceError) Error binding parameter 2 - probably unsupported type.
[SQL: INSERT INTO post (content, date_posted, post_img, user_id) VALUES (?, ?, ?, ?)]
[parameters: ('here is a pic, i hope..', '2020-01-07 21:02:46.223754', <FileStorage: '0008beb24a17e995.jpg' ('image/jpeg')>, 2)]
(Background on this error at: http://sqlalche.me/e/rvf5)

Traceback (most recent call last)
File "/Users/joshmolot/bluecaller/venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py", line 1245, in _execute_context
self.dialect.do_execute(
File "/Users/joshmolot/bluecaller/venv/lib/python3.8/site-packages/sqlalchemy/engine/default.py", line 581, in do_execute
cursor.execute(statement, parameters)
The above exception was the direct cause of the following exception:
File "/Users/joshmolot/bluecaller/venv/lib/python3.8/site-packages/flask/app.py", line 2463, in __call__
return self.wsgi_app(environ, start_response)
File "/Users/joshmolot/bluecaller/venv/lib/python3.8/site-packages/flask/app.py", line 2449, in wsgi_app
response = self.handle_exception(e)
File "/Users/joshmolot/bluecaller/venv/lib/python3.8/site-packages/flask/app.py", line 1866, in handle_exception
reraise(exc_type, exc_value, tb)
File "/Users/joshmolot/bluecaller/venv/lib/python3.8/site-packages/flask/_compat.py", line 39, in reraise
raise value
File "/Users/joshmolot/bluecaller/venv/lib/python3.8/site-packages/flask/app.py", line 2446, in wsgi_app
response = self.full_dispatch_request()
File "/Users/joshmolot/bluecaller/venv/lib/python3.8/site-packages/flask/app.py", line 1951, in full_dispatch_request
rv = self.handle_user_exception(e)
File "/Users/joshmolot/bluecaller/venv/lib/python3.8/site-packages/flask/app.py", line 1820, in handle_user_exception
reraise(exc_type, exc_value, tb)
File "/Users/joshmolot/bluecaller/venv/lib/python3.8/site-packages/flask/_compat.py", line 39, in reraise
raise value
File "/Users/joshmolot/bluecaller/venv/lib/python3.8/site-packages/flask/app.py", line 1949, in full_dispatch_request
rv = self.dispatch_request()
File "/Users/joshmolot/bluecaller/venv/lib/python3.8/site-packages/flask/app.py", line 1935, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
File "/Users/joshmolot/bluecaller/bluecaller/routes.py", line 41, in home
db.session.commit()
File "/Users/joshmolot/bluecaller/venv/lib/python3.8/site-packages/sqlalchemy/orm/scoping.py", line 162, in do
return getattr(self.registry(), name)(*args, **kwargs)
File "/Users/joshmolot/bluecaller/venv/lib/python3.8/site-packages/sqlalchemy/orm/session.py", line 1027, in commit
self.transaction.commit()
File "/Users/joshmolot/bluecaller/venv/lib/python3.8/site-packages/sqlalchemy/orm/session.py", line 494, in commit
self._prepare_impl()
File "/Users/joshmolot/bluecaller/venv/lib/python3.8/site-packages/sqlalchemy/orm/session.py", line 473, in _prepare_impl
self.session.flush()
File "/Users/joshmolot/bluecaller/venv/lib/python3.8/site-packages/sqlalchemy/orm/session.py", line 2470, in flush
self._flush(objects)
File "/Users/joshmolot/bluecaller/venv/lib/python3.8/site-packages/sqlalchemy/orm/session.py", line 2608, in _flush
transaction.rollback(_capture_exception=True)
File "/Users/joshmolot/bluecaller/venv/lib/python3.8/site-packages/sqlalchemy/util/langhelpers.py", line 68, in __exit__
compat.reraise(exc_type, exc_value, exc_tb)
File "/Users/joshmolot/bluecaller/venv/lib/python3.8/site-packages/sqlalchemy/util/compat.py", line 153, in reraise
raise value
File "/Users/joshmolot/bluecaller/venv/lib/python3.8/site-packages/sqlalchemy/orm/session.py", line 2568, in _flush
flush_context.execute()
File "/Users/joshmolot/bluecaller/venv/lib/python3.8/site-packages/sqlalchemy/orm/unitofwork.py", line 422, in execute
rec.execute(self)
File "/Users/joshmolot/bluecaller/venv/lib/python3.8/site-packages/sqlalchemy/orm/unitofwork.py", line 586, in execute
persistence.save_obj(
File "/Users/joshmolot/bluecaller/venv/lib/python3.8/site-packages/sqlalchemy/orm/persistence.py", line 239, in save_obj
_emit_insert_statements(
File "/Users/joshmolot/bluecaller/venv/lib/python3.8/site-packages/sqlalchemy/orm/persistence.py", line 1136, in _emit_insert_statements
result = cached_connections[connection].execute(
File "/Users/joshmolot/bluecaller/venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py", line 982, in execute
return meth(self, multiparams, params)
File "/Users/joshmolot/bluecaller/venv/lib/python3.8/site-packages/sqlalchemy/sql/elements.py", line 287, in _execute_on_connection
return connection._execute_clauseelement(self, multiparams, params)
File "/Users/joshmolot/bluecaller/venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py", line 1095, in _execute_clauseelement
ret = self._execute_context(
File "/Users/joshmolot/bluecaller/venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py", line 1249, in _execute_context
self._handle_dbapi_exception(
File "/Users/joshmolot/bluecaller/venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py", line 1476, in _handle_dbapi_exception
util.raise_from_cause(sqlalchemy_exception, exc_info)
File "/Users/joshmolot/bluecaller/venv/lib/python3.8/site-packages/sqlalchemy/util/compat.py", line 398, in raise_from_cause
reraise(type(exception), exception, tb=exc_tb, cause=cause)
File "/Users/joshmolot/bluecaller/venv/lib/python3.8/site-packages/sqlalchemy/util/compat.py", line 152, in reraise
raise value.with_traceback(tb)
File "/Users/joshmolot/bluecaller/venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py", line 1245, in _execute_context
self.dialect.do_execute(
File "/Users/joshmolot/bluecaller/venv/lib/python3.8/site-packages/sqlalchemy/engine/default.py", line 581, in do_execute
cursor.execute(statement, parameters)
sqlalchemy.exc.InterfaceError: (sqlite3.InterfaceError) Error binding parameter 2 - probably unsupported type.
[SQL: INSERT INTO post (content, date_posted, post_img, user_id) VALUES (?, ?, ?, ?)]
[parameters: ('here is a pic, i hope..', '2020-01-07 21:02:46.223754', <FileStorage: '0008beb24a17e995.jpg' ('image/jpeg')>, 2)]
(Background on this error at: http://sqlalche.me/e/rvf5)

* Обратите внимание, что в файле rout.py для post_img = url_for('static', filename='post_pics/post_img.jpg') установлено тестовое изображение с именем post_img.jpg. Это изображение появляется при использовании оператора jinja2 {{ post_img }}. Это потому, что я не знаю, как получить доступ к post_img, так что это жесткий код img. Я пробовал несколько различных утверждений, каждое из которых дает мне какую-то ошибку.

Что касается ошибки sqlalchemy, IMO, она должна исходить от маршрута где-то. Моя модель Post и модель User используют те же выражения для profile / header_img, что и post_img. Реализация подушки работает. Если я изменю маршрут, как показано ниже, изображение все еще сохраняется в файле stati c, и ошибки не возникает. До изменения

form = PostForm()
if form.validate_on_submit():
        post = Post(content=form.content.data, post_img=form.post_img.data, author=current_user)
        if form.post_img.data:
            post_img_file = save_post_img(form.post_img.data)
            post_img = post_img_file
        db.session.add(post)
        db.session.commit()
        flash('Post Successful!', 'success')
        return redirect(url_for('profile', id=current_user.id, firstname=current_user.firstname))

После изменения

form = PostForm()
    if form.validate_on_submit():
        post = Post(content=form.content.data, author=current_user)
        db.session.add(post)
        db.session.commit()
        flash('Post Successful!', 'success')
        return redirect(url_for('profile', id=current_user.id, firstname=current_user.firstname))

Любая помощь и понимание будут с благодарностью!

Ответы [ 2 ]

0 голосов
/ 12 января 2020

Я решил это! Спасибо за подсказку с точки зрения объекта хранения файлов. Сначала я получил изображение, которое нужно сохранить в модели Post. Я сделал это, сказав post.post_img = post_img_file Я подтвердил, что он был сохранен в БД с >>> p = Posts.query.all(), как только я добился, что получаю ошибку "не могу объединить объекты 'str' и 'NoneType'", поэтому я удалил url_for в маршрутах и просто сделал следующий оператор jinja2 в моем html {{ post.post_img }}, и это вернуло шестнадцатеричный файл. Поэтому я написал инструкцию url_for, учитывающую тип None, хранящийся в БД. Я написал это в моих формах:

form = PostForm()
    if form.validate_on_submit():
        post = Post(content=form.content.data, post_img=form.post_img.data, author=current_user)
        if form.post_img.data:
            post_img_file = save_post_img(form.post_img.data)
            post.post_img = post_img_file
        db.session.add(post)
        db.session.commit()
        flash('Post Successful!', 'success')
        return redirect(url_for('home'))
    # list of posts by descending time
    posts = Post.query.order_by(Post.date_posted.desc()).all()
    for post in posts:
        if post.post_img != None:
            post_img = url_for('static', filename='post_pics/' + post.post_img)
    return render_template('index.html', posts=posts, post_img=post_img, profile_img=profile_img, form=form, user=user)

, это работало без ошибок, в маршруте, но jinja2 все еще выдавал мне ошибку NoneType, не связанную с конкатенацией, поэтому я должен был учитывать ее в html и сделал это со следующим:

 {% if post.post_img != None %}
      <img class='img-fluid mx-auto post-img' src="{{ url_for('static', filename='post_pics/' + post.post_img) }}"> 
        {% endif %}
0 голосов
/ 08 января 2020

Краткий обзор (потому что здесь много) ...

[parameters: ('here is a pic, i hope..', '2020-01-07 21:02:46.223754', <FileStorage: '0008beb24a17e995.jpg' ('image/jpeg')>, 2)]

Обратите внимание, что вы на самом деле устанавливаете post_img как FileStorage объект, а не то, что я Подозреваю, что вам нужно быть строкой, или в предыдущих примерах возвращаемое значение save_post_img().

Я подозреваю, что в последнем routes.py вам нужно изменить:

post = Post(content=form.content.data, post_img=form.post_img.data, author=current_user)
if form.post_img.data:
    post_img_file = save_post_img(form.post_img.data)
    post_img = post_img_file
db.session.add(post)

К чему-то вроде:

if form.post_img.data:
    post_img_file = save_post_img(form.post_img.data)
    post_img = post_img_file
post = Post(content=form.content.data, post_img=post_img_file, author=current_user)
db.session.add(post)

Так что вы создаете post после того, как получите возврат от save_post_img().

Это предположение. Надеюсь, это направит вас как минимум в правильном направлении.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...