segunda-feira, 17 de outubro de 2011

Plugins Úteis - Sorl e o (muito util) ThumbnailField

Para quem utiliza a fantástica biblioteca Sorl Thumbnail (http://pypi.python.org/pypi/sorl-thumbnail), tem uma funcionalidade muito útil além da inclusion tag {% thumbnail %}

Com a inclusion tag, é possível redimensionar e/ou cortar, uma imagem de acordo com a necessidade. Para isso, basta utilizar assim no seu template:

{% load thumbnail %}

A linha acima deve ser colocar no começo do arquivo para carregar as tags da sorl para seu template. E para a imagem fazemos:

<img src='{% thumbnail seu_objeto.campo_foto 150x150 crop,upscale %}' />

Aqui temos um exemplo em que será gerada uma imagem de 150px de largura por 150px de altura. As opções crop,upscale são opcionais e servem para corte e redimensionamento, respecticamente.

Há uma certa desvantagem em utilizar desta forma. A thumb somente será gerada quando alguma requisição for feita para a página, e após isso o arquivo físico da thumb é gerado em tempo de execução. Posteriormente, se a thumb não for trocada, o sistema utiliza a thumb gerada na primeira requisição ao invés de gerar uma nova.

Uma outra opção, e fazer a geração de todas as thumbs utilizadas para uma mesma foto, no momento que a foto é inserida ou alterada. Para isso temos o ThumbnailField.

No seu model, já com a sorl instalada no seu projeto e/ou no virtualenv, faça:

No ínicio do arquivo models.py que for utilizar thumbs, import o ThumbnailField:

from sorl.thumbnail.fields import ThumbnailField

E na sua classe, crie o campo com o campo importado da seguinte forma:

class SuaClasse(models.Model):
    foto = ThumbnailField(
upload_to="uploads/banners/home/%Y",
help_text=u"Tamanho mínimo e proporcional a 1024x768",
options = ('upscale',),
size = (1920,1000),
generate_on_save = True,
extra_thumbnails={
'1024x768':{ 'size':(1024,768), 'options':('upscale',) },
'1280x1024':{ 'size':(1280,1024), 'options':('upscale',) },
'1280x800':{ 'size':(1280,800), 'options':('upscale',) },
'1440x900':{ 'size':(1440,900), 'options':('upscale',) },
'1366x768':{ 'size':(1366,768), 'options':('upscale',) },
},

Deste modo, teremos 6 arquivos para cada imagem que for inserida.

  • A original foto,  que terá no máximo 1920x1000 px de tamanho, sendo que fará uso do upscale
  • E 5 extra_thumbnails, onde cada uma terá um tamanho próprio, também utilizando o upscale.

No template para utilizar o campo foto,  nada muda, continua sendo :

{{ seu_obj.foto }}

Já para acessar alguma das thumbs, utilize:

{{ seu_obj.foto.extra_thumbnails.NOME_DO_THUMBNAIL }} ,  por exemplo:

{{ seu_obj.foto.extra_thumbnails.1280x1024 }} 


Em alguns casos temos que acessá-los em uma view, seja para montar um json para o ajax, ou até mesmo um xml pro inútil e arcaico Flash. Para isso utilize:

seu_obj.foto.extra_thumbnails_tag['1280x800']


Outra opção é criar um model, muito útil para quando deseja-se mostrar a thumb no change list do admin. Para isso, utilize:


def thumb_admin(self):
from sorl.thumbnail import get_thumbnail
im = get_thumbnail(self.imagem, '120x120', crop='center', quality=99)
if im:
return  '<a href="%s/"><img src="%s" alt="Imagem" /></a>' % (self.id, str(im.url))
return  'Sem foto'
thumb_admin.is_safe = True
thumb_admin.allow_tags = True
thumb_admin.short_description = 'Thumb'


hasta!








terça-feira, 11 de outubro de 2011

Forçando download de arquivos no Django

Muitas vezes, nossas classes possuem módulos que tem imagens em alta definição, ou até mesmo arquivos referente ao contexto da classe, tais como manuais em pdf, ou em outro formato qualquer.

Para algumas situações é melhor que o visitante faça download do arquivo, mesmo que o browser possua os plugins necessários para exibí-lo. Para forçar o download basta utilizar a view abaixo:


def download_view(request):
 from django.http import HttpResponse, Http404
 from os import path
 import mimetypes

 arquivo = "%s/%s" % (settings.MEDIA_ROOT, request.GET.get('filename'), ) 

 if not (path.exists(arquivo)):
  raise Http404()

 mimetype, encoding = mimetypes.guess_type(arquivo)

 if mimetype is None:
  mimetype = 'application/force-download'

 file = arquivo.split("/")[-1]

 response = HttpResponse(open(arquivo, 'r').read())
 response['Content-Type'] = mimetype
 response['Pragma'] = 'public'
 response['Expires'] = '0'
 response['Cache-Control'] = 'must-revalidate, post-check=0, pre-check=0'
 response['Content-Disposition'] = 'attachment; filename=%s' % file
 response['Content-Transfer-Encoding'] = 'binary'
 response['Content-Length'] = str(path.getsize(arquivo))
 return response


O parâmetro filename vem por get, permitindo que a mesma view seja utilizada para vários downloads de diferentes classes. O que pode ser ajustado é o caminho da definição da variável filename de acordo com a necessidade.

hasta!

terça-feira, 27 de setembro de 2011

Como ordenar uma listagem de produtos pelos mais recentes, deixando os esgotados pro fim?

Recentemente tive que fazer a ordenação descrita no título deste post.

A solução encontrada foi a utilização da poderosa ferramenta extra que o django oferece em seu ORM.

Tinha a seguinte situação:

models.py

class Produto(models.Model):
     ...
     ...
     estoque = models.IntegerField()
     ...
     ...


Meu campo estoque gerencia a quantidade disponível de cada produto na loja. 

Fiz da seguinte forma no meu arquivo view.py:

def lista_produto(request):
     ...
     ...
     produtos = Produto.objecst.all().extra(select={'esgotado': "( if (produtos_produto.estoque > 0,0,-10000) )+produtos_produto.id"}).extra( order_by = ['-esgotado'] )
     ...
     ...

Juntamente com todos os campos da classe Produto, crio um campo a mais para o retorno, sendo este chamado de esgotado. Para este campo será executado a query passada a seguir para o banco de dados:

 if (produtos_produto.estoque > 0,0,-10000) )+produtos_produto.id

Caso o estoque for maior que 0, o IF irá retornar 0, caso contrário será retornado -10000. O valor negativo aqui é arbitrário, podendo este ser qualquer valor, desde que seu módulo seja pelo menos igual ao maior id do banco.



Ao valor do IF é adiconado o ID do produto corrente.

Desta forma todos os produtos que não tiverem estoque, será trazido neste campo um valor negativo visto que a conta ficará -10000 + ID,  e os que possuírem algum estoque, será 0 + ID.

Finalmente, passamos a nova ordenção, esgotado DESC, também pelo extra:
.extra( order_by = ['-esgotado'] )

Assim, todos os produtos com estoque ficarão com um valor positivo para o valor esgotado, e os que não tiverem, será um valor negativo, ficando por últimos com a ordenação decrescente.

hasta!