sexta-feira, 6 de dezembro de 2013

App de cidades e estados brasileiros

Vez ou outra é necessário cadastrar a cidade e o estado em um formulário de algum projeto, seja para cadastro de perfis, pedidos, etc.

Quando tem endereço envolvido ainda é mais fácil, pois existem diversos webservices que fornecem o nome da forma correta, como consta nos correios. O problema acontece quando desejamos apenas cidade e estado.

Sempre aparece alguns "espertinhos" (pra não dizer outra coisa), que escrevem sua cidade cada hora de um jeito, abreviando das mais diversas maneiras, e com isso, o banco fica uma bagunça quando precisa-se filtrar ou tirar um relatório baseado em cidades.

Por isso, resolvi montar um banco com base nas informações dispostas no site do IBGE (http://www.cidades.ibge.gov.br/xtras/home.php - atualizado em 2010) para ajudar com esta tarefa.

Espero que ajude outros programadores.

Segue o link: https://github.com/zejuniortdr/cidades-br

hasta!

quinta-feira, 7 de novembro de 2013

Como instalar o MySQL no OS X 10.9 Mavericks

Estava ocorrendo o erro:

EnvironmentError: mysql_config not found
ao tentar instalar o mysql com o comando:

pip install mysql-python

com o ENV ativo, então resolvi reinstalar o MySQL Achei um tutorial  (disponivel neste link) com 1 linha de comando que ajudou muito:

bash <(curl -Ls http://git.io/eUx7rg)

Pro caso de sair do ar, replico o conteúdo aqui:

#!/bin/bash
#############################################
# AUTHOR: JONATHAN SCHWENN @JONSCHWENN      #
# MAC MINI VAULT - MAC MINI COLOCATION      #
# MACMINIVAULT.COM - @MACMINIVAULT          #
# VERSION 1.05 RELEASE DATE OCT 11 2013     #
# DESC:  THIS SCRIPT INSTALLS MySQL on OSX  #
#############################################
#REQUIREMENTS:
#  OS X 10.7 or newer
#############################################
# CHECK FOR OS X 10.7+
if [[  $(sw_vers -productVersion | grep '10.[7-9]')  ]]
then
# CHECK FOR EXISTING MySQL
if [[ -d /usr/local/mysql && -d /var/mysql ]]
then
echo "It looks like you already have MySQL installed..."
echo "This script will most likely fail unless MySQL is completley removed"
echo "..."
 while true; do
  read -p "DO YOU WANT TO CONTINUE? [y/N]" yn
  case $yn in
  [Yy]* ) break;;
  [Nn]* ) exit ;;
  * ) echo "Please answer yes or no.";;
  esac
 done
fi
# LOOKS GOOD, LETS GRAB MySQL AND GET STARTED ...
echo "Downloading MySQL Installers ... may take a few moments"
curl -s -o ~/Downloads/MySQL.dmg http://cdn.mysql.com/Downloads/MySQL-5.6/mysql-5.6.14-osx10.7-x86_64.dmg
hdiutil attach -quiet ~/Downloads/MySQL.dmg
cd /Volumes/mysql-5.6.14-osx10.7-x86_64/
echo "..."
echo "..."
echo "Installing MySQL, administrator password required ..."
sudo installer -pkg mysql-5.6.14-osx10.7-x86_64.pkg -target /
echo "..."
echo "..."
echo "Installing MySQL start up items..."
sudo installer -pkg MySQLStartupItem.pkg -target /
echo "..."
echo "..."
echo "Click Install to install the MySQL preferance pane"
echo "..."
echo "..."
# MOVING PREFPANE TO DOWNLOADS FOLDER SO IT CAN STILL BE INSTALLED
# AFTER THE SCRIPT COMPLETES AND REMOVES THE INSTALLER FILES
# AS SCRIPT DOESN'T WAIT FOR USER TO CLICK "INSTALL" FOR PREFPANE
cp -R MySQL.prefPane ~/Downloads/MySQL.prefpane
open ~/Downloads/MySQL.prefPane/
echo "..."
sleep 15
sudo /usr/local/mysql/support-files/mysql.server start
echo "export PATH=$PATH:/usr/local/mysql/bin" >> ~/.bash_profile
sudo mkdir /var/mysql; sudo ln -s /tmp/mysql.sock /var/mysql/mysql.sock
if [[  $(sudo /usr/local/mysql/support-files/mysql.server status | grep "SUCCESS") ]]
then
mypass="$(cat /dev/urandom | base64 | tr -dc A-Za-z0-9_ | head -c8)"
echo $mypass > ~/Desktop/MYSQL_PASSWORD
echo "Setting MySQL root Password to $mypass"
echo "Placing password on desktop..."
/usr/local/mysql/bin/mysql -uroot -e "GRANT ALL ON *.* TO 'root'@'localhost' IDENTIFIED BY '$mypass' WITH GRANT OPTION;"
echo "..."
echo "..."
cd ~/
hdiutil detach -quiet /Volumes/mysql-5.6.14-osx10.7-x86_64/
sleep 2
rm ~/Downloads/MySQL.dmg
# NEW MY.CNF PERFORMANCE OPTION START
echo "BASE PERFORMANCE MY.CNF IS JUST A GENERIC SUGGESTION FOR PERFORMANCE"
echo "YOUR RESULTS MAY VARY AND YOU MAY WANT TO FURTHER TUNE YOUR MY.CNF SETTINGS"
echo "BASE PERFORMANCE MY.CNF INCREASES BUFFERS/MEMORY USAGE"
echo "8GB+ RAM IS RECOMMENDED FOR BASE PERFORMANCE MY.CNF"
echo "..."
sudo cp /usr/local/mysql/my.cnf /usr/local/mysql/mmv.cnf
sudo tee -a /usr/local/mysql/mmv.cnf > /dev/null  << EOF

# CUSTOMIZED BY MMVMySQL SCRIPT - JUST GENERIC SETTINGS
# DO NOT TREAT AS GOSPEL

skip-external-locking
key_buffer_size = 384M
max_allowed_packet = 1M
table_open_cache = 512
sort_buffer_size = 2M
read_buffer_size = 2M
read_rnd_buffer_size = 8M
myisam_sort_buffer_size = 64M
thread_cache_size = 8
query_cache_size = 32M
thread_concurrency = 4
EOF
        while true; do
                read -p "DO YOU WANT TO LOAD A BASE PERFORMANCE MY.CNF FILE? [y/N]" cnf
                case $cnf in
                [Yy]* ) sudo cp /usr/local/mysql/mmv.cnf /etc/my.cnf; sudo /usr/local/mysql/support-files/mysql.server restart; break  ;;
                [Nn]* ) break;;
                * ) echo "Please answer yes or no.";;
                esac
        done
# NEW MY.CNF PERFORMANCE OPTION END
# NEW SEQUEL PRO INSTALL OPTION START
while true; do
                read -p "DO YOU WANT TO AUTOMATICALLY INSTALL SEQUEL PRO? [Y/n]" sp
                case $sp in
                [Yy]* ) curl -s -o ~/Downloads/SequelPro.dmg https://sequel-pro.googlecode.com/files/sequel-pro-1.0.2.dmg; hdiutil attach -quiet ~/Downloads/SequelPro.dmg;cp -R /Volumes/Sequel\ Pro\ 1.0.2/Sequel\ Pro.app/ /Applications/Sequel\ Pro.app/; hdiutil detach -quiet /Volumes/Sequel\ Pro\ 1.0.2/;sleep 5; rm ~/Downloads/SequelPro.dmg; echo "Sequel Pro is now in your Applications folder!";  break  ;;
                [Nn]* ) break;;
                * ) echo "Please answer yes or no.";;
                esac
        done
# NEW SEQUEL PRO INSTALL OPTION END
echo " "
echo " "
echo "ALL DONE!  Install Sequel Pro or phpmyadmin to administer MySQL"
echo "Log off and log back in for 'mysql' to be recognized as a command in terminal"
else
"SORRY, MySQL IS NOT RUNNING ... THERE MUST BE A PROBLEM"
fi
else
echo "ERROR: YOU ARE NOT RUNNING OS X 10.7 OR NEWER"
exit 1
fi

Para instalar abra o terminal e execute o comando:

bash <(curl -Ls http://git.io/eUx7rg)

Siga os passos conforme forem aparecendo. Quando pedir a senha, coloque a senha do usuário do mac, será gerado uma senha nova para o usuario root do mysql e ela estará disponivel tanto no terminal quanto num arquivo direto na Mesa.

Se por ventura quando for rodar o syncdb, pegar o erro:

Library not loaded: libmysqlclient.18.dylib
Reason: image not found

faça um link simbolico para essa lib com o comando:

sudo ln -s /usr/local/mysql/lib/libmysqlclient.18.dylib /usr/lib/libmysqlclient.18.dylib




sexta-feira, 23 de agosto de 2013

Ordenando por um campo calculado por uma def no admin

Seguindo o exemplo anterior, Como utilizar os inlines no front para os ModelForms (class based view) , imagine que gostaríamos de exibir quantas fotos cada cadastro postou. Fácil né?

Basta fazer uma def no models.py como abaixo e depois colocá-la no list_display do admin.py:

def qtde_fotos(self):
    return self.foto_set.all().count()


Mas e se, quiséssemos ordenar no admin por este valor? Saber em ordem quais os cadastros que mais enviaram fotos? Por ser uma def, o django não ordena automaticamente. Pra isso temos que fazer alguns ajustes. Primeiro de tudo, apague esta def do seu model caso tenha feito.

No admin de seu model no arquivo admin.py, deixe como abaixo:


def queryset(self, request):
    qs = super(CadastroAdmin, self).queryset(request)
    qs = qs.annotate(qtde=Count('foto'))
    return qs

def qtde_fotos(self, inst):
    return inst.qtde
qtde_fotos.admin_order_field = 'qtde'


depois basta adicionar a def qtde_fotos no list_display.


hasta!

Como utilizar os inlines no front para os ModelForms (class based view)

Assim como prometido, mesmo que com uma "pequena" demora, segue a continuação do post
Como utilizar os inlines no front para os ModelForms (function based view). Faremos algo bem similar com alguns detalhes a mais.

Objetivo é fornecer um formulário de cadastro e este pode fazer uploads de fotos. Quando o email já existir no banco de dados, faremos que as fotos do segundo envio seja direcionadas para o primeiro cadastro, do contrário, uma nova instancia para todos os objetos envolvidos.

models.py

from django.db import models

# Create your models here.

class Cadastro(models.Model):
 """(Cadastro description)"""
 nome = models.CharField(max_length=255)
 email = models.EmailField(verbose_name='E-mail')
 telefone = models.CharField(max_length=20)
 aceite = models.BooleanField(default=True)
 data = models.DateField(auto_now_add=True)
 hora = models.TimeField(auto_now_add=True)

 class Meta:
  verbose_name, verbose_name_plural = u"Cadastro" , u"Cadastros"
  ordering = ('-data',)

 def __unicode__(self):
  return "%s" % self.nome

class Foto(models.Model):
 """(Foto description)"""
 cadastro = models.ForeignKey(Cadastro)
 foto = models.ImageField(upload_to="uploads/fotos/foto/%Y")
 data = models.DateField(auto_now_add=True)
 hora = models.TimeField(auto_now_add=True)
 aprovado = models.BooleanField(default=False)

 class Meta:
  verbose_name, verbose_name_plural = u"Foto" , u"Fotos"
  ordering = ('-data',)

 def __unicode__(self):
  return "%s" % self.id

 def thumb_admin(self):
  from sorl.thumbnail import get_thumbnail
  im = get_thumbnail(self.foto, '120x120', crop='center', quality=99)
  if im:
   return  '<a href="http://www.blogger.com/media/%s" onclick="window.open(this.href);return false;"><img alt="Imagem" src="%s" /></a>' % (self.foto, str(im.url))
  return  'Sem foto'
 thumb_admin.is_safe = True
 thumb_admin.allow_tags = True
 thumb_admin.short_description = 'Thumb'

admin.py

from django import forms
from django.contrib import admin
from .models import *

class FotoInline(admin.TabularInline):
 model = Foto
 readonly_fields = ['foto',]
 extra = 5


class CadastroAdmin(admin.ModelAdmin):
 inlines = [
  FotoInline,
 ]
 search_fields = ('nome', 'email',)
 list_display = ('nome', 'email','telefone','data', 'hora',)
 list_filter = ['data',]
 date_hierarchy = 'data'
 readonly_fields = ['nome', 'email','telefone','aceite']
 save_on_top = True

admin.site.register(Cadastro, CadastroAdmin)



class FotoAdmin(admin.ModelAdmin):
 
 list_display = ('cadastro', 'data', 'hora','thumb_admin','aprovado')
 list_filter = ['data','aprovado']
 date_hierarchy = 'data'
 list_editable = ['aprovado']
 readonly_fields = ['cadastro','foto',]
 save_on_top = True

admin.site.register(Foto, FotoAdmin)

forms.py

# coding: utf-8
from django import forms
from django.utils.translation import ugettext_lazy as _
from .models import *
from util import util as U

from django.forms.models import inlineformset_factory

class CadastroForm(forms.ModelForm):
 class Meta:
  model = Cadastro


class FotoForm(forms.ModelForm):

 aprovado = forms.BooleanField(initial=False, required=False)
 aceite = forms.BooleanField(initial=True)

 class Meta:
  model = Foto


 def dados(self):
  return {'form':self.cleaned_data, 'data':datetime.now()}

 def clean(self):
  cleaned_data = super(FotoForm, self).clean()
  #if cleaned_data.get("senha") != cleaned_data.get("confirme"):
  # self._errors["confirme"] = self.error_class(['Senha nao confere'])
  return self.cleaned_data


FotoFormSet = inlineformset_factory(Cadastro, Foto, extra=5)

urls.py

# coding: utf-8
from django.conf.urls.defaults import patterns, include, url

from . import views

urlpatterns = patterns('fotos.views',
 url(r'^$', views.CadastroView.as_view(), name='cadastro_form'),
)

form.html

<html>
 <head>
 <meta charset="utf-8">
 <title></title>
 </head>
 <body>
  <form action="" method="post" enctype="multipart/form-data">
   {% csrf_token %}
   {{ form.errors }}

   <p>Nome:{{ form.nome }}</p>
   <p>Telefone: {{ form.telefone }}</p>
   <p>E-mail: {{ form.email }}</p>
   <p>Foto: {{ form.foto }}</p>
   <p>Aceite: {{ form.aceite }}</p>

   {{ foto_formset.management_form }}
   {% for form in foto_formset %}
    {{ form.id }}
    <div class="form-row inline {{ foto_formset.prefix }}">
     {{ form.foto.label_tag }}
     {{ form.foto }}
     {{ form.foto.errors }}
    </div>
   {% endfor %}

   <input type="submit" name="" value="Enviar">

  </form>
 </body>
</html>

views.py

class CadastroView(CreateView):
 #template_name = 'form.html'
 template_name = 'index.html'
 model = Cadastro
 form_class = CadastroForm
 success_url = '/envie/?sucesso=1#sucesso'

 def form_valid(self, form):
  context = self.get_context_data()
  foto_form = context['foto_formset']

  email = form.cleaned_data['email']

  if foto_form.is_valid():
   print "erros: %s" % foto_form.errors
   if Cadastro.objects.filter(email=email):
    foto_form.instance = Cadastro.objects.get(email=email)
   else:
    self.object = form.save()
    foto_form.instance = self.object
   foto_form.save()
   return HttpResponseRedirect('/envie/?sucesso=1#sucesso')
  else:
   addDebug("erros: %s" % foto_form.errors)
   return self.render_to_response(self.get_context_data(form=form))

 def form_invalid(self, form):
  return self.render_to_response(self.get_context_data(form=form))

 def get_context_data(self, **kwargs):
  context = super(CadastroView, self).get_context_data(**kwargs)
  r = self.request.POST
  f = self.request.FILES  

  if r and f:
   context['foto_formset'] = FotoFormSet(self.request.POST,f)
  else:
   context['foto_formset'] = FotoFormSet()
  return context

cadastro.js

jQuery(document).ready(function($) {

 $('.bt-mais').click(function(event) {
  if($('.enviar-foto-fake').length < 5){
   $('.enviar-foto-fake').last().clone().insertAfter($('.enviar-foto-fake').last());
   $('.enviar-foto-fake').last().find('div p.nome-foto').text('Você pode enviar até cinco fotos');
   $('.enviar-foto-fake').last().find('input').attr({
    id: 'id_foto_set-'+($('.enviar-foto-fake').length-1)+'-foto',
    name: 'foto_set-'+($('.enviar-foto-fake').length-1)+'-foto'
   });
   $('#id_foto_set-TOTAL_FORMS').val($('.enviar-foto-fake').length);
  }

  if ($('.enviar-foto-fake').length == 5) {
   $(this).hide();
  };

 })
});

hasta!

Gerando imagens com marca d'agua com django-watermark 0.1.6-pre1

Algumas vezes é necessário que seja feita um tratamento em imagens, para que estas apresentem o logo ou alguma outra informação. Dependendo da utilização, pode simplesmente colocar as imagens como backgrounds de divs com position absolute dentro de um container. Esta abordagem tem dois pontos negativos:

  1. Alguém com o mínimo de conhecimento conseguirá isolaras imagens através do código fonte e obter a imagem original sem a marca facilmente.
  2. Caso seja necessário integração com algum plugin de compartilhamento de conteúdo em redes sociais, por exemplo, a imagem que o plugin pega também é a original.


Então qual a correta forma de solucionar o problema? django-watermark.


Instalação

Para instalação basta utilizar o pip ou easy_install:

easy_install django-watermark
pip install -U django-watermark

Configuração

Para configurá-lo em seu projeto, adicone o aplicativo no INSTALLED_APPS no settings.py

INSTALLED_APPS = (
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    ...
    'watermarker',
    ...
)

Execute o syncdb para criar a tabela deste app.

python manage.py syncdb

Opções:


position - Este é bastante personalizável. Primeiro, você pode exibirsua marca d'água em um canto suas imagens usando um dos BR, BL, TR, e TL. Estes representam 'bottom-right', 'bottom-left', 'top-right' e 'top-left', respectivamente.

Alternativamente, você pode usar o posicionamento relativo ou absoluto para a marca d'água. Posicionamento relativo usa porcentagens; posicionamento absoluto utiliza pixels exatos. Você pode misturar e combinar estes dois modos de posicionamento, mas você não pode misturar e combinar relativa / absoluta com o posicionamento canto. Quando se utiliza o posicionamento relativo / absoluto, o valor para o parâmetro de posição é XxY, onde X é o valor da esquerda e o símbolo Y representa o valor superior. Os valores de esquerda e superior devem ser separados com um x minúsculo.

Se você quiser sua imagem marca d'água para mostrar-se no centro de qualquer imagem, você pode utilizar como posição = 50% x50% ou mesmo position = C. Se você queria que a marca d'água para mostrar-se a meio caminho entre as margens esquerda e direita da imagem e 100 pixels da parte superior, você utilizaria = 50% x100.

Finalmente, você pode dizer ao filtro para gerar uma posição para sua marca de água de forma dinâmica. Para fazer isso, use position = R.

opacity - Este parâmetro permite que você especifique a transparência da marca d'água aplicada. O valor deve ser um número inteiro entre 0 e 100, onde 0 é totalmente transparente e 100 é totalmente opaco. Por padrão, a opacidade é fixado em 50%.

tile - Se você quer sua marca d'água para azulejo por toda a imagem, basta especificar um parâmetro como tile= 1. Por padrão, o tile é fixado em 0.

scale - Se você gostaria de ter a marca d'água tão grande quanto possível a imagem de destino e totalmente visível, você pode querer usar scale = F. Se você quiser especificar um fator de escala particular, é só usar algo como scale = 1.43.

greyscale - Se você quer sua marca d'água a ser em tons de cinza, você pode especificar o parâmetro greyscale = 1 e todas saturações de cores vão embora.

rotation - Definir este parâmetro para qualquer inteiro entre 0 e 359 (na verdade, qualquer número inteiro deve funcionar, mas para sua própria sanidade, eu recomendo manter o valor entre 0 e 359). Se você quiser que a rotação ser aleatória, use rotation= R em vez de um número inteiro.

obscure - Defina esse parâmetro como 0 para fazer o nome do arquivo da imagem original do visível para o usuário. O padrão é 1 (ou verdadeiro) para ocultar o nome do arquivo original.

quality - Manter esta a um número inteiro entre 0 e 100 para especificar a qualidade da imagem resultante. O padrão é 85.

random_position_once - Defina como 0 ou 1 para especificar o comportamento de posicionamento aleatório para marca d'água da imagem. Quando ajustado para 0, a marca d'água será colocado aleatoriamente em cada solicitação. Quando ajustado para 1, a marca d'água será posicionado aleatoriamente na primeira solicitação, e os pedidos subsequentes usarão a imagem produzida. O padrão é True (posicionamento aleatório só acontece no primeiro pedido).

Utilização

Sua utilização também é bastante simples. No template que precisar gerar a marca d'agua carrege o filter da aplicação com o comando:

{% load watermarker %}



Exemplos


{{ image_url|watermark:"My Watermark,position=br,opacity=35" }}

Procura uma marca d'água com o nome "My Watermark", coloque-o no canto inferior direito da imagem de destino, utilizando um nível de transparência de 35%.

{{ image_url|watermark:"Your Watermark,position=tl,opacity=75" }}

Procura uma marca d'água com o nome "Your Watermark", coloque-o no canto superior esquerdo da imagem de destino, utilizando um nível de transparência de 75%.

{{ image_url|watermark:"The Watermark,position=43%x80%,opacity=40" }}

Procura uma marca d'água com o nome  "The Watermark", coloca-lo em 43% em relação ao eixo x e de 80% no eixo dos y da imagem do alvo, a um nível de 40% de transparência.

{{ image_url|watermark:"The Watermark,position=R,opacity=10,rotation=45" }}

Procura uma marca d'água com o nome "The Watermark", gera aleatoriamente uma posição para ele, a um nível de 10% de transparência, rotação de 45 graus.

{{ image_url|watermark:"w00t,opacity=40,tile=1" }}

Procura uma marca d'água chamado "w00t", telhas de TI em toda a imagem de destino inteira, a um nível de 40% de transparência.

sexta-feira, 21 de junho de 2013

Exibir e ordenar por um campo FK de outra classe no change list

Comumente utilizamos classe com FKs e no change list do admin existe a possibilidade de fazer filtros por esses campos da seguinte forma:

list_filter = ['suafk__seuatributo']

Sabia que tem como fazer isso ser exibido também no list_display?

Se tentarmos utilizar da mesma forma surge o seguinte erro:

ImproperlyConfigured at /admin/seu-app/sua-classe/
Sua-ClasseAdmin.list_display[4], 'suafk__seuatributo' is not a callable or an attribute of 'Sua-ClasseAdmin' or found in the model 'Sua-Classe'.

O numero 4 indica a posição que colocou o atributo a ser exibido, e pode variar de acordo com a posição que colocar.

Mas então como fazer isso? Simples:

No seu models.py crie um método dentro da classe que tem a FK do atributo que deseja listar da seguinte forma:

def seu_metodo(self):
    return self.suafk.seuatributo
seu_metodo.admin_order_field = 'suafk__seuatributo'


Agora basta chamar o metodo recem criado no list_display:

list_display = ('seu_metodo')


hasta!

sexta-feira, 24 de maio de 2013

Como utilizar os inlines no front para os ModelForms (function based view)

Para isso é preciso de dois models, um model Pai que conterá N instancias de objetos de uma classe Filho, como por exemplo a relação de Conteúdo para Fotos. Assumindo este exemplo, deixe os arquivos como a seguir:

models.py
from django.db import models
# Create your models here.
class Conteudo(models.Model):
 """(Conteudo description)"""

 # ...
 # SEUS ATRIBUTOS AQUI
 # ...

 class Meta:
  verbose_name, verbose_name_plural = u"Conteúdo" , u"Conteúdos"


class Foto(models.Model):
 """(Foto description)"""
 conteudo= models.ForeignKey(Conteudo, verbose_name=u'Conteúdo')
 imagem = models.ImageField(upload_to="uploads/conteudos/imagem/%Y")

 class Meta:
  verbose_name, verbose_name_plural = u"Foto" , u"Fotos"
  ordering = ('id',)

 def __unicode__(self):
  return "%s" % str(self.imagem)

forms.py
from django.forms.models import inlineformset_factory
from models import Conteudo, Foto
class ConteudoForm(forms.ModelForm):
 class Meta:
  model = Conteudo

FotoFormSet = inlineformset_factory(Conteudo, Foto, extra=1)


views.py
from models import *
from django.shortcuts import render_to_response, get_object_or_404, redirect
from django.template import Context, loader, RequestContext
from django.core.context_processors import csrf
from forms import ConteudoForm, FotoFormSet

def conteudo_enviar(request):
 ret = ''
 if request.POST:
  c = {}
  c.update(csrf(request))

  r = request.POST
  f = request.FILES
  form = ConteudoForm(r,f)
  formset = FotoFormSet(r,f)

  if form.is_valid() and request.FILES:
   try:
    form.save()
    formset.instance = get_object_or_404(Conteudo, id=form.instance.id)
    formset.save()
    ret = u'Conteúdo enviado com sucesso.'

    # ZERANDO O FORM
    form = ConteudoForm()
    formset = FotoFormSet()

   except:
    ret = u'Erro ao enviar o conteúdo'
  elif request.FILES:
   for field in form:
    if field.errors:
     ret += "%s: %s" % (field.label, striptags(str(field.errors)) )
  else:
   ret += "Imagem: pelo menos uma imagem do conteúdo deve ser enviada"
 else:
  form = ConteudoForm()
  formset = FotoFormSet()

 VARS = {
  'form':form,
  'formset':formset,
  'ret':ret,
 }
 return render_to_response('conteudo_template.html', VARS, context_instance=RequestContext(request))

conteudo_template.html

<script type="text/javascript" src="http://underscorejs.org/underscore-min.js"></script>
<script type="text/html" id="foto-template">
 <div id="foto-<%= id %>" class="imagem">
  <input id="id_foto_set-<%= id %>-id" type="hidden" name="foto_set-<%= id %>-id">
     <label for="id_foto-<%= id %>-title">Imagem:</label>
     <input id="id_foto_set-<%= id %>-imagem" class="file" type="file" name="foto_set-<%= id %>-imagem">
     <input type="hidden" name="foto-<%= id %>-acao" id="id_foto-<%= id %>-acao">
     <input type="hidden" name="foto-<%= id %>-id" id="id_foto-<%= id %>-id">
     {% if not request.META.HTTP_USER_AGENT|isIE %}
   <div class="filename"></div>
   <div class="button-container">
   <input type="button" name="filebutton" class="filebutton" value="Procurar">
   </div>
  {% endif %}
  </div>
</script>

<script type="text/javascript">
jQuery(document).ready(function($) {
  // Stuff to do as soon as the DOM is ready;
  $('.add-foto').click(function(ev){
  ev.preventDefault();
  var count = $('.fotos').children().length;
  var tmplMarkup = $('#foto-template').html();
  var compiledTmpl = _.template(tmplMarkup, { id : count });
  $('div.fotos').append(compiledTmpl);
  // update form count
  $('#id_foto_set-TOTAL_FORMS').attr('value', count+1);
  console.log(count+1);
 });
});
</script>
{{ formset.management_form }}
<div class="fotos">
 {% for foto_form in formset %}
  <div id="foto-{{ forloop.counter0 }}" class="imagem">
   {{ foto_form.id }}
   {% if request.META.HTTP_USER_AGENT|isIE %}
    {# IE HACK PARA INPUT FILE #}
    {{ foto_form }}
    {# <input type="file" id="id_imagem" name="imagem[]" class="file" style="right:0; height: 50px; width:478px; position:absolute;opacity:1.0;filter:alpha(opacity=100);"> #}
   {% else %}
    {{ foto_form }}
    <div class="filename"></div>
    <div class="button-container">
     <input type="button" name="filebutton" class="filebutton" value="Procurar">
    </div>
   {% endif %}
  </div>
 {% endfor %}
</div>
<div class="form-actions">
  <button class="medium green add-foto">Adicionar mais fotos</button>
</div>

Para as class-based views, mostrarei como fazer no próximo post. hasta!

sexta-feira, 17 de maio de 2013

Como criar uma aba no Facebook e pegar os dados dos usuário no aplicativo

Extraído de: http://blog.hubspot.com/blog/tabid/6307/bid/26330/How-to-Create-Custom-Tabs-for-Facebook-Business-Pages.aspx

Passo 1: Faça login como Desenvolvedor

Faça login como desenvolvedor no Facebook em : https://developers.facebook.com/apps

Passo 2: Crie e nomeie seu novo aplicativo

Clique em "Criar um novo aplicativo" no canto superior esquerdo. Você precisa fornecer um "Display Name", que é como seu "app", ou guia, será chamado. Você também precisa criar um "namespace", que é basicamente apenas um ID único para o seu aplicativo.
create new facebook app

Passo 3: Escolha uma imagem e atualizar informações básicas para a App


Escolha uma imagem e um ícone para o seu guia personalizado. Lembre-se, isso vai exibir no topo da sua página do Facebook, então pense nisso como um call-to-action. Escolha uma imagem que vai receber seus visitantes ao clicar!

Atualize suas informações básicas com o nome de domínio e categoria. Você também pode fazer upload de uma imagem personalizada para o seu aplicativo, clicando em "ícone de edição."

basic info


Passo 4: Criar o conteúdo que será exibido dentro da aba do aplicativo


Agora crie uma página da web fora do Facebook. Isto é o que será exibido dentro de sua guia personalizado Facebook. Para garantir o conteúdo da página exibida corretamente no Facebook, certifique-se que a largura da sua página web esteja configurado para 100%, 520px ou 810px. Certifique-se de todas as imagens, vídeos, etc, que você incluir em sua página são menos do que qualquer 520px ou 810px, dependendo de quão grande você escolheu para fazer o conteúdo da sua guia.

facebook screenshot


Para se certificar de que todos podem ver o sua nova guia da página do Facebook, você também precisará fornecer uma URL segura. Esta deve ser a mesma URL que você forneceu para o separador de página, mas com "https://" em vez de "http://" na frente. Se o seu site não suporta https, o aplicativo ainda funciona para quem tem a navegação segura desativada em suas configurações do Facebook, mas você deve obter um certificado SSL. 

Salve as alterações que você fez para o app.


Passo 5: Diga Facebook o conteúdo que você deseja exibir no seu guia Personalizar


Volte para suas configurações de aplicativos em developers.facebook.com, desça até a opção "Selecionar como seu aplicativo se integra com Facebook" seção, e escolha "Page Tab",  nomeie do seu guia que você gostaria, então copie a URL da página que você criou no passo 4, e cole a URL em "Página Tab URL".


page tab


Para se certificar de que todos podem ver o sua nova guia da página do Facebook, você também precisará fornecer uma URL segura. Esta deve ser a mesma URL que você forneceu para o separador de página, mas com "https://" em vez de "http://" na frente. Se o seu site não suporta https, o aplicativo ainda funciona para quem tem navegação segura desativada em suas configurações do Facebook, mas você deve olhar para obter um certificado SSL.

Salve as alterações que você fez para o app.

Passo 6: Adicione o seu guia para sua página do Facebook 


Este passo pode ser um pouco complicado, então leia com atenção. Para instalar sua guia nova em sua página de negócios, você precisa visitar um link com vários parâmetros de URL personalizada. O link é: 

http://www.facebook.com/dialog/pagetab?app_id=YOUR_APP_ID&amp;next=YOUR_URL 

Você vai precisar de substituir os termos em negrito na URL acima. "YOUR_APP_ID" e "YOUR_URL" com algumas informações que o Facebook oferece para o seu aplicativo, o ID do seu aplicativo e a url do seu site.

facebook summary


Passo 7: Página que será renderizada na aba e channel file

Channel File

Crie um arquivo com o conteúdo abaixo que será utilizado na página da sua aba:

<?php

  $cache_expire = 60*60*24*365;
  header("Pragma: public");
  header("Cache-Control: maxage=".$cache_expire);
  header('Expires: '.gmdate('D, d M Y H:i:s', time()+$cache_expire).' GMT');
?>
<script src="//connect.facebook.net/en_US/all.js"></script>

Se não tiver trabalhando com php, pode criar um arquivo apenas com o script do facebook, o php acima serve apenas para controle de cache, ou pode fazer da forma que sua linguagem assim permitir.

Conteúdo da aba:

Na sua página que se hospedará externamente ao facebook, insira os códigos abaixo, subistituindo os devidos valores:

Antes de fechar a tag HEAD:

<meta property="og:title" content="TITULO" />
<meta property="og:url" content="<?=$url?>/" />
<meta property="og:type"        content="company" />
<meta property="og:site_name"   content="TITULO DO SITE" />
<meta property="og:description" content="DESCRICAO" />
<meta property="fb:app_id"      content="ID DO APLICATIVO" />
<meta property="og:image"       content="https://URL DA THUMB/thumb.jpg" />

Logo depois da abertura da tag BODY:

<!-- Facebook JS SDK -->
<div id="fb-root"></div>
<script>
 // Additional JS functions here
 window.fbAsyncInit = function() {
  FB.init({
    appId      : 'ID DO APLICATIVO', // App ID
    channelUrl : '//CHANNEL FILE', // Channel File ex: 'SEU SITE/channel.php'
    status     : true, // check login status
    cookie     : true, // enable cookies to allow the server to access the session
    xfbml      : true  // parse XFBML
  });

  // Additional init code here
  FB.getLoginStatus(function(response) {
   if (response.status === 'connected') {
    // connected
    //alert('usuario já autorizou a aplicacao');
    //console.log(JSON.stringify(response));

    FB.api('/me', function(response){
     //DADOS ESTARÃO TODOS NO RESPONSE
     // Ex:
     // NOME = response.name
    });

   } else if (response.status === 'not_authorized') {
    // not_authorized
    login();
   } else {
    // not_logged_in
    login();
   }
  });
 };

 function login() {
  FB.login(function(response) {
   if (response.authResponse) {
    // connected
    FB.api('/me', function(response) {
     location.reload();
    });
   } else {
    // cancelled
    //alert("Usuário Cancelou");
   }
  },{scope: 'email'});
 }

 function postToWall() {
  FB.login(function(response) {
    if (response.authResponse) {
   FB.ui({
    method: 'feed',
    name: 'NOME',
    link: 'LINK DO SEU APLICATIVO', // EX: https://www.facebook.com/SUA PAGINA/app_(ID DO APLICATIVO)';
    picture: 'URL DA FOTO DO POST',
    caption: 'LEGENDA',
    description: 'DESCRICAO/TEXTO DO POST'
   },
   function(response) {
     if (response && response.post_id) {
    //alert('Post was published.');
     } else {
    //alert('Post was not published.');
     }
   });
    } else {
   //alert('User cancelled login or did not fully authorize.');
    }
  }, {scope: 'publish_stream'});
  return false;
 }

 // Load the SDK Asynchronously
 (function(d){
  var js, id = 'facebook-jssdk', ref = d.getElementsByTagName('script')[0];
  if (d.getElementById(id)) {return;}
  js = d.createElement('script'); js.id = id; js.async = true;
  js.src = "//connect.facebook.net/en_US/all.js";
  ref.parentNode.insertBefore(js, ref);
 }(document));

Considerações:

  • O que acontece na verdade depois de tudo isso é que sua página será executada dentro de um iframe do facebook, e graças a isso, é possível fazer tudo que uma página normal faz: upload, persistência em banco de dados, ajax, etc.
  • O SSL se faz ncessário, pois caso não exista, apenas usuários que desabilitaram esta opção nas configurações do facebook poderão ver o aplicativo, sendo que muitos não fazem ideia de onde isso fica, e nem o que faz.
  • A função de login, solicita além dos dados publicos (nome, lista de amigos, etc) o eemail da pessoa. Para mais permissões, consulte https://developers.facebook.com/docs/reference/login/#permissions

quarta-feira, 8 de maio de 2013

GIT - Visão geral e comandos úteis

Git é um sistema de controle de versão distribuído grátis e de código aberto,  desenhado para lidar com tudo, desde pequenos até grandes projetos com rapidez e eficiência.

Para instalar:

Ou também pode ser usado os famosos apt-get, yum, ports, etc.

Fluxo

Seu repositório consiste em três "árvores" mantidas pelo git. A primeira delas é sua Working Directory que contém os arquivos vigentes que estão na máquina. A segunda é a Index que funciona como uma área temporária, que contém as mudanças propostas pelos comandos "add" e finalmente a HEAD que aponta para o último commit (confirmação) que você fez.


Comandos mais utilizados:

  • git init
    • Para criar um novo repositório
  • git clone usuário@servidor:/caminho/para/o/repositório
    • Para clonar um repositório em um servidor remoto
  • git add /caminho/para/o/arquivo
    • Para adicionar um arquivo que foi criado e ainda não está sob controle do git
  • git add .
    • Para adicionar todos os arquivos, novos e modificados
  • git commit -m 'comentario sobre sua mudança'
    • Para dar o commit nas alterações propostas pelo add
  • git pull origin master
    • Fazer o download das ultima revisão dos arquivos do repositorio (Head)
  • git push origin master
    • Enviar a árvore do repositório (Head) as mudanças do commit
  • git status
    • Verificar o status de cada arquivo/pasta no repositório
  • git remote add origin <servidor>
    • Se você não clonou um repositório existente e quer conectar seu repositório a um servidor remoto, você deve adicioná-lo

Na prática

  1. git clone usuário@servidor:/caminho/para/o/repositório
  2. git pull origin master
  3. Crie/modifique os arquivos
  4. git add .
  5. git commit -m 'comentario sobre sua mudança'
  6. git push origin master


Links Úteis




sexta-feira, 19 de abril de 2013

Gerando um PDF para download ou arquivo a partir de um Template em Django

Você vai precisar dos seguintes aplicativos instalados no ENV:

  • html5lib==0.95
  • pisa==3.0.33
  • reportlab==2.7

Crie um arquivo chamado report.py na raiz do seu projeto com o conteúdo abaixo:
# -*- coding: utf-8 -*-
from django import http
from django.conf import settings
from django.http import HttpResponse
from django.template import Context
from django.template import RequestContext
from django.template.loader import get_template
from django.template.loader import render_to_string
import cgi, os
import cStringIO as StringIO
import ho.pisa as pisa

def fetch_resources(uri, rel):
    path = os.path.join(settings.STATIC_PATH, uri.replace(settings.MEDIA_URL, ""))
    return path

def write_to_pdf(template_src, context_dict, filename):
    template = get_template(template_src)
    context = Context(context_dict)
    html  = template.render(context)
    result = StringIO.StringIO()
    pdf = pisa.pisaDocument(StringIO.StringIO(html.encode("UTF-8")), result, link_callback=fetch_resources)
    if not pdf.err:
        response = http.HttpResponse(mimetype='application/pdf')
        response['Content-Disposition'] = 'attachment; filename=%s.pdf' % filename
        response.write(result.getvalue())

        # OPCIONAL, CASO QUEIRA GERAR O ARQUIVO EM DISCO
        f = open('%s/uploads/projetos/%s.pdf' % (settings.MEDIA_ROOT, filename), 'w+')
        f.write(result.getvalue())
        f.close()


        return response

    return http.HttpResponse('Problema ao gerar PDF: %s' % cgi.escape(html))

Crie a view que será responsável pela chamada da geração do PDF:
from app.report import write_to_pdf

def projeto2pdf(request, slug=None):
    projeto = get_object_or_404(Projeto, slug=slug)
    response = write_to_pdf('template_pdf.html', {'projeto': projeto}, projeto.slug)
    return response

Caso seu template possua alguma imagem, lembre-se de colocar nos src das tags img apenas o caminho a partir do caminho definido na def fetch_resorces do arquivo report.py

...
<img src="site/img/sua_imagem.png" alt="imagem" width="176" height="103">
...

Agora basta criar a url para a chamada da view:
...
url(r'^projeto2pdf/(?P<slug>[-\w]+)/$', 'projeto2pdf', name='projeto2pdf'),
...

hasta!

domingo, 7 de abril de 2013

Enviando email multi-formato (txt, html) com EmailMultiAlternatives

# ENVIANDO EMAIL DE CADASTRO
from django.template import Context, loader, RequestContext
from django.core.mail import EmailMultiAlternatives

tHtml = loader.get_template('email/cadastro.html')
tTxt = loader.get_template('email/cadastro.txt')

c = Context({
 'email': self.email,
 'senha': self.senha,
})

destinatario = []
destinatario.append(self.email)
subject = "PROMOÇÃO INOVE SUA COZINHA com WICKBOLD"
emailFrom = ""
      
text_content = tTxt.render(c)
html_content = tHtml.render(c)
msg = EmailMultiAlternatives(subject, text_content, emailFrom, destinatario)
msg.attach_alternative(html_content, "text/html")
   
try:
 msg.send()
except:
 pass

quinta-feira, 4 de abril de 2013

NoReverseMatch Exception Value: u"'" is not a registered namespace

Nas versões mais novas do Django (1.4+), o comportamento da tag {% url %} utilizada nos templates mudou, e para continuar utilizando, agora com suporte aos NAMESPACES, deve-se fazer conforme abaixo:


No urls.py utilize namespaces conforme abaixo:
 url(r'^', include('appviews.urls', namespace='appsite')),

Certifique-se que no template que deseja utilizar a tag {% url %} tenha o import abaixo:
{% load url from future %}

Para utilizar a tag {% url %} em seus links, utilize a nova sintaxe:
<a href="{% url '<NAMESPACE>:<URL_NAME>' %}">Seu link</a>




hasta!

terça-feira, 8 de janeiro de 2013

Autenticação do Django utilizando old_password do mysql

No mesmo diretório do seu arquivo settings.py crie um arquivo chamado hashers.py com o seguinte conteúdo:

from django.contrib.auth.hashers import BasePasswordHasher, mask_hash

from django.utils.datastructures import SortedDict
from django.utils.crypto import constant_time_compare
from django.utils.translation import ugettext_noop as _

class MySQLOldPasswordHasher(BasePasswordHasher):
    """
    Classe para criptografar as senhas utilizando o modelo da funcao old_password do mysql 4.x
    Class to encrypt the passwords using the model of mysql 4.x  old_password function
    """
    algorithm = "mysql_old_password"

    def salt(self):
        return ''

    def encode(self, password, salt):
        nr = 1345345333
        add = 7
        nr2 = 0x12345671

        for c in (ord(x) for x in password if x not in (' ', '\t')):
            nr^= (((nr & 63)+add)*c)+ (nr << 8) & 0xFFFFFFFF
            nr2= (nr2 + ((nr2 << 8) ^ nr)) & 0xFFFFFFFF
            add= (add + c) & 0xFFFFFFFF

        password = "%08x%08x" % (nr & 0x7FFFFFFF,nr2 & 0x7FFFFFFF)
        return "%s$%s" % (self.algorithm, password)

    def verify(self, password, encoded):
        encoded_2 = self.encode(password, '')
        return constant_time_compare(encoded, encoded_2)

    def safe_summary(self, encoded):
        return SortedDict([
            (_('algorithm'), self.algorithm),
            (_('hash'), mask_hash(encoded, show=3)),
        ])

Adicone esse novo hasher no início da setting PASSWORD_HASHERS no seu arquivo settings.py:

PASSWORD_HASHERS = (
    'app.hashers.MySQLOldPasswordHasher',
    'django.contrib.auth.hashers.BCryptPasswordHasher',
    'django.contrib.auth.hashers.PBKDF2PasswordHasher',
    'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
    'django.contrib.auth.hashers.SHA1PasswordHasher',
    'django.contrib.auth.hashers.MD5PasswordHasher',
    'django.contrib.auth.hashers.CryptPasswordHasher',
)

A função de login fica como de costume:

from django.contrib.auth import authenticate, logout, login as authlogin
from django.core.context_processors import csrf
def logar(request):
 if request.POST:
  c = {}
  c.update(csrf(request))
  r = request.POST

  usuario = r.get('usuario')
  senha = r.get('senha')

  u = authenticate(username=usuario, password=senha)

  if u is not None:
   c = u.get_profile()
   if c:
    if c.ativo:
     if u.is_active:
      authlogin(request, u)
      # USUARIO LOGADO COM SUCESSO
      return HttpResponse('1')
    # USUARIO INATIVO     
    return HttpResponse('-3')
  # USUARIO NAO ENCONTRADO / SENHA INCORRETA
  return HttpResponse('0')
 # REQUISICAO SEM POST
 return HttpResponse('-1')


Da mesma forma pode ser feito com outros hashers. Basta adicioná-los ao arquivo hashers.py e listá-los na ordem desejada no settings como vimos acima.

Uma coisa notada analisando os algoritmos de hash disponível no Django (disponíveis em django.contrib.auth.hashers), o password gerado automático que é salvo no banco possui 3 partes separadas pelo simbolo "$":

  • Algoritmo de criptografia utilizado
  • Número de iterações
  • Hash da senha
No exemplo acima que a função old_password() do mysql não possui iterações, a segunda parte é omitda.



hasta!