Долгое время читатель, первый постер.
Я создаю основную 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))
Любая помощь и понимание будут с благодарностью!