Mostrando postagens com marcador Django/Python. Mostrar todas as postagens
Mostrando postagens com marcador Django/Python. Mostrar todas as postagens

quinta-feira, 14 de setembro de 2017

Django + Masonry + Imagesloaded + Twitter Style Pagination

O que é o Masonry?

Como o proprio site do plugin explica:
Masonry is a JavaScript grid layout library. It works by placing elements in optimal position based on available vertical space, sort of like a mason fitting stones in a wall. You’ve probably seen it in use all over the Internet.

 O objetivo no final deste post é ter um layout organizado pelo plugin, com registros paginados e requisições via ajax para obter os registros das páginas seguintes, utilizando uma Class Based View no Django.

Requisitos:




Bom, primeiramente faremos a View:

class PostListView(View):
	def get(self, request, *args, **kwargs):

		posts = Post.objects.all()

		RPP = 18
		PAGINA = 1
		TOTAL = posts.count()

		if request.GET.get('page'):
			PAGINA = int(request.GET.get('page'))

		page_template = 'blog/paginacao.html'

		template = 'blog/list.html'
		if request.is_ajax():
			template = page_template

		tem_mais = posts[PAGINA*RPP+1:]
		tem_mais = tem_mais.count()
		
		posts = posts[(PAGINA-1)*RPP:PAGINA*RPP]

		if posts.count() == 0:
			return HttpResponse('')

		VARS = {
			'posts':posts,
			'pag':PAGINA,
			'tem_mais': tem_mais,
			'total': TOTAL,
		}
		return render(request, template, VARS)


Serão necessários dois templates:

blog/list.html
{% extends "base.html" %}

{% block scripts %}
    <script src="{% static "site/vendor/masonry/jquery.masonry.pkgd.min.js" %}" type="text/javascript"></script>
    <script src="{% static "site/vendor/masonry/imagesloaded.pkgd.min.js" %}" type="text/javascript"></script>
    <script src="{% static "site/src/js/layout.js" %}" type="text/javascript"></script>
    <script src="{% static "site/src/js/blog.list.js" %}" type="text/javascript"></script>
{% endblock scripts %}

{% block conteudo %}
	<!-- Masonry Grid -->
	<div class="masonry-grid">
	    <div class="masonry-grid-sizer col-xs-6 col-sm-4 col-md-3 col-lg-2"></div>

	    {% for p in posts %}
	        <!-- Post Item -->
	        <div class="masonry-grid-item col-xs-6 col-sm-4 col-md-3 col-lg-2">
	            
	        </div>
	        <!-- End Post Item -->
	    {% endfor %}

	</div>
	<!-- End Masonry Grid -->
	<div class='loading-bg'>
	    <div class='loading'></div>
	</div>
{% endblock conteudo %}

blog/paginacao.html
<!-- Masonry Grid -->
	<div class="masonry-grid">
	    <div class="masonry-grid-sizer col-xs-6 col-sm-4 col-md-3 col-lg-2"></div>

	    {% for p in posts %}
	        <!-- Post Item -->
	        <div class="masonry-grid-item col-xs-6 col-sm-4 col-md-3 col-lg-2">
	            
	        </div>
	        <!-- End Post Item -->
	    {% endfor %}

	</div>
	<!-- End Masonry Grid -->
	<div class='loading-bg'>
	    <div class='loading'></div>
	</div>

layout.js:
var Layout = function () {
    'use strict';

    var mobile = (/android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(navigator.userAgent.toLowerCase()));

    // handle on page scroll
    var handleHeaderOnScroll = function() {
        if ($(window).scrollTop() > 60) {
            $('body').addClass('page-on-scroll');
        } else {
            $('body').removeClass('page-on-scroll');
        }
    }

    // handle carousel
    var handleCarousel = function() {
        var $item = $('.carousel .item');
        var $wHeight = $(window).height();
        $item.eq(0).addClass('active');
        $item.height($wHeight);
        $item.addClass('full-screen');

        $('.carousel img').each(function() {
            var $src = $(this).attr('src');
            var $color = $(this).attr('data-color');
            $(this).parent().css({
                'background-image' : 'url(' + $src + ')',
                'background-color' : $color
            });
            $(this).remove();
        });

        $(window).on('resize', function (){
            $wHeight = $(window).height();
            $item.height($wHeight);
        });
    }

    if($(window).width() > 992) {
        $('.screen__height').height($(window).height()/1.3);

        $(window).resize(function(){
            $('.screen__height').height($(window).height()/1.3);
        });
    }



    // handle group element heights
    var handleHeight = function() {
       $('[data-auto-height]').each(function() {
            var parent = $(this);
            var items = $('[data-height]', parent);
            var height = 0;
            var mode = parent.attr('data-mode');
            var offset = parseInt(parent.attr('data-offset') ? parent.attr('data-offset') : 0);

            items.each(function() {
                if ($(this).attr('data-height') == "height") {
                    $(this).css('height', '');
                } else {
                    $(this).css('min-height', '');
                }

                var height_ = (mode == 'base-height' ? $(this).outerHeight() : $(this).outerHeight(true));
                if (height_ > height) {
                    height = height_;
                }
            });

            height = height + offset;

            items.each(function() {
                if ($(this).attr('data-height') == "height") {
                    $(this).css('height', height);
                } else {
                    $(this).css('min-height', height);
                }
            });

            if(parent.attr('data-related')) {
                $(parent.attr('data-related')).css('height', parent.height());
            }
       });
    }

    return {
        init: function () {
            handleHeaderOnScroll(); // initial setup for fixed header
            handleCarousel(); // initial setup for carousel
            handleHeight(); // initial setup for group element height

            // handle minimized header on page scroll
            $(window).scroll(function() {
                handleHeaderOnScroll();
            });
        }
    };
}();

$(document).ready(function() {
    Layout.init();
});

blog.list.js
jQuery(document).ready(function($) {

    var carregando = false;
    $(window).on('scroll', function(event) {
        event.preventDefault();
        /* Act on the event */
        if($(window).scrollTop() + $(window).height() > $(document).height() - 100 && carregando == false && parseInt($('#tem_mais').val()) > 0) {
            // console.log("near bottom!");
            carregando = true
            retrive_posts();
        }
    });


    $('body').on('click', '.next', function(event) {
        event.preventDefault();
        carregando = true;
        /* Act on the event */
        retrive_posts();
    });

    function retrive_posts(){
        var pag = parseInt($('#pag').val())+1;
        $('.loading-bg').show();

        $.get('?page='+pag, function(data) {
            /*optional stuff to do after success */
            // console.log(data);
            // console.log(data == '');

            // console.log($('#tem_mais').length);
            console.log($('#tem_mais').val());

            if(data){
                $('#pag').val(parseInt(pag));
                // console.log(data);

                var aux = $('<div/>');
                $(aux).html(data);

                var tem_mais = $(aux).find('.tem_mais')[0];
                $('#tem_mais').val($(tem_mais).val());

                if (parseInt($('#tem_mais').val()) == 0) {
                    $('button.next').hide();
                }

                var $container = $('.masonry-grid');
                $container.imagesLoaded( function() {
                    $container.masonry({
                        itemSelector: '.masonry-grid-item', // use a separate class for itemSelector, other than .col-
                        columnWidth: '.masonry-grid-sizer',
                        percentPosition: true,
                        transitionDuration: 0,
                    });
                });

                // console.log($(aux).find('.masonry-grid-item.appended'));
                $(aux).find('.masonry-grid-item.appended').each(function(index, el) {
                    $(el).css({ opacity: 0 });
                    // console.log($(el));
                    $(el).imagesLoaded(function(){
                      // show elems now they're ready
                      $(el).css({ opacity: 1 });
                      $container.append( $(el) );
                      $container.masonry( 'appended', $(el), true );
                      $(el).removeClass('.appended')
                    });
                });
                carregando = false;
                $('.loading-bg').hide();
            }else{
                $('button.next').hide();
            }

        });
    }
});

segunda-feira, 10 de abril de 2017

from django.utils.importlib import import_module - Django 1.9.X

O erro:

from django.utils.importlib import import_module

ImportError: No module named importlib

Para resolver edite o arquivo que está gerando o erro, no meu caso django-1.9.12/lib/python2.7/site-packages/sorl/thumbnail/helpers.py substitua:

Isto
from django.utils.importlib import import_module

Por:
from importlib import import_module

hasta!

segunda-feira, 26 de setembro de 2016

Manter apenas um usuário logado por credenciais

Neste post vou mostrar como manter apenas um usuário logado por credenciais. Desta forma quando ele logar com as mesmas credenciais em outra máquina, a sessão anterior é deslogada.

No seu models.py da app Cadastro adicione o seguinte atributo:

class Cadastro(models.Model):
 user = models.OneToOneField(User, null=True, blank=True)
 ...
 session_key = models.CharField(max_length=100, null=True)
 ...


E na sua views de login edite conforme a necessidade, incluíndo as linhas abaixo:
...
from django.contrib.auth import authenticate, logout, login as authlogin
from django.contrib.sessions.backends.db import SessionStore
...
u = authenticate(username=email, password=senha)
if u is not None:
 if u.is_active:
  if u.cadastro.session_key: # check if user has session_key. This will be true for users logged in on another device
   try:
    s = SessionStore.objects.get(session_key=u.cadastro.session_key)
   except Session.DoesNotExist:
    pass
  else:
   s.delete() # delete the old session_key from db

  # set new session_key for user instance
  u.cadastro.session_key = request.session.session_key
  u.cadastro.save() # save the user

  u.save()
  authlogin(request, u)
...

hasta!

terça-feira, 20 de setembro de 2016

Como ler feed de redes sociais: Facebook, Twitter, Instagram



A ideia é simples: gostaria de postar algo nas redes sociais e seria fantástico se esses posts fossem incorporados no site.

Bom, tem como.

Primeiro vamos a estrutura:


  1. Crie uma app para armazenar esse conteúdo:
    python manage.py startapp redes_sociais
  2. models.py:
    from django.db import models
    
    # Create your models here.
    
    REDES_SOCIAIS_C = (
    	('FACEBOOK', 'Facebook'),
    	('TWITTER', 'Twitter'),
    	('INSTAGRAM', 'Instagram'),
    )
    
    class Post(models.Model):
    	"""(Post description)"""
    	texto = models.TextField()
    	imagem = models.ImageField(upload_to=U.retira_acento,null=True,blank=True,)
    	imagem_src = models.TextField(null=True,blank=True)
    	redesocial = models.CharField(max_length=255, choices=REDES_SOCIAIS_C)
    	pid = models.CharField(max_length=255)
    	data = models.DateTimeField(null=True, blank=True)
    	link = models.CharField(max_length=255, null=True, blank=True)
    	ativo = models.BooleanField(default=True)
    
    	class Meta:
    		verbose_name, verbose_name_plural = u"Post" , u"Posts"
    		ordering = ('-data',)
    
    	def __unicode__(self):
    		return u"%s" % self.texto
    
    
    	def get_imagem(self):
    		if self.imagem:
    			return self.imagem
    		elif self.imagem_src:
    			return self.imagem_src
    		return None
  3. admin.py:
    # coding: utf-8
    from django.contrib import admin
    from .models import *
    
    class PostAdmin(admin.ModelAdmin):
    	search_fields = ('texto',)
    	list_display = ('texto', 'redesocial', 'pid','link' ,'data','ativo')
    	list_filter = ['redesocial','data','ativo']
    	list_editable = ['ativo',]
    	readonly_fields = ['texto', 'redesocial', 'pid','link' ,'data','imagem','imagem_src']
    	save_on_top = True
    
    	fieldsets = (
    	    (u'Ativo', {'fields': ('ativo',)}),
    	    (u'Infos', {'fields': ('redesocial', 'texto', 'pid', 'link')}),
    	    (u'Data', {'fields': ('data',)}),
    	    (u'Imagem', {'fields': ('imagem','imagem_src')}),
    	)
    
    admin.site.register(Post, PostAdmin)
    
  4. Fiz dois campos para imagens para dar prioridade de edição sobre o campo do tipo file, uma vez que o facebook retorna o caminho da imagem com mais de 255 chars para um varchar normal. O método get_imagem é o responsável por definir qual imagem exibir.

Instagram:

  1. Instale o app em seu celular e crie uma conta.
  2. Acesse: https://www.instagram.com/developer/ e registre uma aplicação
    1. Na aba Details preencha as informações obrigagtórias
    2. Na aba Security coloque uma url válida em Valid redirect URIs. Esta urla lhe dará o access token depois da autorização
    3. Desabilite a opção: Disable implicit OAuth
    4. Acesse https://www.instagram.com/oauth/authorize/?client_id=[CLIENT_ID]&redirect_uri=[URI_DO_PASSO 2.2]&response_type=code
    5. Você será redirecionado para a URI informada com o seu access token no get:
      1. EX: [URI_DO_PASSO 2.2]/?code=rx1p450j0gz2ahgq8ufn0mvw50mvv47e
  3. Configure seu settings.py com as informações abaixo:
    # INSTAGRAM
    INSTAGRAM_USER_ID = '[USER_ID DO FEED]'
    INSTAGRAM_CLIENT_ID = '[CLIENT_ID DO PASSO 2]'
    INSTAGRAM_CLIENT_SECRET = '[CLIENT_SECRET DO PASSO 2]'
    INSTAGRAM_ACCESS_TOKEN = '[TOKEN OBTIDO NO PASSO 5.1]'
    
    
  4. Para o [USER_ID DO FEED] utilizei um site que fornece de maneira fácil: https://smashballoon.com/instagram-feed/find-instagram-user-id/
  5. Instale o python-instagrampy no seu ENV:
    pip install python-instagram==1.3.2
  6. Crie um arquivo chamado feed_instagram.py na mesma pasta do settings.py com o seguinte conteúdo:
    #!/usr/bin/env python
    # coding: utf-8
    
    from os.path import abspath, dirname
    from datetime import datetime, date, timedelta
    import sys, os, commands, time
    
    SETTINGS_DIRECTORY = dirname(dirname(abspath(__file__)))
    
    sys.path.insert(0, SETTINGS_DIRECTORY)
    os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'
    
    from django.conf import settings
    from django.db.models import Count, Min, Sum, Avg
    
    from django.template.defaultfilters import slugify
    
    import re, json
    
    def log(texto):
    	print "===================================="
    	print texto
    	print "===================================="
    
    
    def carga():
    	log("INICIO DA ROTINA")
    	from redes_sociais.models import Post
    	from instagram.client import InstagramAPI
    
    	# AUTH REQUIRED
    	access_token = settings.INSTAGRAM_ACCESS_TOKEN
    	client_secret = settings.INSTAGRAM_CLIENT_SECRET
    	api = InstagramAPI(access_token=access_token, client_secret=client_secret)
    	recent_media, next_ = api.user_recent_media(user_id=settings.INSTAGRAM_USER_ID, count=10)
    	for media in recent_media:
    		#print dir(media)
    		#print media.caption.text
    		post = Post.objects.filter(pid=media.id)
    
    		texto = media.caption.text if media.caption else None
    
    		try:
    			if post:
    				post = post[0]
    				post.texto = u'{0}'.format(texto)
    				post.data = media.created_time
    				post.imagem = media.get_standard_resolution_url()
    			else:
    				post = Post(
    					redesocial = 'INSTAGRAM',
    					pid = media.id,
    					texto = u'{0}'.format(texto),
    					data = media.created_time,
    					link = media.link,
    					imagem = media.get_standard_resolution_url(),
    				)
    			post.save()
    		except Exception, e:
    			log(u"Erro ao inserir [{0}]: {1}".format(media.id, media.get_standard_resolution_url()))
    			log(e)
    
    	# AUTH NON REQUIRED
    	# api = InstagramAPI(client_id=settings.INSTAGRAM_CLIENT_ID, client_secret=settings.INSTAGRAM_CLIENT_SECRET)
    	# popular_media = api.media_popular(count=20)
    	# for media in popular_media:
    	# 	print media.images['standard_resolution'].url
    
    	log("FIM DA ROTINA")
    
    if __name__ == '__main__':
    	os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings")
    	from django.core.wsgi import get_wsgi_application
    	application = get_wsgi_application()
    
    	carga()
    
    
    

Twitter

  1. Crie um aplicativo em: https://apps.twitter.com/
  2. Após criado, na aba settings tem as informações necessárias para configuração:
    1. Access Token [TOKEN]
    2. Access Token Secret [TOKEN_SECRET]
    3. Access Level Read and write
    4. Owner [SEU USUARIO]
    5. Owner ID [SEU ID]
  3. Configure seu settings.py com as informações abaixo:
    1. # TWITTER
      TWITTER_USERNAME = 'zejuniortdr'
      TWITTER_CONSUMER_KEY = '0aYPbXNNNYvhGyPg9CYufUncN'
      TWITTER_CONSUMER_SECRET = 'Lf6MfaSRp3qQaAGlbrVyt0hyo3659obj5bL2W6tiO8PWEAE0vU'
      TWITTER_ACCESS_TOKEN = '30026596-WKqQtcC3AmboRBOqp9uE1cEPfJkZIfeNB8mdR1nVK'
      TWITTER_ACCESS_SECRET = '9j7D0yzgvIybg6vnB9iqac1354Shv21YE2OwSO1oUarTw'
      
  4. Instale o python-instagrampy no seu ENV:
    pip install tweepy==3.3.0
  5. Crie um arquivo chamado feed_twitter.py na mesma pasta do settings.py com o seguinte conteúdo:
    #!/usr/bin/env python
    # coding: utf-8
    
    from os.path import abspath, dirname
    from datetime import datetime, date, timedelta
    import sys, os, commands, time
    
    SETTINGS_DIRECTORY = dirname(dirname(abspath(__file__)))
    
    sys.path.insert(0, SETTINGS_DIRECTORY)
    os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'
    
    from django.conf import settings
    
    import re
    
    def log(texto):
    	print "===================================="
    	print texto
    	print "===================================="
    
    
    def carga():
    	log("INICIO DA ROTINA")
    	from redes_sociais.models import Post
    	import tweepy
    	from tweepy import OAuthHandler
    
    	consumer_key = settings.TWITTER_CONSUMER_KEY
    	consumer_secret = settings.TWITTER_CONSUMER_SECRET
    	access_token = settings.TWITTER_ACCESS_TOKEN
    	access_secret = settings.TWITTER_ACCESS_SECRET
    
    	auth = OAuthHandler(consumer_key, consumer_secret)
    	auth.set_access_token(access_token, access_secret)
    
    	api = tweepy.API(auth)
    
    	username = settings.TWITTER_USERNAME
    	user = tweepy.API(auth).get_user(username)
    	# log(dir(user))
    
    
    	status = api.user_timeline(screen_name=username, count=200, include_entities=True)
    
    
    	for s in status:
    		imagem = None
    		if 'media' in s.entities.keys():
    			imagem = s.entities['media'][0]['media_url']
    
    		post = Post.objects.filter(pid=s.id)
    		try:
    			if post:
    				post = post[0]
    				post.texto = u'{0}'.format(s.text)
    				post.data = s.created_at
    				post.imagem = imagem
    			else:
    				post = Post(
    					redesocial = 'TWITTER',
    					pid = s.id,
    					texto = u'{0}'.format(s.text),
    					data = s.created_at,
    					link = 'https://twitter.com/{0}/status/{1}'.format(username, s.id),
    					imagem = imagem,
    				)
    			post.save()
    		except Exception, e:
    			log(u"Erro ao inserir [{0}]: {1}".format(status.id, status.text))
    
    	log("FIM DA ROTINA")
    
    if __name__ == '__main__':
    	os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings")
    	from django.core.wsgi import get_wsgi_application
    	application = get_wsgi_application()
    
    	carga()
    
    

Facebook

  1. Crie um app com uma conta de desenvolvedor onde esta pessoa seja um dos administradores da Fan Page que quer extrair o feed: https://developers.facebook.com/apps
  2. Configure seu settings.py com as informações abaixo:
    # FACEBOOK
    FACEBOOK_PAGE_ID = [ID DA PAGINA QUE QUER O FEED DE POSTAGENS] # must be integer
    FACEBOOK_APP_ID = [ID DA APLICACAO CRIADA] # must be integer
    FACEBOOK_APP_SECRET = "[APP SECRET DA APLICACAO CRIADA]"
    
  3. Instale as dependencias do Facebook no seu ENV:
    facebook-sdk==2.0.0
    facepy==1.0.8
  4. Crie um arquivo chamado feed_facebook.py na mesma pasta do settings.py com o seguinte conteúdo:
    #!/usr/bin/env python
    # coding: utf-8
    
    from os.path import abspath, dirname
    from datetime import datetime, date, timedelta
    import sys, os, commands, time
    
    SETTINGS_DIRECTORY = dirname(dirname(abspath(__file__)))
    
    sys.path.insert(0, SETTINGS_DIRECTORY)
    os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'
    
    from django.conf import settings
    from django.db.models import Count, Min, Sum, Avg
    
    from django.template.defaultfilters import slugify
    
    import re, json
    
    def log(texto):
    	print "===================================="
    	print texto
    	print "===================================="
    
    
    def carga():
    	log("INICIO DA ROTINA")
    	from redes_sociais.models import Post
    	from facepy import utils, GraphAPI
    	import facebook
    
    	app_id = settings.FACEBOOK_APP_ID
    	page_id = settings.FACEBOOK_PAGE_ID
    	app_secret = settings.FACEBOOK_APP_SECRET
    	oath_access_token = utils.get_application_access_token(app_id, app_secret)
    
    
    	a = GraphAPI(oath_access_token)
    	response = a.get('{0}/posts?fields=id,picture,message,link,full_picture,created_time,object_id'.format(page_id))
    
    	for r in response['data']:
    		post_id = None
    		post_message = None
    		post_link = None
    		post_full_picture = None
    		post_created_time = None
    		for k, v in r.items():
    			if k == 'id':
    				post_id = v
    			elif k == 'message':
    				post_message = v
    			elif k == 'link':
    				post_link = v
    			elif k == 'full_picture':
    				post_full_picture = v
    			elif k == 'created_time':
    				post_created_time = v
    
    		post = Post.objects.filter(pid=post_id)
    
    
    		try:
    			if post:
    				post = post[0]
    				post.texto = u'{0}'.format(post_message)
    				post.pid = u'{0}'.format(post_id)
    				post.data = post_created_time
    				post.imagem_src = post_full_picture
    				post.link = u'{0}'.format(post_link)
    			else:
    				post = Post(
    					texto = u'{0}'.format(post_message),
    					redesocial = 'FACEBOOK',
    					pid = u'{0}'.format(post_id),
    					imagem_src = post_full_picture,
    					data = post_created_time,
    					link = u'{0}'.format(post_link),
    				)
    
    			post.save()
    		except Exception, e:
    			log(u"Erro ao inserir [{0}]: {1} - {2}".format(post_id, post_message, e))
    
    
    	log("FIM DA ROTINA")
    
    if __name__ == '__main__':
    	os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings")
    	from django.core.wsgi import get_wsgi_application
    	application = get_wsgi_application()
    
    	carga()
    
    
    

Agora para executar as rotinas, basta estar com seu ENV ativado e rodar os comandos:
python app/feed_instagram.py 
python app/feed_twitter.py 
python app/feed_facebook.py

Após rodar as rotinas vai ter em sua base de dados os post das três redes sociais. Na view que for fazer o render, basta utilizar o ORM  para obter os registros.

Espero ter ajudado mais gente.

Nota: do Google Plus está em desenvolvimento, mas é a documentação mais enjoada de conseguir informação. Se alguém tiver algum script pronto que faça o que estes acima se propõe a fazer seria de grande ajuda para complementar este post, com os devidos créditos, lógico.


hasta!


sexta-feira, 8 de abril de 2016

SyntaxError: expected expression, got '<' /admin/jsi18n/ (line 1) interpolate is not defined

SyntaxError: expected expression, got '<'  <!DOCTYPE html> /admin/jsi18n/ (line 1) 
interpolate is not defined

Esse erro apareceu quando fiz a utilização do SelectFilter2 para um form no front.

Ele é responsável pela ferramenta de filter_horizontal / filter_vertical do admin, e deveria ser exibido assim:




E com o erro era exibido assim:





Para resolver esse problema:


Adicione no seu urls.py:
...
url(r'^jsi18n/$', 'django.views.i18n.javascript_catalog'),
...


E no seu template que for utilzar o widget:

<script type="text/javascript" src="/jsi18n/" > </script>

sexta-feira, 25 de setembro de 2015

Plugins Úteis: django-geoposition - Como incorporar o google maps dentro do admin para geolocalização

Precisa incorporar um mapa com base em um endereço no administrativo para exibir no front? Tarefa simples pro django-geoposition (https://github.com/philippbosch/django-geoposition).

A instalação é simples:
pip install django-geoposition

Coloque a linha abaixo no seu arquivo settings.py, em INSTALLED_APPS:
INSTALLED_APPS = (
    ...
    'geoposition',
    ...
)

Outra configuração que vale a pena fazer, é incluir no seu arquivo settings.py as linhas abaixo para definir o zoom:
GEOPOSITION_MAP_OPTIONS = {
    'minZoom': 15,
    'maxZoom': 18,
}

No seu arquivo models.py, deixe como a seguir:
from geoposition.fields import GeopositionField
from localflavor.br.br_states import STATE_CHOICES

class SuaClasse(models.Model):
 """(Unidade description)"""
 endereco = models.CharField(max_length=255, verbose_name=u'Endereço', help_text='Para uma melhor localização no mapa, preencha sem abreviações. Ex: Rua Martinho Estrela,  1229') 
 bairro = models.CharField(max_length=255,)
 cidade = models.CharField(max_length=255,help_text="Para uma melhor localização no mapa, preencha sem abreviações. Ex: Belo Horizonte")
 estado = models.CharField(max_length=2, null=True, blank=True,choices=U.STATE_CHOICES)
 position = GeopositionField(verbose_name=u'Geolocalização', help_text="Não altere os valores calculados automaticamente de latitude e longitude")

 class Meta:
  verbose_name, verbose_name_plural = u"Sua Classe" , u"Suas Classes"
  ordering = ('endereco',)

 def __unicode__(self):
  return u"%s" % self.endereco 

No administrativo será gerado tudo automático, mas existem algumas melhorias que podem ser feitas pra ficar ainda mais bacana. Inclua o arquivo css e js abaixo que em seguida explico como faremos.
# coding: utf-8
from django import forms
from django.contrib import admin
from .models import *


class SuaClasseForm(forms.ModelForm):
 class Media:
  css = {
   'all': ('admin/css/geoposition_override.css',)
  }
  js = ('admin/js/geoposition_override.js',)

class SuaClasseAdmin(admin.ModelAdmin):
 form = SuaClasseForm
 search_fields = ('endereco', 'cidade',)
 list_display = ('endereco', 'cidade','estado','bairro')
 list_filter = ['estado',]
 save_on_top = True



admin.site.register(SuaClasse, SuaClasseAdmin)

No arquivo css adicionado (geoposition_override.css), iremos ocultar o campo de busca do mapa, e utilizaremos nossos próprios atributos da classe para tal, mesmo porquê, o atributo position irá só gravar em banco a latitude e longitude e não o endereço pesquisado.

Crie um arquivo dentro de <STATIC_DIR>/admin/css, chamado geoposition_override.cssm, e nele coloque simplemente a linha abaixo:

.geoposition-search input{display:none;}

Agora, crie um js chamado geoposition_override.js na pasta <STATIC_DIR>/admin/js com as linhas abaixo:
django.jQuery(document).ready(function($) {
 $('#id_position_0, #id_position_1').attr('readonly', 'readonly');

 $('#id_endereco, #id_cidade, #id_estado').blur(function(event) {
  /* Act on the event */
  if ($('#id_endereco').val()!='' && $('#id_cidade').val()!='' && $('#id_estado').val()!='') {
   $('.geoposition-search input').val($('#id_endereco').val()+' ' +$('#id_cidade').val()+' '+$('#id_estado').val());
   
   // TRIGGER DO ENTER PARA EXECUTAR A BUSCA
   var e = $.Event("keydown");
   e.which = 50; // # Some key code value
   $(".geoposition-search input").trigger(e);
  };
 });
});

E o resultado:

hasta!

terça-feira, 21 de julho de 2015

django.core.exceptions.AppRegistryNotReady: Models aren't loaded yet.

Precisa escrever um script em python e fazer uso do Django para automatizar algo pela cron?

Na versão atual do django, quando fazemos isso igual nas versões mais antigas, tipo 1.4 até 1.6, recebmos o erro:

django.core.exceptions.AppRegistryNotReady: Models aren't loaded yet.


Para resolver isso, deixe seu script python como a seguir:

#!/usr/bin/env python
# coding: utf-8

from os.path import abspath, dirname

SETTINGS_DIRECTORY = dirname(dirname(abspath(__file__)))

sys.path.insert(0, SETTINGS_DIRECTORY)
os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'

import django
django.setup()


def sincorniza_busca():
 from django.template.defaultfilters import striptags

 # importe seus models aqui
 from sua_app.models import SeuModel

 # faça o que tiver que fazer aqui



if __name__ == '__main__':
 sua_funcao()



hasta!

quinta-feira, 22 de janeiro de 2015

Validações customizadas no forms.ModelForm com método clean para um atributo


As vezes alguns campos requerem validações além do tipo de dados neles inserido. Abaixo tem um exemplo que fiz para um caso desse.

A necessidade era validar um charfield de acordo com uma série de valores e por ser campo aberto não poderia fazer um choices.


models.py
class SuaClasse(models.Model):
 ...
 seu_atributo = models.CharField(max_length=20)
 ...

admin.py
from django.core.exceptions import ValidationError
import re

class SuaClasseAdminForm(forms.ModelForm):
 class Meta:
  model = SuaClasse

 def clean_seu_atributo(self):
  cleaned_data = super(SuaClasseAdminForm, self).clean()
  seu_atributo = cleaned_data.get("seu_atributo")
  if not seu_atributo in ['TL','TR','BR','BL','C','R']:
   m = re.search('[0-9]*(%)*x[0-9]*(%)*', seu_atributo)
   if not m:
    raise ValidationError("Preencha o atributo com uma das opções disponíveis.")

class SuaClasseAdmin(admin.ModelAdmin):
 form = SuaClasseAdminForm

 ...

No exemplo acima, preciso que o valor de seu_atributo seja um dos valores contidos na lista ['TL','TR','BR','BL','C','R'] ou respeite a expressão regular que testa para os seguintes exemplos: 300x200, 50%x300, 40x90% ou 150x400.

O que podemos notar é que o método clean_seu_atributo fará a validação no campo especificado e trará o erro logo acima do campo em caso de falha. Também é possivel fazer somente o método clean, contudo o erro será mostrado no início do form, sem vínculo a campo algum.


hasta!

quarta-feira, 21 de janeiro de 2015

Como criar um PNG com marca d'água e texto dinâmico

A necessidade: Criar uma ferramenta para upload de uma imagem e gerar uma saída com uma marca d'água e um texto dinâmico. Saída era esperada em PNG para melhor renderização no Facebook.

Pra isso, você vai precisar de:
  • PIL==1.1.7
  • django-easy-thumbnails-watermark==0.6.7

Crie uma classe no models.py para tal:
class Visita(models.Model):
 """(Visita description)"""
 data = models.DateField(auto_now_add=True)
 linha1 = models.CharField(max_length=255)
 linha2 = models.CharField(max_length=255, null=True, blank=True,)
 imagem = models.ImageField(upload_to="uploads/visitas/imagem/%Y",help_text="800x671")

 class Meta:
  verbose_name, verbose_name_plural = u"Visita à Fábrica" , u"Visitas à Fábrica"
  ordering = ('-data',)

 def __unicode__(self):
  return u"%s" % self.data


 def gerar(self):
  return 'Gerar' % self.id
 gerar.allow_tags = True
 gerar.is_safe = True

Aqui criei um modelo que tem uma data, por questão de organização, duas linhas para texto que serão inseridas na imagem e a imagem em si.

No urls.py, crie uma entrada para chamada da view:
url(r'^visitas/(?P\d+)/$', 'imagens_facebook.views.visitas', name="visitas"),

E da view abaixo:
def visitas(request, vid):
 img_obj = get_object_or_404(Visita, id=vid)
 img = '{0}/{1}'.format(settings.MEDIA_ROOT, img_obj.imagem)
 IMAGE = Image.open('{0}/{1}'.format(settings.MEDIA_ROOT, img_obj.imagem))
 WM = {
  'image':'{0}/site/img/visita.png'.format(settings.STATIC_PATH,),
  'position': 'BL',
  'opacity': 1,
  'scale': 1,
  'tile': False,
  'greyscale': False,
  'rotation': 0,
 }
 WK_DONE = watermark_processor(IMAGE, WM )
 path = '{0}/watermarks/visitas/{1}'.format(settings.MEDIA_ROOT, 'imagem.png')
 WK_DONE.save(path, "PNG")
 img = Image.open('{0}/watermarks/visitas/{1}'.format(settings.MEDIA_ROOT, 'imagem.png'))
 draw = ImageDraw.Draw(img)
 font = ImageFont.truetype("{0}/fonts/trebuc.ttf".format(settings.STATIC_PATH),20)
 draw.text((60, 465), img_obj.linha1.upper() ,(55,185,115),font=font)
 if img_obj.linha2:
  draw.text((60, 490), img_obj.linha2.upper() ,(55,185,115),font=font)
 img.save('{0}/watermarks/visitas/{1}'.format(settings.MEDIA_ROOT, 'imagem.png'))
 return force_download(request, path)

Linha a linha temos:

  1. Definição da View "visitas" que recebe o request e o id da visita, identificado por "vid"
  2. Obtenção do objeto visita com o id informado
  3. Definição do caminho de onde está a imagem que foi feita upload
  4. Abertura da imagem como objeto da Classe Image
  5. Definição da WaterMark com o parâmetros necessários:
    1. caminho da marca d'agua
    2. Posição que será inserida, BL para bottom left
    3. Opacidade =1 para não ter transparencia, do contrario coloque valores entre 0 e 1.
    4. Escala 1 para não alterar o tamanho
    5. Tile é falso para não esticar imagem utilizada como marca d'agua em toda a imagem definida no upload.
    6. Escala de cinza falso para sair colorido
    7. Sem rotação para sair em orientação normal.
  6. Criação da imagem com marca d'agua utilizando o watermark_processor
  7. Definição de onde será gerado o arquivo
  8. Ação de salvar para gerar o arquivo em png
  9. A partir desta linha é onde será escrito o texto dinamico. Aqui abrimos a imagem gerada no passo anterior.
  10. Criação de um objeto draw da classe ImageDraw.
  11. Definição da fonte a ser utilizada. Aqui será necessário copiar o arquivo da fonte para seu projeto.
  12. A escrita do texto proprimente dita com os respectivos parâmetros: posição(left, top), texto a ser inserido, cor em RGB e caminho da font definida no passo anterior
  13. Apenas um if para testar se há uma segunda linha no texto.
  14. Se houver repete a mesma ação do passo 12, passando o texto diferente.
  15. Ação de salvar a imagem para gerar o arquivo físico.
  16. View para forçar o download do arquivo gerado. Esta view pode ser vista aqui: http://djangoweb.blogspot.com.br/2011/10/forcando-download-de-arquivos-no-django.html

hasta!


quinta-feira, 15 de janeiro de 2015

Tradução de maneira descomplicada com o Model Translation


Todo mundo já deve ter passado por um projeto multi-idioma. Se ainda não, recomendo mesmo assim a leitura deste post. Por participar de algo assim, pude aprender mais algumas coisas interessantes.

Aqui seguem alguns passos que podem auxiliar e muito a confecção de um projeto multi-idioma

1. Primeiro de tudo, defina quais idiomas o projeto terá. 

Isso se faz com algumas configurações no settings.py:

LANGUAGE_CODE = 'pt-br'
TIME_ZONE = 'America/Sao_Paulo'
USE_I18N = True
USE_L10N = True
ugettext = lambda s: s
LANGUAGES = (
    ('pt-BR', ugettext(u'Português')),
    ('en-US', ugettext(u'Inglês')),
    ('es-ES', ugettext(u'Espanhol')),
)
USE_TZ = False
LOCALE_PATHS = (
   os.path.join( PROJECT_PATH, '../locale' ),
)
  • LANGUAGE_CODE: Aqui é definido a linguagem inicial do projeto. Valor padrão: 'en-us'. Veja a lista de outras línguas aqui: http://www.i18nguy.com/unicode/language-identifiers.html
  • TIME_ZONE: Define o fuso horário do projeto. Valor padrão: 'America/Chicago' 
  • USE_I18N: Flag que determina se o sistema de tradução do Django deve ser habilitado ou não. Deve ser True para multi-idiomas e False, por questões de otimização, quando tiver apenas um idioma. Valor padrão: False 
  • USE_L10N: Um booleano que especifica se a formatação localizada de dados será ativado por padrão ou não. Se esta é definida como True , por exemplo Django irá exibir números e datas usando o formato da localidade atual. Valor padrão: True 
  • LANGUAGES: Tupla com as linguagens que serão utilizadas no site. 
  • USE_TZ: Determina se o django usará o datetime com base no fuso ou não.Valor padrão: False 
  • LOCALE_PATH: Tupla com os diretórios onde serão gerados os arquivos para tradução.

2. Instale o django-modeltranslation

$ pip install django-modeltranslation

No settings, certifique-se que o modeltranslation está acima do admin do Django, pra não ter problemas, coloque ele como a primeira linha do INSTALLED_APPS:
INSTALLED_APPS = (
    # TRANSLATION
    'modeltranslation',
    ...
)

Cerfitique-se que o Locale Middleware está instalado:
MIDDLEWARE_CLASSES = (
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
    'django.middleware.locale.LocaleMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
)

E inclua a url de tradução no urls.py:
...
(r'^i18n/', include('django.conf.urls.i18n')),
...

Na aplicação que terá conteúdos dinâmicos para cada idioma deixe os arquivos como abaixo:

models.py
# coding: utf-8
from django.db import models

# Create your models here.

class Conteudo(models.Model):
	"""(Conteudo description)"""
	titulo = models.CharField(u'Título', max_length=255)
	texto = models.TextField()
	slug = models.SlugField(verbose_name=u'Slug / URL')

	class Meta:
		verbose_name, verbose_name_plural = u"Conteúdo" , u"Conteúdos"
		ordering = ('titulo',)

	def __unicode__(self):
		return u"%s" % self.titulo

admin.py
# Register your models here.

from django.contrib import admin
from .models import *
from modeltranslation.admin import TranslationAdmin

class ConteudoAdmin(TranslationAdmin):
	search_fields = ('titulo', 'texto',)
	list_display = ('titulo', 'texto')
	prepopulated_fields = {"slug": ("titulo",)}
	save_on_top = True

admin.site.register(Conteudo, ConteudoAdmin)

translation.py
# translation.py

from modeltranslation.translator import translator, TranslationOptions
from .models import *

class ConteudoOptions(TranslationOptions):
    fields = ('titulo', 'texto',)
translator.register(Conteudo, ConteudoOptions)


Quando usar a migração (South para Django<1.7, ou nativo para Django>=1.7), serão gerados no banco os campos definidos no translation.py para cada idioma, isto é, o resultado será uma tabela com:
  • titulo_pt_br
  • texto_pt_br
  • titulo_en_us
  • texto_en_us
  • titulo_es_es
  • texto_es_es
  • slug

E o admin já trará todos eles com uma marcação de idioma através de [<idioma>] nas labels de cada campo.


Observação Importante: 

Caso não haja necessidade de traduzir a url que ficará no navegador, não inclua o slug no translation.py (recomendo que não inclua! Insista, convença seu chefe!). Isso facilita e muito quando o usuario troca de idioma para redirecioná-lo para a mesma página ao invés de mandá-lo de volta pra home do site e/ou fazer qualquer gambiarra para redirecioná-lo corretamente.

3. Nos templates, fica muito simples:

{{ obj.titulo }}

Aqui o próprio Model Translation irá verificar qual o é o idioma atual e exibir o valor correto. Sem IFs e sem duplicação de templates.

4. Para conteúdos estáticos, o processo continua o mesmo com a geração dos .po

Carrege o módulo i18n nos templates que for traduzir:
{% load i18n %}

Marque nos templates estáticos onde sofrerá tradução com as tags trans e blocktrans
{% trans "Exemplo de texto a ser traduzido nos seus htmls" %}

Execute o comando makemessages para gerar as mensagens:
$ python manage.py makemessages --locale=en --ignore=templates/admin --ignore=project/settings.py

Serão gerados arquivos .po para tradução no LOCALE_PATH definido nas settings com a seguinte estrutura:
  • locale
    • en
      • LC_MESSAGES
        • django.po
    • es
      • LC_MESSAGES
        • django.po


Nestes arquivos, serão armazenados os termos a serem traduzidos para envio para a pessoa responsável para tradução, e eles se parecem com isso:
#: templates/atendimento/atendimento.html:21
#: templates/atendimento/sucesso.html:21 templates/curriculos/sucesso.html:21
#: templates/curriculos/trabalhe-conosco.html:25
msgid "Dados enviados com sucesso"
msgstr ""

No exemplo acima, as linhas iniciadas com # indicam o caminho dos arquivos em tem o termo contido no msgid em comum, e no msgstr é onde deve ser preenchido com a tradução para o idioma correspondente.

Caso a pessoa responsável pela tradução não conseguir/quiser editar direto os .po, existem alguns apps que podem ser utilizados para facilitar esse processo, como o Rosetta (mais informações em: https://github.com/mbi/django-rosetta) por exemplo. Este cria uma interface com base no bom e velho admin provido pelo Django.

Depois de tudo preenchido, basta compilar as mensagens com o comando compilemessages:
$ python manage.py compilemessages --locale=en
$ python manage.py compilemessages --locale=es


5. Para trocar de idioma no site, basta um form:


{% get_available_languages as LANGUAGES %}
{% get_current_language as LANGUAGE_CODE %}

<form action="{% url 'set_language' %}" method="post">
{% csrf_token %}
<input name="next" type="hidden" value="{{ redirect_to }}" />
<select name="language">
{% get_language_info_list for LANGUAGES as languages %}
{% for language in languages %}
<option value="{{ language.code }}"{% if language.code == LANGUAGE_CODE %} selected="selected"{% endif %}>
    {{ language.name_local }} ({{ language.code }})
</option>
{% endfor %}
</select>
<input type="submit" value="Go" />
</form>


Em {{ redirect_to }}, se o slug não tiver sido traduzido e cada conteúdo tiver uma única url, aqui vc pode por a url atual, {{ request.META.PATH_INFO }},  e quando o usuário trocar de idioma, já vai ser redirecionado para a mesma página que estava visualizando.



Referências:


hasta!

segunda-feira, 15 de dezembro de 2014

Apps não aparecem no administrativo mesmo como superuser autenticado

Criou os apps e tem certeza que as classes no admin.py estão corretas?

Caso tenha, e/ou esteja fazendo um downgrade do django 1.7 pro 1.6, e estava funcionando antes, certifique-se que colocou a linha abaixo no urls.py antes do urlpatterns:


...
admin.autodiscover()
...


hasta!

sábado, 22 de novembro de 2014

Definindo uma linguagem inicial para o projeto, não importando a linguagem setada pelo navegador

Eis o middleware que faz a mágica:


class ForceDefaultLanguageMiddleware(object):
    """
    Ignorar cabeçalhos HTTP Accept-Language

    Isso vai forçar as máquinas I18N para escolher sempre settings.LANGUAGE_CODE
    como o idioma inicial padrão, a menos que um outro é definido através de sessões ou cookies

    Deve ser instalado * antes * qualquer middleware que verifica request.META [' HTTP_ACCEPT_LANGUAGE '] ,
    como por exemplo:  django.middleware.locale.LocaleMiddleware
    """

    def process_request(self, request):
        if request.META.has_key('HTTP_ACCEPT_LANGUAGE'):
            del request.META['HTTP_ACCEPT_LANGUAGE']


e no settings.py, basta colocar o middleware antes do LocaleMiddleware como dito acima:

MIDDLEWARE_CLASSES = (
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'settings.middleware.ForceDefaultLanguageMiddleware',
    'django.middleware.locale.LocaleMiddleware',
)


hasta!

sexta-feira, 21 de novembro de 2014

Plugins Úteis - Color picker no Django Admin

Que tal um color picker parao admin quando precisar selecionar uma cor e ter certeza que o usuário não faz ideia do que #0000FF quer dizer?

Simples.

Você vai precisar de um plugin:
pip install django-paintstore


models.py

No seu models.py deixe como a seguir:

# coding: utf-8
from django.db import models
from paintstore.fields import ColorPickerField

# Create your models here.
class SUA_CLASSE(models.Model):
 """(SUA_CLASSE description)"""
 ...
 cor = ColorPickerField(help_text="Clique no campo para abrir o colorpicker.", max_length=7)
 ...

admin.py

E deixe o admin.py deixa como abaixo:

# coding: utf-8
from django.contrib import admin
from django import forms

from .models import *
# Register your models here.

class SUA_CLASSEForm(forms.ModelForm):

 class Media:
  css = {'all': ('/static/paintstore/css/colorpicker.css','/static/site/js/plugins/paintstore/init.css')}
  js = (
   '/static/paintstore/jquery_1.7.2.js',
   '/static/paintstore/colorpicker.js',
   '/static/site/js/plugins/paintstore/init.js',
  )
 class Meta:
  model = SUA_CLASSE
  fields = '__all__'

class SUA_CLASSEFormAdmin(admin.ModelAdmin):
 form = SUA_CLASSEForm
 
admin.site.register(SUA_CLASSE, SUA_CLASSEAdmin)


RESOURCES:


Os arquivos referenciados acima estão abaixo, salve-os e altere os caminhos no Form, conforme necessidade:

init.js

jQuery(document).ready(function($) {
 $('#id_cor').ColorPicker({
  onSubmit: function(hsb, hex, rgb, el, parent) {
   $(el).val('#' + hex);
   $(el).ColorPickerHide();
   $(el).css({
    color: ''+$('#id_cor_titulo').val(),
    backgroundColor: '#'+hex
   });
  },
  onBeforeShow: function () {
   $(this).ColorPickerSetColor(this.value);
  }
 }).bind('keyup', function(){
  $(this).ColorPickerSetColor(this.value.replace('#', ''));
 });

 $('#id_cor,').css({
  color: ''+$('#id_cor_titulo').val(),
  backgroundColor: $('#id_cor_titulo').val()
 });
});

init.css

#id_cor{
 width:50px;
 background-color:red;
 cursor:pointer;
 color:black;
 border:solid 1px black;
}

quinta-feira, 14 de agosto de 2014

Gerando um PDF em Django sem template e forçando seu donwload

Você vai precisar dos seguintes aplicativos instalados no ENV:
  • reportlab==2.7

Faça uma view conforme abaixo:

#############################################################
# PDF
#############################################################
class PDFView(View):

 def post(self, request, *args, **kwargs):
  # IMPORTS NECESSARIOS
  from reportlab.pdfgen import canvas
  from reportlab.lib.pagesizes import letter, landscape, A4
  from reportlab.lib.units import inch, cm
  from reportlab.lib.colors import HexColor
  from django.conf import settings
  import os

  # VALIDAÇÃO DO CSRF_TOKEN
  c = {}
  c.update(csrf(request))
  
  # NOME DO ARQUIVO
  nome_arquivo = "1.pdf"
  arquivo = '%s/uploads/%s' % (settings.MEDIA_ROOT, nome_arquivo)

  if os.path.isfile(arquivo):
   # REDIRECT CASO NÃO ENCONTRAR O ARQUIVO
   return redirect('/')

  imagem =  '%s/%s' % (settings.MEDIA_ROOT, "sua_imagem_aqui.jpg")

  # CRIAÇÃO DO PDF EM MODO PAISAGEM , TAMANHO A4
  c = canvas.Canvas(arquivo, pagesize=landscape(A4))
  c.drawImage(
   '%s' % imagem,
   0,
   0,
   29.7*cm,
   21*cm
  )

  # DEFINIÇÃO DE FONTE, FAMILIA, TAMANHO E COR
  c.setFont("Helvetica", 16)
  c.setFillColor(HexColor(0x094d8a))

  # ESCRITA DO TEXTO POR CIMA DA IMAGEM COM ALINHAMENTO
  c.drawString(18.4*cm, 14*cm, "%s" % participante.nome)

  # GERAÇÃO E CRIACAO DO PDF
  c.showPage()
  c.save()

  # REDIRECT PARA VIEW QUE FORCARÁ O DOWNLOAD
  return redirect('certificados:download',participante.id)

 def get(self, request, *args, **kwargs):
  return redirect('/')


#############################################################
# DOWNLOAD
#############################################################
class ForceDownload(View):
 def get(self, request, *args, **kwargs):
  from django.http import HttpResponse
  from os import path
  
  import mimetypes

  arquivo = '%s/uploads/%s.pdf' % (settings.MEDIA_ROOT, kwargs['pk'])

  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 # Create your views here.


E no urls deixe da seguinte forma:


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

urlpatterns = patterns('',

 ############################################################################
 # DOWNLOADS
 ############################################################################
 url(r'^downloads/(?P<pk>\d+)/$', views.ForceDownload.as_view(), name='download'),

 ############################################################################
 # CERTIFICADO
 ############################################################################
 url(r'^(?P<slug>[-\w]+)/$', views.PDFView.as_view(), name='pdf_detail'),


)

hasta!

quarta-feira, 2 de julho de 2014

Como fazer a contrab trabalhar pra você

Normalmente quando é necessário uma tarefa repetitiva e que possui um certo grau de integração com outros serviços, utilizo o recurso da contrab.

No python basta criar seu arquivo e definir nele o que será feito. Em seguida basta agendar a tarefa na crontab de acordo com a periodicidade necessária.

Para listar as tarefas agendadas na cron basta executar o comando:
crontab -l

Para criar/editar tarefas basta executar o comando:
crontab -e

Vale ressaltar que caso nao goste do editor que a cron utiliza, pode trocar por algum  de sua preferencia, como o nano, basta executar o seguinte comando:

export VISUAL=nano

Para definir o tempo que você pode fornecer valores para minutos, horas, dias do mes, meses e dias da semana. Nesta ordem. Também é possível definir * para qualquer um destes parametros de forma a referenciar qualquer valor.

Outra coisa que vale dizer é : Se vc precisa executar uma rotina que precisa de mais de um comando , certifique-se que todas as linhas sejam aglutinadas em apenas uma, através do &&.

Exemplo:

# m h  dom mon dow   command

0 * * * 1,2,3,4,5 . /ENV/bin/activate && python carga.py && deactivate

O comando acima roda sempre que os minutos forem iguais a 0, para todas a horas, em todos os dias do mês, durante todos os meses e somente de segunda a sexta. Ele inicia o  ambiente virtual, executa o arquivo carga.py e desativa o ambiente virtual.

quarta-feira, 7 de maio de 2014

Paginação estilo Twitter com Django CBV e django-endless-pagination



Quem já não precisou fazer uma paginação ao estilo do twitter? Bom não é complicado. Pelo menos não com as dicas abaixo!

Requisitos necessários:

  • Python >= 2.6
  • Django >= 1.3
  • jQuery >= 1.7


Instalação:
pip install django-endless-pagination

Configuração:


Adicione a app na setting INSTALLED_APPS no settings.py:
...
'endless_pagination',
...

Adicione a linha abaixo na setting TEMPLATE_CONTEXT_PROCESSORS também no settings.py:
...
'django.core.context_processors.request',
...

Na views.py

from django.shortcuts import render, render_to_response, get_object_or_404
from django.template import Context, loader, RequestContext
from .models import Produto

class ProdutoTTPag(View):
 def get(self, request, *args, **kwargs):

  template = 'produtos/lista-produtos.html'
  page_template = 'produtos/paginacao.html'

  produtos = Produto.objects.filter(ativo=True)
  
  context = {
   'page_template': page_template,
   'produtos_list' : produtos,
  }

  if request.is_ajax():
   template = page_template

  return render_to_response(template, context, context_instance=RequestContext(request))


No urls.py

Adicione a linha abaixo:
...
url(r'^(?P[-\w]+)/$', views.ProdutoTTPag.as_view(), name='produtos'),
...


No template:

Aqui será necessário a criação de dois arquivos html. Um para ser de fato a lista (lista-produtos.html) e o outro para ser apenas os itens, para que o ajax possa trazê-los de forma correta (paginacao.html).

lista-produtos.html:
...
{% load endless %}
...

{% block extra_js %}
    <script src="{{ STATIC_URL }}endless_pagination/js/endless-pagination.js" type="text/javascript"></script>
    <script type="text/javascript">
     jQuery(document).ready(function($) {
      //$.endlessPaginate({
         //    paginateOnScroll: true,
         //    paginateOnScrollChunkSize: 5,
         //    paginateOnScrollMargin: 200
         //});

      $.endlessPaginate({});
     });
    </script>
{% endblock extra_js %}

...
<section class="list-products">
 <ul>
  {% include page_template %}
 </ul>
...
</section>

Aqui a parte comentada serve para trazer os resultados das outras páginas conforme o scroll da página.

paginacao.html:

{% load endless %}

{% paginate 9 produtos_list %}
{% for p in produtos_list %}
 <li>
  {# HTML PARA ITEM DO PRODUTO #}
 </li>
{% endfor %}
{% show_more %}

Mais informações em:

  1. http://django-endless-pagination.readthedocs.org/en/latest/index.html
  2. https://github.com/frankban/django-endless-pagination
  3. https://pypi.python.org/pypi/django-endless-pagination/2.0


hasta!

quarta-feira, 16 de abril de 2014

error: command 'cc' failed with exit status 1

Problemas para instalação das libs do python no ENV no Mavericks?

Com o ENV ligado, se a instalação de algum pacote apresentar o seguinte erro:

error: command 'cc' failed with exit status 1

Execute estas duas linhas no seu shell:

export CFLAGS=-Qunused-arguments

export CPPFLAGS=-Qunused-arguments


Essas exportações dizem ao compilador para ignorar argumentos não utilizados, em vez de reclamar sobre eles.

A razão parece ser que Python compila módulos usando as opções com que ele foi construído, exceto uma dessas opções não funciona mais em mavericks.

Agora é só instalar suas libs normalmente com o pip.


hasta!

quinta-feira, 10 de abril de 2014

Como fazer um filtro personalizado pro admin do Django com o SimpleListFilter

Muitas vezes os filtros automáticos que o admin do django faz são muito úteis. Contudo há momentos que simplesmente colocar o nome do atributo no list_filter não atende. Pra esses momentos temos o SimpleListFilter. 

Ano de Copa, álbuns de figurinhas à venda, suponhamos que, assim como eu, você tenha feito (ou queira fazer)  um sistema pra gerenciamento das figurinhas da Panini.

O models.py ficaria assim:

# coding: utf-8
from django.db import models
class Figurinha(models.Model):
 """(Figurinha description)"""
 numero = models.CharField(max_length=3, verbose_name=u'Número')
 quantidade = models.IntegerField(default=0)
 especial = models.BooleanField(default=False, help_text='Figurinhas brilhantes e/ou diferenciadas')
 data = models.DateTimeField(null=True, blank=True, auto_now=True, verbose_name=u'Modificação')

 class Meta:
  ordering = ('id',)

 def __unicode__(self):
  return self.numero



Algumas explicações antes de continuarmos:

  • Número é sim um CharField, pois neste álbum existem figurinhas alfanuméricas (J1, J2, J3, J4, L1, L2, L3, L4 e W1)
  • Quantidade será o campo atualizável, onde 0 são as figurinhas que faltam, 1 pra colada no álbum e maiores que 1 pra quantidade de repetidas. Ex: Quantidade = 2, são 1 no álbum e 1 repetida.
  • Data será atualizada a cada atualização da figurinha para que seja possível emitir a data da ultima atualização da lista

E o admin.py :

class StatusListFilter(admin.SimpleListFilter):
 # Human-readable title which will be displayed in the
 # right admin sidebar just above the filter options.
 title = 'Status'

 # Parameter for the filter that will be used in the URL query.
 parameter_name = 's'

 def lookups(self, request, model_admin):
  """
  Returns a list of tuples. The first element in each
  tuple is the coded value for the option that will
  appear in the URL query. The second element is the
  human-readable name for the option that will appear
  in the right sidebar.
  """
  return (
   ('falta', 'Falta'),
   ('tenho', 'Tenho'),
   ('repetida', 'Repetida'),
  )

 def queryset(self, request, queryset):
  """
  Returns the filtered queryset based on the value
  provided in the query string and retrievable via
  `self.value()`.
  """
  # Compare the requested value (either '80s' or 'other')
  # to decide how to filter the queryset.
  if self.value() == 'falta':
   return queryset.filter(quantidade=0)
  elif self.value() == 'tenho':
   return queryset.filter(quantidade__gt=0)
  elif self.value() == 'repetida':
   return queryset.filter(quantidade__gt=1)

class FigurinhaAdmin(admin.ModelAdmin):
 search_fields = ('numero',)
 list_display = ('numero','quantidade','especial','data')
 list_filter = ['quantidade','especial','data',StatusListFilter]
 list_editable = ['quantidade',]
 date_hierarchy = 'data'
 readonly_fields = ['numero','especial',]
 save_on_top = True

 fieldsets = (
  (u'Número', {'fields': ('numero',)}),
  ('Quantidade', {'fields': ('quantidade',)}),
  ('Especial', {'fields': ('especial',)}),
 )

admin.site.register(Figurinha, FigurinhaAdmin)


Para mais informações sobre o SimpleListFilter, consulte https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.list_filter

hasta!

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