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!


segunda-feira, 26 de setembro de 2011

Enviando emails no Django através de um pool de contas autenticadas

Recentemente passei por um problema com relação ao envio de emails. O cliente possuia as constas hospedadas em um servidor de emails que limitava o envio diário. Logo, com todos os contatos, logs de erros, logs de controle, etc sendo enviados por uma mesma conta, tinha dias que excedia tal limite.

Para que isso não voltasse a ocorrer, a idéia era ter uma lista de possíveis conexões, para que em caso de falha de autenticação ou limite, utilizasse a outra. Logo veio o problema. No settings.py apenas há espaço para uma autenticação de email.

Então foi que fiz o seguinte:

send_email_by_pool.py ( na raiz do projeto )


import random
import smtplib
from django.conf import settings
from django.core.mail import send_mail, get_connection

def send_mail_by_pool(subject, message, from_email=None, recipient_list=None, fail_silently=False,
auth_user=None, auth_password=None, connection=None):
"""
Seleciona a conexao atual usando um pool de conexoes
"""
global _valid_connections

ret = 0
while _valid_connections and ret == 0:
try:
connection = connection or choose_connection()
from_email = from_email or connection.username

auth_user = auth_user or connection.username
auth_password = auth_password or connection.password

ret = send_mail(subject, message, from_email, recipient_list, fail_silently, auth_user, auth_password, connection)

except (smtplib.SMTPAuthenticationError, smtplib.SMTPSenderRefused):
if not connection:
raise

_valid_connections.remove(connection._pool_name)

return ret

_valid_connections = settings.EMAIL_CONNECTIONS.keys()
def choose_connection():
global _valid_connections
chosen = random.choice(_valid_connections)
conn_settings = settings.EMAIL_CONNECTIONS[chosen]
connection = get_connection(**conn_settings)
connection._pool_name = chosen
return connection

no settings.py:



EMAIL_CONNECTIONS = {
'default': {
'backend': 'django.core.mail.backends.smtp.EmailBackend',
'host': 'smtp.seu_servidor.com.br',
'username': 'seu_email@ seu_servidor.com.br',
'password': 'sua_senha',
'use_tls': False, # para o caso de usar TLS, colocar True
'fail_silently': False,
},
'second': {
'backend': 'django.core.mail.backends.smtp.EmailBackend',
'host': 'smtp.seu_servidor.com.br',
'username': 'seu_email2@ seu_servidor.com.br',
'password': 'sua_senha2',
'use_tls': False, # para o caso de usar TLS, colocar True
'fail_silently': False,
},
}

E na sua view:


from send_email_by_pool import *

subject = "Testando envio"
message = "oi"
from_email = None
recipient_list = ['zejuniortdr@gmail.com',]
fail_silently = False
auth_user = None
auth_password = None 
connection = None

ret = send_mail_by_pool(subject, message, from_email, recipient_list, fail_silently, auth_user, auth_password, connection)



Desta forma, o envio ocorrerá passando por tantas contas quantas forem necessárias até enviar, desde que haja uma conta autorizada e que não tenha excedido o limite.

hasta!



sexta-feira, 23 de setembro de 2011

Upload em Python

Em alguns caso é necessário fazer um upload manual no python.

Para tal, fiz o seguinte:



def upload(request):
target = os.path.join(settings.MEDIA_ROOT, 'uploads/anuncios/logo/2010')
if request.method == 'POST':
if request.FILES.get('logo_file'):
f = request.FILES['logo_file']
try:
destination = open('%s/%s' % (target, f.name), 'wb+')
for chunk in f.chunks():
destination.write(chunk)
destination.close()
except:
retorno = "{error:'100', msg:'Problema: Erro de permissão para escrever em arquivo'}"
return HttpResponse(retorno)

retorno = "{error:'', msg:'Sucesso', arquivo:'%s'}" % (f.name)
return HttpResponse(retorno)
else:
retorno = "{error:'200', msg:'Problema: Sem FILES'}"
return HttpResponse(retorno)
else:
retorno = "{error:'300', msg:'Problema: Sem Post'}"
return HttpResponse(retorno)

Plugins Úteis - South - Uma app para Django muito útil para gerenciamento do Banco de Dados


South

South é uma aplicação do django responsável por gerenciar as modificações feitas no projeto que influenciam em criação/edição/remoção de tabelas e/ou campos no banco de dados.

Para instalar o south no ambiente:
pip install south
adicionar ele na installed_apps

Com o South NÃO DEVE-SE USAR o syncdb do django para criação das tabelas de suas apps. Apenas deve ser rodado o syncdb para criação das tabelas referente as aplicações padrões do settings e mais a do south. 

Para criação das tabelas de uma nova aplicação utilizar:
python manage.py schemamigration NOME_DA_APP --initial

Para criação de todas as tabelas do projeto, basta omitir o nome da app.

Os comandos acima vão criar apenas os schemas de migração, nada será alterado no banco de dados. Para aplicar as migrações deve-se utilizar o comando:
python manage.py migrate NOME_DA_APP

Para todos os esquemas, basta omitir o nome da app.

Depois da situação inicial criada, caso haja necessidade de adicionar/remover campos nos models, utilizar seguinte comando para sincronizar os schemas:
python manage.py schemamigration NOME_DA_APP --auto

E para aplicar as migrações da mesma forma descrita anteriormente:
python manage.py migrate NOME_DA_APP

Caso deseje aplicar o South em algum projeto que não tenha, e as tabelas já tenham sido criadas, basta instalar o South como descrito acima, e utilizar o comando:
python manage.py convert_to_south NOME_DA_APP 


O south cria no banco de dados algumas tabelas para controle do que já foi migrado para não tentar fazer novamente, caso deseje informar o south que aquela migração já tenha sido realizada, possivelmente para corrigir algum problema, basta utilizar o comando:
python manage.py migrate --fake

Desta forma ele vai apagar as migrações contidas nos schemas gerados e colocar no banco como realizada, sem fazer nenhuma alteração no banco.

quinta-feira, 22 de setembro de 2011

Permissão somente para usuários do admin (staff_member)

As vezes, algumas views requer permissões mais rígidas, como por exemplo alguma view de integração, carga, ou até mesmo para dados sigilosos.

Para tal, basta utilizar o decorator staff_member_required, que permite apenas acesso aos membos da equipe.

Para fazer uso do mesmo basta importar em seu arquivo views.py ou admin_views.py :

from django.contrib.admin.views.decorators import staff_member_required

E na view utilizar como qualquer outro decorator:

@staff_member_required
def sua_view(request):
        #seu código aqui

sexta-feira, 16 de setembro de 2011

Correndo uma lista de valores similar a lista de impressão

Para uma entrada  1;2;3-6;7;8-10, por exemplo, assim como na impressão seria impresso as páginas 1, 2, de 3 a 6, 7 e de 8 a 10, precisei fazer um esquema para liberação de pedidos segundo essa entrada para liberar faixas distintas de pedidos.

Para tal, uso a def abaixo:

def corre_cadeia(lista_char, separador_item, identificador_lista):
 lista  = lista_char.split(separador_item)
 nova_lista = []
 for l in lista:
  if l.find(identificador_lista) == -1:
   nova_lista.append(int(l))
  else:
   inicio, termino = l.split(identificador_lista)
   inicio = int(inicio)
   termino = int(termino)
   i = inicio
   while i<= termino:
    nova_lista.append(i)
    i+=1
 return nova_lista

Ex:
>>> lista = '1;2;3-6;7;8-10'
>>> corre_cadeia ( lista, ';' , '-')
[1,2,3,4,5,6,7,8,9,10]

hasta!