MultiUpload no Web2py

MultiUpload no Web2py

Upload de vários arquivos no mesmo form

Fala pessoal, tudo certo? Espero que sim!

Recentemente me vi frente à necessidade de criar um projeto no Web2py com upload de vários arquivos em uma página via ajax e após algumas buscas percebi que, apesar de haver material sobre o assunto, ainda é um pouco escasso e cada tutorial trata do assunto abordando de maneira diferente, diante disso eu peguei um pouco de cada artigo que li e resolvi montar um que atendesse melhor à minha demanda.

Talk is cheap, show me the code

Então sem mais conversa, vamos ao código:

Primeiro você precisa do Web2py rodando localmente, abra a interface administrativa e crie um novo projeto e chame como quiser (afinal o projeto é seu! ;-)).

O modelo

Agora vamos criar nosso modelo:

Para manter as coisas o mais simples possível, vou criar o modelo no arquivo models/db.py mesmo, mas você pode criar em qualquer arquivo dentro da pasta models.

db.define_table('album',
Field('titulo'),
Field('data_fotos', 'date'),
Field('fotos', 'list:reference arquivos'))
db.album.fotos.requires = IS_IN_DB(db, 'arquivos.id', 'arquivos.arquivo', multiple=True)
db.define_table('arquivos',
Field('arquivo', 'upload', autodelete=True))

O código acima cria as duas tabelas que iremos utilizar em nossa aplicação, a primeira tabela (linha 1 a 4) é referente ao álbum de fotos, onde poderemos inserir um titulo(linha 2), uma data (linha 3) e as fotos (linha 4), note que temos uma lista que referencia a tabela arquivos, na linha 5 estamos dizendo que o campo fotos deve estar relacionado com a tabela arquivos através do campo id e que será um campo de seleção multipla. A segunda tabela é bem simples e só tem um campo do tipo upload que é um tipo especial do web2py para lidar com upload de arquivos.

O controlador

O passo seguinte é criar o controlador, para isso basta criarmos um arquivo na pasta controllers, aqui chamarei de album.py e nele colocaremos o código abaixo:

# coding: utf8
def index():
  grid = SQLFORM.grid(db.album, user_signature=False)
  return dict(grid=grid)

def upload_file():
  try:
    id = db.arquivos.insert(arquivo=db.arquivos.arquivo.store(request.vars.file,request.vars.filename))
    return id
  except:
    return dict(message=T('Upload error'))

def delete_file():
  try:
    name = request.args[0]
    db(db.arquivos.arquivo==name).delete()
    return name
  except:
    return 'erro'

Temos 3 funções no controlador index, upload_file e delete_file, a primeira cria uma grid com as funções CRUD (Create, Read, Update, Delete) baseada nas regras que definimos no nosso modelo anteriormente, a segunda função tratará nossos uploads via ajax, e a terceira obviamente servirá para excluirmos os arquivos via ajax posteriormente.

Ao executarmos a aplicação e acessarmos a url http://localhost:8000/multiupload/album/index veremos a tela de cadastro dos Álbuns, clicando no botão (+Add) teremos a seguinte tela: ser

MultiUpload-no-Web2py-01

Quase tudo certo, porém falta justamente o campo de upload dos arquivos, motivo desse tutorial 😉

A Visualização

Vamos agora criar o arquivo da view onde será gerado nosso HTML, crie um arquivo chamado index.html dentro da pasta views, e insira o código abaixo:

{{response.files.extend([URL('static','css/custom.css'),URL('static','js/pekeUpload.js'),URL('static','js/album.js')])}}
{{extend 'layout.html'}}
{{=grid}}

No código acima estamos adicionando referência aos arquivos css e javascript do plugin responsável pelos uploads e o arquivo album.js que criaremos mais pra frente (linha 1).

O Plugin

Vamos agora baixar os arquivos do plugin que usaremos para gerenciar nossos uploads via ajax, clique aqui e baixe os arquivos custom.css e pekeUpload.js e salve nas pastas static/css e static/js.

O Javascript

Agora já temos quase tudo que precisaremos, falta somente o arquivo album.js que devemos criar dentro da pasta static/js com o seguinte código:

$(document).ready(function(){
  //Função para listar os arquivos existentes
  $('#album_fotos option:selected').each(function(){
    var file = $(this).text();
    var p = '

';
    p += '<a class="btn btn-success btn-download" href="http://'+window.location.host+'/multiupload/default/download/'+file+'">Arquivo</a>';
    p += '<button class="btn btn-danger excluir" type="button"></button>

';
    $('#album_fotos').parent().append(p);
  });
  //Função para excluir os arquivos
  $('button.excluir').click(function(){
      var arq = $(this).parent().attr('id');
      $(this).parent().hide();
      $.ajax({
          url: 'http://'+window.location.host+'/multiupload/album/delete_file/'+arq,
          type: 'post',
          data: arq,
          success: function(data){
            if (data != 'erro'){
              $('#album_fotos option:selected').each(function(){
                  var file = $(this).text();
                  if(file == data){
                  $(this).remove();
                  }
              });
            }else{
              alert('Erro ao excluir arquivo!');
            }
        },
        error: function(){
          alert('Erro ao excluir arquivo, verifique sua conexão!');
        }
      });
  });
  //Função para tratar os uploads via ajax
  arquivos = $('#album_fotos');
  arquivos.parent().append('<input class="upload" id="file_upload" type="file" name="file_upload" />');
  arquivos.hide();
  $('#file_upload').pekeUpload({
    btnText: 'Adicionar',
    url: 'http://' + window.location.host + '/multiupload/album/upload_file',
    theme: 'bootstrap',
    showErrorAlerts: false,
    onFileError: function(file,error){
      $('#album_fotos').append($('
', {value: error, text: error, selected: true}));      var div = '</pre>
<div class="alert alert-success">';
 div += '<button class="close" type="button" data-dismiss="alert">×</button>';
 div += '
'+file['name']+' adicionado com sucesso!</div>
<pre>'      $('div.pekecontainer').append(div);    }  });});

O código acima faz basicamente o seguinte, a primeira função (linhas 3 a 9) transforma cada arquivo selecionado no selectbox em um link para download e um botão para realizar a exclusão do mesmo.

A segunda função (linhas 11 a 34) faz uma chamada ajax para a função delete_file do controlador álbum e remove o arquivo da lista de seleção.

A terceira função (linhas 36 a 51) esconde o controle selectbox que lista os arquivos relacionados com o álbum, insere um botão de upload e passa algumas configurações ao pekeUpload (39 a 50).

Alguns detalhes muito importantes que devemos considerar são os seguintes:

Linha 41 – Passamos a URL com a chamada ao nosso controlador ‘album’, função ‘upload_file’.

Linha 42 – Definimos o tema para o plugin que pode utilizar o bootstrap para deixar mais bonito.

Linha 43 – Desligamos os alertas de erros, mas é por um bom motivo, explico:

O pekeUpload utiliza o retorno da função como 1 para dizer que o upload do arquivo foi feito com sucesso e qualquer outro retorno serve para indicar que aconteceu algum erro, porém precisamos retornar o id do arquivo adicionado para poder relacionar com o álbum ao qual ele pertence, dessa forma precisamos desligar os alertas de erros e tratar os uploads bem sucedidos na função onFileError (44 a 49) para exibir uma mensagem amigável ao usuário dizendo que o upload ocorreu com sucesso.

Espero que com o tempo consiga encontrar uma solução melhor para esse problema, inclusive aceito qualquer ajuda ou sugestão, mas enquanto isso, essa foi a melhor forma que encontrei de fazer isso!

Finalizando

Após as mudanças acima nossa aplicação ficará assim:

MultiUpload-no-Web2py-02

E após fazer o upload de alguns arquivos, teremos a seguinte tela:

MultiUpload-no-Web2py-03

Bom pessoal, espero que tenham gostado desse pequeno tutorial e que mandem sugestões de como melhorar essa singela aplicação.
A aplicação está hospedada no meu github

Sintam-se livres para clonar o repositório acima e modificar o que precisarem.

Abraço a todos e até a próxima. 😉

Author Description

Glauco Junior

Desenvolvedor Python, Javascript, PHP e C#. Atualmente trabalha desenvolvendo soluções para Digital Signage e WEB. Amante da Ciência e da Tecnologia e eterno aprendiz.

There are 2 comments. Add yours

  1. 29th setembro 2014 | José says: Responder
    Excelente. Muchas gracias por el ejemplo!

Join the Conversation