Solucionado (ver solução)
Solucionado
(ver solução)
4
respostas

Gravar imagens em banco de dados e depois recuperá-las para serem exibidas

Fiz uns ajustes no código do projeto jogoteca, para gravar arquivos de imagem diretamente na base de dados e recuperar essas imagens da base de dados e exibi-las em uma página, pesquisei muito mas não achei um exemplo completo, só encontrei partes de códigos que fui montando, funcionou mas não sei se a forma que fiz é a melhor ou se tem outro jeito melhor para fazer isso.

Segue aqui os trechos de código que fazem o processo de gravar imagens em banco de dados e depois recuperá-las para serem exibidas:

models.py

class Jogos(db.Model):
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    nome = db.Column(db.String(50), nullable=False)
    categoria = db.Column(db.String(40), nullable=False)
    console = db.Column(db.String(20), nullable=False)
    
    # Campo imagem
    imagem = db.Column(db.LargeBinary, nullable=True)

    def __repr__(self):
        return '<Name %r>' % self.name

views.py

from flask import render_template, request, redirect, session, flash, url_for
from jogoteca import app, db
from models import Jogos, Usuarios

# os imports abaixos foram feitos para lidar com as imagens
from io import BytesIO
import base64
import io

@app.route('/criar', methods=['POST',])
def criar():
    nome = request.form['nome']
    categoria = request.form['categoria']
    console = request.form['console'] 
    
    # Aqui pego o arquivo carregado na página novo.html
    # E jogo na variavel "file" que é do tipo <class 'werkzeug.datastructures.file_storage.FileStorage'>
    file = request.files['arquivo']
    # Aqui é feito o read() do arquivo para variavel imagem.
    # O file.read() irá retornar a imagem no formato de bytes para ser inserida no campo imagem da tabela jogos
    imagem = file.read()
    jogo = Jogos(nome=nome, categoria=categoria, console=console, imagem=imagem)

    db.session.add(jogo)
    db.session.commit()
    return redirect(url_for('lista'))

@app.route('/lista')
def lista():
     if session["usuario_logado"] != None:
         flash(f'Usuário {session["usuario_logado"]} logado com sucesso!!!', 'info')

         # Aqui é feita a parte de busca dos jogos e o decode do arquivo de imagem
         lista_jogos = Jogos.query.order_by(Jogos.id)
         lista = []
         for jogo in lista_jogos:
             if jogo.imagem != None:
                jogo.imagem = base64.b64encode(io.BytesIO(jogo.imagem).getvalue()).decode()
             lista.append(jogo)
         return render_template('lista.html',titulo='Lista de Jogos', jogos=lista)

     else:
         flash(f'Usuário não esta logado!!!', 'error')
         session["rota"] = 'lista'
         return redirect(url_for('login'))	
...

novo.html

{% extends "template.html" %}
{% block conteudo %}
     <form action="{{ url_for('criar') }}" method="post" enctype="multipart/form-data">
     
         <input type="file" name="arquivo" accept=".jpeg, .jpg">
         
        <fieldset>
          <div class="form-group">
            <label for="nome">Nome</label>
            <input type="text" id="nome" name="nome" class="form-control">
          </div>
          <div class="form-group">
            <label for="categoria">Categoria</label>
            <input type="text" id="categoria" name="categoria" class="form-control">
          </div>
          <div class="form-group">
            <label for="console">Console</label>
            <input type="text" id="console" name="console" class="form-control">
          </div>
          <button type="submit" class="btn btn-primary btn-salvar">Salvar</button>
        </fieldset>
     </form>
{% endblock %}

lista.html

{% extends "template.html" %}
{% block conteudo %}         
        <table class="table table-striped table-responsive table-bordered">
            <thead class="thead-default">
                <tr>
                    <th align="left">Capa</th>
                    <th align="left">Nome</th>
                    <th align="left">Categoria</th>
                    <th align="left">Console</th>
                    <th align="center" colspan="2">Ações</th>
                </tr>
            </thead>
            <tbody>
                {% for jogo in jogos%}
                <tr>
                    <td width="15%"><img src="data:image/jpeg;base64,{{ jogo.imagem }}" class="img-fluid img-thumbnail" width="100" height="200" onerror="this.style.display='none'"></td>
                    <td>{{ jogo.nome }}</td>
                    <td>{{ jogo.categoria }}</td>
                    <td>{{ jogo.console }}</td>
                </tr>
                {% endfor %}
            </tbody>
        </table>
{% endblock %}

4 respostas

Oi Luisandro, tudo bem?

Pelo que pude analisar, você fez um ótimo trabalho ao implementar a funcionalidade de gravação e recuperação de imagens no banco de dados. A sua abordagem está correta e é uma das maneiras de lidar com imagens em aplicações web.

No entanto, é importante ressaltar que, embora seja possível armazenar imagens diretamente no banco de dados, essa prática pode levar a alguns problemas, como o aumento do tamanho do banco de dados e a diminuição do desempenho. Se o projeto for maior.

Mas para o projeto da aula, sua solução foi muito boa!

Uma alternativa seria armazenar as imagens em um sistema de arquivos e apenas salvar o caminho para a imagem no banco de dados. Dessa forma, você poderia recuperar a imagem usando o caminho armazenado no banco de dados.

Por exemplo, você poderia criar uma pasta chamada 'imagens' no seu projeto e salvar as imagens lá. O campo 'imagem' na sua tabela Jogos poderia ser alterado para armazenar uma string representando o caminho para a imagem, em vez do binário da imagem.

class Jogos(db.Model):
    ...
    # Campo imagem
    imagem = db.Column(db.String(255), nullable=True)
    ...

No método 'criar', você salvaria a imagem na pasta 'imagens' e armazenaria o caminho para a imagem no banco de dados.

@app.route('/criar', methods=['POST',])
def criar():
    ...
    # Aqui pego o arquivo carregado na página novo.html
    file = request.files['arquivo']
    # Aqui é feito o save() do arquivo para a pasta imagens.
    file.save(os.path.join('imagens', file.filename))
    imagem = os.path.join('imagens', file.filename)
    jogo = Jogos(nome=nome, categoria=categoria, console=console, imagem=imagem)
    ...

Na função 'lista', você não precisaria fazer o decode da imagem, pois estaria trabalhando com o caminho para a imagem.

@app.route('/lista')
def lista():
    ...
    lista_jogos = Jogos.query.order_by(Jogos.id)
    lista = []
    for jogo in lista_jogos:
        lista.append(jogo)
    return render_template('lista.html',titulo='Lista de Jogos', jogos=lista)
    ...

E no template 'lista.html', você usaria o caminho para a imagem no atributo 'src' da tag 'img'.

<td width="15%"><img src="{{ url_for('static', filename=jogo.imagem) }}" class="img-fluid img-thumbnail" width="100" height="200" onerror="this.style.display='none'"></td>

Essa é apenas uma ideia claro, você pode precisar fazer modificaçõe e pesquisar mais sobre se quiser :D

Parabéns pelo seu trabalho mais uma vez!

Um abraço e bons estudos.

O objetivo de gravar em banco é que tenho um projeto onde possuo documentos sigilosos que não podem ficar em pastas do servidor eu preciso que fiquem no banco de dados.

O trecho de código abaixo que recupera as imagem é o que não parece bom, pois preciso fazer uma interação nos resultados pra conveter em base 64 e depois fazer o decode para que a imagem seja exibida.

Queria saber se existe outra forma de retornar a imagem do banco já pronta pra ser exibida na página html sem precisar fazer essa iteração para cada registro?

         lista_jogos = Jogos.query.order_by(Jogos.id)
         lista = []
         for jogo in lista_jogos:
             if jogo.imagem != None:
                jogo.imagem = base64.b64encode(io.BytesIO(jogo.imagem).getvalue()).decode()
             lista.append(jogo)
         return render_template('lista.html',titulo='Lista de Jogos', jogos=lista)
solução!

Oi Luisandro, tudo bem?

Entendo suas preocupações em relação à eficiência do processo de recuperação e à segurança dos dados sigilosos.

Vou fornecer algumas sugestões para otimizar e aprimorar o seu código, bem como abordar a sua pergunta sobre evitar a iteração e a conversão de base64 para cada imagem recuperada.

1. Armazenamento de Imagens no Banco de Dados

Você está armazenando as imagens diretamente no banco de dados como dados binários (campo imagem do tipo LargeBinary). Esta é uma abordagem válida, especialmente se você precisa armazenar dados sigilosos e confidenciais. No entanto, você pode melhorar a eficiência da recuperação das imagens da seguinte maneira:

2. Evitar Conversão de Base64 na Recuperação

No seu código atual, você está convertendo as imagens em base64 no momento da recuperação para exibi-las no HTML. Isso pode ser otimizado, evitando essa conversão repetitiva. Você pode criar uma rota específica para servir as imagens diretamente do banco de dados em seu formato binário original.

Vamos criar uma nova rota para servir as imagens:

from flask import send_file

@app.route('/imagem/<int:jogo_id>')
def servir_imagem(jogo_id):
    jogo = Jogos.query.get(jogo_id)
    if jogo and jogo.imagem:
        return send_file(io.BytesIO(jogo.imagem), mimetype='image/jpeg')
    else:
        # Trate o caso em que a imagem não foi encontrada
        pass

Agora, no seu template 'lista.html', você pode referenciar diretamente essa rota para exibir as imagens sem a necessidade de decodificação em base64:

<td width="15%"><img src="{{ url_for('servir_imagem', jogo_id=jogo.id) }}" class="img-fluid img-thumbnail" width="100" height="200" onerror="this.style.display='none'"></td>

Com essa abordagem, você estará servindo as imagens diretamente do banco de dados sem a necessidade de decodificação base64 repetitiva.

3. Melhorias no Armazenamento

No entanto, ainda podemos melhorar o armazenamento das imagens. Se você deseja manter as imagens no banco de dados por motivos de segurança, é uma boa prática criar um campo para armazenar o tipo de conteúdo (MIME type) da imagem. Isso pode ser útil ao servir as imagens e garantir que elas sejam exibidas corretamente.

Além disso, você pode considerar a compactação das imagens antes de armazená-las no banco de dados para economizar espaço. Você pode usar bibliotecas como Pillow (PIL) para comprimir as imagens antes de salvá-las no banco de dados e descompactá-las ao servi-las.

Aqui está um exemplo de como você pode melhorar a estrutura da sua tabela Jogos:

class Jogos(db.Model):
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    nome = db.Column(db.String(50), nullable=False)
    categoria = db.Column(db.String(40), nullable=False)
    console = db.Column(db.String(20), nullable=False)

    # Campo imagem
    imagem = db.Column(db.LargeBinary, nullable=True)
    mimetype = db.Column(db.String(50))  # Campo para armazenar o tipo de conteúdo (MIME type)

4. Compactação e Descompactação de Imagens

Para compactar e descompactar imagens, você pode usar a biblioteca Pillow. Antes de salvar a imagem no banco de dados, compacte-a e defina o tipo de conteúdo (MIME type) corretamente:

from PIL import Image
import io

@app.route('/criar', methods=['POST'])
def criar():
    nome = request.form['nome']
    categoria = request.form['categoria']
    console = request.form['console']

    file = request.files['arquivo']
    imagem = file.read()

    # Compactar a imagem antes de salvar
    with Image.open(io.BytesIO(imagem)) as img:
        img.thumbnail((300, 300))  # Redimensionar a imagem, se necessário
        output = io.BytesIO()
        img.save(output, format='JPEG')
        imagem = output.getvalue()

    mimetype = file.mimetype  # Obtenha o tipo de conteúdo da imagem

    jogo = Jogos(nome=nome, categoria=categoria, console=console, imagem=imagem, mimetype=mimetype)

    db.session.add(jogo)
    db.session.commit()
    return redirect(url_for('lista'))

5. Recuperação de Imagens Compactadas

Quando você recuperar a imagem, lembre-se de configurar o tipo de conteúdo correto ao enviá-la para o navegador:

@app.route('/imagem/<int:jogo_id>')
def servir_imagem(jogo_id):
    jogo = Jogos.query.get(jogo_id)
    if jogo and jogo.imagem:
        return send_file(io.BytesIO(jogo.imagem), mimetype=jogo.mimetype)
    else:
        # Trate o caso em que a imagem não foi encontrada
        pass

Com essas melhorias, você estará armazenando as imagens de forma mais eficiente e as servindo diretamente do banco de dados quando necessário. Além disso, o tipo de conteúdo apropriado será definido para garantir que as imagens sejam exibidas corretamente no navegador.

Um abraço.

Obrigado pelo retorno vou testar estes ajustes